added support for encrypted and symmetrically signed id tokens and user info responses
parent
ffe1b29906
commit
05e9624ae3
|
@ -45,8 +45,7 @@ public interface OIDCTokenService {
|
|||
*/
|
||||
public OAuth2AccessTokenEntity createIdToken(
|
||||
ClientDetailsEntity client, OAuth2Request request, Date issueTime,
|
||||
String sub, JWSAlgorithm signingAlg,
|
||||
OAuth2AccessTokenEntity accessToken);
|
||||
String sub, OAuth2AccessTokenEntity accessToken);
|
||||
|
||||
/**
|
||||
* Create a registration access token for the given client.
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
package org.mitre.discovery.web;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mitre.discovery.util.WebfingerURLNormalizer;
|
||||
|
@ -278,12 +276,12 @@ public class DiscoveryEndpoint {
|
|||
m.put("grant_types_supported", Lists.newArrayList("authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer", "client_credentials", "urn:ietf:params:oauth:grant_type:redelegate"));
|
||||
//acr_values_supported
|
||||
m.put("subject_types_supported", Lists.newArrayList("public", "pairwise"));
|
||||
//userinfo_signing_alg_values_supported
|
||||
//userinfo_encryption_alg_values_supported
|
||||
//userinfo_encryption_enc_values_supported
|
||||
m.put("id_token_signing_alg_values_supported", Collections2.transform(serverSigningAlgs, toAlgorithmName));
|
||||
//id_token_encryption_alg_values_supported
|
||||
//id_token_encryption_enc_values_supported
|
||||
m.put("userinfo_signing_alg_values_supported", Collections2.transform(clientSymmetricAndAsymmetricSigningAlgs, toAlgorithmName));
|
||||
m.put("userinfo_encryption_alg_values_supported", Collections2.transform(encService.getAllEncryptionAlgsSupported(), toAlgorithmName));
|
||||
m.put("userinfo_encryption_enc_values_supported", Collections2.transform(encService.getAllEncryptionEncsSupported(), toAlgorithmName));
|
||||
m.put("id_token_signing_alg_values_supported", Collections2.transform(clientSymmetricAndAsymmetricSigningAlgs, toAlgorithmName));
|
||||
m.put("id_token_encryption_alg_values_supported", Collections2.transform(encService.getAllEncryptionAlgsSupported(), toAlgorithmName));
|
||||
m.put("id_token_encryption_enc_values_supported", Collections2.transform(encService.getAllEncryptionEncsSupported(), toAlgorithmName));
|
||||
m.put("request_object_signing_alg_values_supported", Collections2.transform(clientSymmetricAndAsymmetricSigningAlgs, toAlgorithmName));
|
||||
m.put("request_object_encryption_alg_values_supported", Collections2.transform(encService.getAllEncryptionAlgsSupported(), toAlgorithmName));
|
||||
m.put("request_object_encryption_enc_values_supported", Collections2.transform(encService.getAllEncryptionEncsSupported(), toAlgorithmName));
|
||||
|
|
|
@ -21,7 +21,10 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService;
|
||||
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
|
||||
import org.mitre.jwt.signer.service.impl.SymmetricCacheService;
|
||||
import org.mitre.oauth2.model.AuthenticationHolderEntity;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
|
@ -44,9 +47,12 @@ import com.google.common.base.Strings;
|
|||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.nimbusds.jose.Algorithm;
|
||||
import com.nimbusds.jose.JWEHeader;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jose.util.Base64URL;
|
||||
import com.nimbusds.jwt.EncryptedJWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
|
@ -70,8 +76,21 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
|
|||
@Autowired
|
||||
private ConfigurationPropertiesBean configBean;
|
||||
|
||||
@Autowired
|
||||
private JWKSetCacheService encrypters;
|
||||
|
||||
@Autowired
|
||||
private SymmetricCacheService symmetricCacheService;
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, String sub, JWSAlgorithm signingAlg, OAuth2AccessTokenEntity accessToken) {
|
||||
public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, String sub, OAuth2AccessTokenEntity accessToken) {
|
||||
|
||||
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
|
||||
|
||||
if (client.getIdTokenSignedResponseAlg() != null) {
|
||||
signingAlg = client.getIdTokenSignedResponseAlg();
|
||||
}
|
||||
|
||||
|
||||
OAuth2AccessTokenEntity idTokenEntity = new OAuth2AccessTokenEntity();
|
||||
JWTClaimsSet idClaims = new JWTClaimsSet();
|
||||
|
@ -105,12 +124,44 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
|
|||
Base64URL at_hash = IdTokenHashUtils.getAccessTokenHash(signingAlg, accessToken);
|
||||
idClaims.setClaim("at_hash", at_hash);
|
||||
}
|
||||
|
||||
if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE)
|
||||
&& client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE)
|
||||
&& !Strings.isNullOrEmpty(client.getJwksUri())) {
|
||||
|
||||
JwtEncryptionAndDecryptionService encrypter = encrypters.getEncrypter(client.getJwksUri());
|
||||
|
||||
if (encrypter != null) {
|
||||
|
||||
EncryptedJWT idToken = new EncryptedJWT(new JWEHeader(client.getIdTokenEncryptedResponseAlg(), client.getIdTokenEncryptedResponseEnc()), idClaims);
|
||||
|
||||
encrypter.encryptJwt(idToken);
|
||||
|
||||
idTokenEntity.setJwt(idToken);
|
||||
|
||||
} else {
|
||||
logger.error("Couldn't find encrypter for client: " + client.getClientId());
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
SignedJWT idToken = new SignedJWT(new JWSHeader(signingAlg), idClaims);
|
||||
|
||||
jwtService.signJwt(idToken);
|
||||
|
||||
idTokenEntity.setJwt(idToken);
|
||||
SignedJWT idToken = new SignedJWT(new JWSHeader(signingAlg), idClaims);
|
||||
|
||||
if (signingAlg.equals(JWSAlgorithm.HS256)
|
||||
|| signingAlg.equals(JWSAlgorithm.HS384)
|
||||
|| signingAlg.equals(JWSAlgorithm.HS512)) {
|
||||
JwtSigningAndValidationService signer = symmetricCacheService.getSymmetricValidtor(client);
|
||||
|
||||
// sign it with the client's secret
|
||||
signer.signJwt(idToken);
|
||||
} else {
|
||||
|
||||
// sign it with the server's key
|
||||
jwtService.signJwt(idToken);
|
||||
}
|
||||
|
||||
idTokenEntity.setJwt(idToken);
|
||||
}
|
||||
|
||||
idTokenEntity.setAuthenticationHolder(accessToken.getAuthenticationHolder());
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ import java.util.Date;
|
|||
import java.util.UUID;
|
||||
|
||||
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
|
||||
import org.mitre.jwt.signer.service.impl.SymmetricCacheService;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
|
@ -39,6 +41,7 @@ import org.springframework.security.oauth2.provider.token.TokenEnhancer;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.nimbusds.jose.Algorithm;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
|
@ -67,6 +70,13 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
|
|||
@Autowired
|
||||
private OIDCTokenService connectTokenService;
|
||||
|
||||
@Autowired
|
||||
private JWKSetCacheService encryptors;
|
||||
|
||||
@Autowired
|
||||
private SymmetricCacheService symmetricCacheService;
|
||||
|
||||
|
||||
@Override
|
||||
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
|
||||
|
||||
|
@ -89,16 +99,13 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
|
|||
claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
|
||||
|
||||
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
|
||||
if (client.getIdTokenSignedResponseAlg() != null) {
|
||||
signingAlg = client.getIdTokenSignedResponseAlg();
|
||||
}
|
||||
|
||||
SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims);
|
||||
|
||||
jwtService.signJwt(signed);
|
||||
|
||||
token.setJwt(signed);
|
||||
|
||||
|
||||
/**
|
||||
* Authorization request scope MUST include "openid" in OIDC, but access token request
|
||||
* may or may not include the scope parameter. As long as the AuthorizationRequest
|
||||
|
@ -118,7 +125,7 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
|
|||
|
||||
OAuth2AccessTokenEntity idTokenEntity = connectTokenService.createIdToken(client,
|
||||
originalAuthRequest, claims.getIssueTime(),
|
||||
userInfo.getSub(), signingAlg, token);
|
||||
userInfo.getSub(), token);
|
||||
|
||||
// attach the id token to the parent access token
|
||||
token.setIdToken(idTokenEntity);
|
||||
|
|
|
@ -15,7 +15,10 @@ import java.util.UUID;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService;
|
||||
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
|
||||
import org.mitre.jwt.signer.service.impl.SymmetricCacheService;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -23,11 +26,15 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.nimbusds.jose.Algorithm;
|
||||
import com.nimbusds.jose.JWEHeader;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jwt.EncryptedJWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
|
@ -46,6 +53,12 @@ public class UserInfoJwtView extends UserInfoView {
|
|||
@Autowired
|
||||
private ConfigurationPropertiesBean config;
|
||||
|
||||
@Autowired
|
||||
private JWKSetCacheService encrypters;
|
||||
|
||||
@Autowired
|
||||
private SymmetricCacheService symmetricCacheService;
|
||||
|
||||
@Override
|
||||
protected void writeOut(JsonObject json, Map<String, Object> model,
|
||||
HttpServletRequest request, HttpServletResponse response) {
|
||||
|
@ -67,17 +80,53 @@ public class UserInfoJwtView extends UserInfoView {
|
|||
|
||||
claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
|
||||
|
||||
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
|
||||
if (client.getUserInfoSignedResponseAlg() != null) {
|
||||
signingAlg = client.getUserInfoSignedResponseAlg();
|
||||
}
|
||||
|
||||
SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims);
|
||||
|
||||
jwtService.signJwt(signed);
|
||||
if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE)
|
||||
&& client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE)
|
||||
&& !Strings.isNullOrEmpty(client.getJwksUri())) {
|
||||
|
||||
// encrypt it to the client's key
|
||||
|
||||
JwtEncryptionAndDecryptionService encrypter = encrypters.getEncrypter(client.getJwksUri());
|
||||
|
||||
if (encrypter != null) {
|
||||
|
||||
EncryptedJWT encrypted = new EncryptedJWT(new JWEHeader(client.getIdTokenEncryptedResponseAlg(), client.getIdTokenEncryptedResponseEnc()), claims);
|
||||
|
||||
encrypter.encryptJwt(encrypted);
|
||||
|
||||
|
||||
Writer out = response.getWriter();
|
||||
out.write(encrypted.serialize());
|
||||
|
||||
} else {
|
||||
logger.error("Couldn't find encrypter for client: " + client.getClientId());
|
||||
}
|
||||
} else {
|
||||
|
||||
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
|
||||
if (client.getUserInfoSignedResponseAlg() != null) {
|
||||
signingAlg = client.getUserInfoSignedResponseAlg();
|
||||
}
|
||||
|
||||
SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims);
|
||||
|
||||
Writer out = response.getWriter();
|
||||
out.write(signed.serialize());
|
||||
if (signingAlg.equals(JWSAlgorithm.HS256)
|
||||
|| signingAlg.equals(JWSAlgorithm.HS384)
|
||||
|| signingAlg.equals(JWSAlgorithm.HS512)) {
|
||||
|
||||
// sign it with the client's secret
|
||||
JwtSigningAndValidationService signer = symmetricCacheService.getSymmetricValidtor(client);
|
||||
signer.signJwt(signed);
|
||||
|
||||
} else {
|
||||
// sign it with the server's key
|
||||
jwtService.signJwt(signed);
|
||||
}
|
||||
|
||||
Writer out = response.getWriter();
|
||||
out.write(signed.serialize());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("IO Exception in UserInfoJwtView", e);
|
||||
} catch (ParseException e) {
|
||||
|
|
Loading…
Reference in New Issue