converted client filters to nimbus-jose

pull/306/merge
Justin Richer 2013-02-19 17:33:22 -05:00
parent 8d8010d90f
commit 3d9ec51eb3
4 changed files with 180 additions and 126 deletions

View File

@ -21,9 +21,9 @@ import java.math.BigInteger;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
@ -40,15 +40,10 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.mitre.jwt.model.JwtClaims;
import org.mitre.jwt.signer.JwsAlgorithm;
import org.mitre.jwt.signer.JwtSigner;
import org.mitre.jwt.signer.impl.RsaSigner;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService;
import org.mitre.key.fetch.KeyFetcher;
import org.mitre.openid.connect.config.OIDCServerConfiguration;
import org.mitre.openid.connect.model.IdToken;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
@ -60,9 +55,14 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jwt.ReadOnlyJWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
/**
* Abstract OpenID Connect Authentication Filter class
@ -339,95 +339,105 @@ public class AbstractOIDCAuthenticationFilter extends
refreshTokenValue = tokenResponse.get("refresh_token").getAsString();
}
IdToken idToken = IdToken.parse(idTokenValue); // TODO: catch parsing errors?
try {
SignedJWT idToken = SignedJWT.parse(idTokenValue);
// validate our ID Token over a number of tests
JwtClaims idClaims = idToken.getClaims();
// check the signature
JwtSigningAndValidationService jwtValidator = getValidatorForServer(serverConfig);
if (jwtValidator != null) {
if(!jwtValidator.validateSignature(idToken.toString())) {
throw new AuthenticationServiceException("Signature validation failed");
}
} else {
logger.info("No validation service found. Skipping signature validation");
}
// check the issuer
if (idClaims.getIssuer() == null) {
throw new AuthenticationServiceException("Id Token Issuer is null");
} else if (!idClaims.getIssuer().equals(serverConfig.getIssuer())){
throw new AuthenticationServiceException("Issuers do not match, expected " + serverConfig.getIssuer() + " got " + idClaims.getIssuer());
}
// check expiration
if (idClaims.getExpiration() == null) {
throw new AuthenticationServiceException("Id Token does not have required expiration claim");
} else {
// it's not null, see if it's expired
Date now = new Date(System.currentTimeMillis() - (timeSkewAllowance * 1000));
if (now.after(idClaims.getExpiration())) {
throw new AuthenticationServiceException("Id Token is expired: " + idClaims.getExpiration());
}
}
// check not before
if (idClaims.getNotBefore() != null) {
Date now = new Date(System.currentTimeMillis() + (timeSkewAllowance * 1000));
if (now.before(idClaims.getNotBefore())){
throw new AuthenticationServiceException("Id Token not valid untill: " + idClaims.getNotBefore());
}
}
// check audience
if (idClaims.getAudience() == null) {
throw new AuthenticationServiceException("Id token audience is null");
} else if (!idClaims.getAudience().equals(serverConfig.getClientId())) {
throw new AuthenticationServiceException("Audience does not match, expected " + serverConfig.getClientId() + " got " + idClaims.getAudience());
}
// check issued at
if (idClaims.getIssuedAt() == null) {
throw new AuthenticationServiceException("Id Token does not have required issued-at claim");
} else {
// since it's not null, see if it was issued in the future
Date now = new Date(System.currentTimeMillis() + (timeSkewAllowance * 1000));
if (now.before(idClaims.getIssuedAt())) {
throw new AuthenticationServiceException("Id Token was issued in the future: " + idClaims.getIssuedAt());
}
}
// validate our ID Token over a number of tests
ReadOnlyJWTClaimsSet idClaims = idToken.getJWTClaimsSet();
// check the signature
JwtSigningAndValidationService jwtValidator = getValidatorForServer(serverConfig);
if (jwtValidator != null) {
if(!jwtValidator.validateSignature(idToken)) {
throw new AuthenticationServiceException("Signature validation failed");
}
} else {
logger.info("No validation service found. Skipping signature validation");
}
// check the issuer
if (idClaims.getIssuerClaim() == null) {
throw new AuthenticationServiceException("Id Token Issuer is null");
} else if (!idClaims.getIssuerClaim().equals(serverConfig.getIssuer())){
throw new AuthenticationServiceException("Issuers do not match, expected " + serverConfig.getIssuer() + " got " + idClaims.getIssuerClaim());
}
// check expiration
// FIXME: Nimbus Date Fields
/*
if (idClaims.getExpirationTimeClaim() == 0) {
throw new AuthenticationServiceException("Id Token does not have required expiration claim");
} else {
// it's not null, see if it's expired
Date now = new Date(System.currentTimeMillis() - (timeSkewAllowance * 1000));
if (now.after(new Date(idClaims.getExpirationTimeClaim()))) {
throw new AuthenticationServiceException("Id Token is expired: " + idClaims.getExpirationTimeClaim());
}
}
// check not before
// FIXME: Nimbus Date Fields
if (idClaims.getNotBefore() != null) {
Date now = new Date(System.currentTimeMillis() + (timeSkewAllowance * 1000));
if (now.before(idClaims.getNotBefore())){
throw new AuthenticationServiceException("Id Token not valid untill: " + idClaims.getNotBefore());
}
}
// check issued at
if (idClaims.getIssuedAt() == null) {
throw new AuthenticationServiceException("Id Token does not have required issued-at claim");
} else {
// since it's not null, see if it was issued in the future
Date now = new Date(System.currentTimeMillis() + (timeSkewAllowance * 1000));
if (now.before(idClaims.getIssuedAt())) {
throw new AuthenticationServiceException("Id Token was issued in the future: " + idClaims.getIssuedAt());
}
}
*/
// check audience
// FIXME: Nimbus audience collection
if (idClaims.getAudienceClaim() == null) {
throw new AuthenticationServiceException("Id token audience is null");
} else if (!Arrays.asList(idClaims.getAudienceClaim()).contains(serverConfig.getClientId())) {
throw new AuthenticationServiceException("Audience does not match, expected " + serverConfig.getClientId() + " got " + idClaims.getAudienceClaim());
}
// compare the nonce to our stored claim
// FIXME: Nimbus claims as strings?
String nonce = (String) idClaims.getCustomClaim("nonce");
if (StringUtils.isBlank(nonce)) {
logger.error("ID token did not contain a nonce claim.");
// compare the nonce to our stored claim
String nonce = idClaims.getNonce();
if (StringUtils.isBlank(nonce)) {
logger.error("ID token did not contain a nonce claim.");
throw new AuthenticationServiceException("ID token did not contain a nonce claim.");
}
throw new AuthenticationServiceException("ID token did not contain a nonce claim.");
}
String storedNonce = getStoredNonce(session);
if (!nonce.equals(storedNonce)) {
logger.error("Possible replay attack detected! The comparison of the nonce in the returned "
+ "ID Token to the session " + NONCE_SESSION_VARIABLE + " failed. Expected " + storedNonce + " got " + nonce + ".");
String storedNonce = getStoredNonce(session);
if (!nonce.equals(storedNonce)) {
logger.error("Possible replay attack detected! The comparison of the nonce in the returned "
+ "ID Token to the session " + NONCE_SESSION_VARIABLE + " failed. Expected " + storedNonce + " got " + nonce + ".");
throw new AuthenticationServiceException(
"Possible replay attack detected! The comparison of the nonce in the returned "
+ "ID Token to the session " + NONCE_SESSION_VARIABLE + " failed. Expected " + storedNonce + " got " + nonce + ".");
}
throw new AuthenticationServiceException(
"Possible replay attack detected! The comparison of the nonce in the returned "
+ "ID Token to the session " + NONCE_SESSION_VARIABLE + " failed. Expected " + storedNonce + " got " + nonce + ".");
}
// pull the subject (user id) out as a claim on the id_token
String userId = idClaims.getSubjectClaim();
// construct an OIDCAuthenticationToken and return a Authentication object w/the userId and the idToken
OIDCAuthenticationToken token = new OIDCAuthenticationToken(userId, idClaims.getIssuerClaim(), serverConfig, idTokenValue, accessTokenValue, refreshTokenValue);
// pull the subject (user id) out as a claim on the id_token
String userId = idToken.getClaims().getSubject();
// construct an OIDCAuthenticationToken and return a Authentication object w/the userId and the idToken
OIDCAuthenticationToken token = new OIDCAuthenticationToken(userId, idClaims.getIssuer(), serverConfig, idTokenValue, accessTokenValue, refreshTokenValue);
Authentication authentication = this.getAuthenticationManager().authenticate(token);
Authentication authentication = this.getAuthenticationManager().authenticate(token);
return authentication;
return authentication;
} catch (ParseException e) {
throw new AuthenticationServiceException("Couldn't parse idToken: ", e);
}
@ -618,27 +628,17 @@ public class AbstractOIDCAuthenticationFilter extends
}
if (signingKey != null) {
Map<String, JwtSigner> signers = new HashMap<String, JwtSigner>();
if (signingKey instanceof RSAPublicKey) {
RSAPublicKey rsaKey = (RSAPublicKey)signingKey;
// build an RSA signer
RsaSigner signer256 = new RsaSigner(JwsAlgorithm.RS256.getJwaName(), rsaKey, null);
RsaSigner signer384 = new RsaSigner(JwsAlgorithm.RS384.getJwaName(), rsaKey, null);
RsaSigner signer512 = new RsaSigner(JwsAlgorithm.RS512.getJwaName(), rsaKey, null);
signers.put(serverConfig.getIssuer() + JwsAlgorithm.RS256.getJwaName(), signer256);
signers.put(serverConfig.getIssuer() + JwsAlgorithm.RS384.getJwaName(), signer384);
signers.put(serverConfig.getIssuer() + JwsAlgorithm.RS512.getJwaName(), signer512);
}
JwtSigningAndValidationService signingAndValidationService = new DefaultJwtSigningAndValidationService(signers);
// TODO: this assumes RSA
JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) signingKey);
validationServices.put(serverConfig, signingAndValidationService);
Map<String, JWSVerifier> verifiers = ImmutableMap.of(serverConfig.getIssuer(), verifier);
return signingAndValidationService;
JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService();
validationServices.put(serverConfig, service);
return service;
} else {
// there were either no keys returned or no URLs configured to fetch them, assume no checking on key signatures

View File

@ -2,6 +2,7 @@ package org.mitre.openid.connect.client;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ -12,9 +13,6 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.StringUtils;
import org.mitre.jwt.model.Jwt;
import org.mitre.jwt.model.JwtClaims;
import org.mitre.jwt.signer.JwtSigner;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.openid.connect.config.OIDCServerConfiguration;
import org.mitre.openid.connect.view.JwkKeyListView;
@ -30,6 +28,9 @@ import org.springframework.util.Assert;
import org.springframework.web.servlet.ModelAndView;
import com.google.common.base.Strings;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
public class OIDCSignedRequestFilter extends AbstractOIDCAuthenticationFilter implements BeanDefinitionRegistryPostProcessor {
@ -108,7 +109,7 @@ public class OIDCSignedRequestFilter extends AbstractOIDCAuthenticationFilter im
public void handleAuthorizationRequest(HttpServletRequest request, HttpServletResponse response,
OIDCServerConfiguration serverConfiguration) throws IOException {
Jwt jwt = createAndSignRequestJwt(request, response, serverConfiguration);
SignedJWT jwt = createAndSignRequestJwt(request, response, serverConfiguration);
Map<String, String> urlVariables = new HashMap<String, String>();
@ -121,31 +122,33 @@ public class OIDCSignedRequestFilter extends AbstractOIDCAuthenticationFilter im
response.sendRedirect(authRequest);
}
public Jwt createAndSignRequestJwt(HttpServletRequest request, HttpServletResponse response, OIDCServerConfiguration serverConfiguration) {
public SignedJWT createAndSignRequestJwt(HttpServletRequest request, HttpServletResponse response, OIDCServerConfiguration serverConfiguration) {
HttpSession session = request.getSession();
Jwt jwt = new Jwt();
JwtClaims claims = jwt.getClaims();
JWTClaimsSet claims = new JWTClaimsSet();
//set parameters to JwtHeader
// header.setAlgorithm(JwsAlgorithm.getByName(SIGNING_ALGORITHM).toString());
//set parameters to JwtClaims
claims.setClaim("response_type", "code");
claims.setClaim("client_id", serverConfiguration.getClientId());
claims.setClaim("scope", scope);
claims.setCustomClaim("response_type", "code");
claims.setCustomClaim("client_id", serverConfiguration.getClientId());
claims.setCustomClaim("scope", scope);
// build our redirect URI
String redirectUri = buildRedirectURI(request, null);
claims.setClaim("redirect_uri", redirectUri);
claims.setCustomClaim("redirect_uri", redirectUri);
session.setAttribute(REDIRECT_URI_SESION_VARIABLE, redirectUri);
//create random nonce and state, save them to the session
String nonce = createNonce(session);
claims.setClaim("nonce", nonce);
claims.setCustomClaim("nonce", nonce);
String state = createState(session);
claims.setClaim("state", state);
claims.setCustomClaim("state", state);
SignedJWT jwt = new SignedJWT(new JWSHeader(serverConfiguration.getSigningAlgorithm()), claims);
try {
signingAndValidationService.signJwt(jwt);
@ -285,12 +288,12 @@ public class OIDCSignedRequestFilter extends AbstractOIDCAuthenticationFilter im
*/
public ModelAndView publishClientJwk() {
// map from key id to signer
Map<String, JwtSigner> signers = signingAndValidationService.getAllSigners();
// map from key id to key
Map<String, PublicKey> keys = signingAndValidationService.getAllPublicKeys();
// TODO: check if keys are empty, return a 404 here or just an empty list?
return new ModelAndView(jwkViewName, "signers", signers);
return new ModelAndView(jwkViewName, "keys", keys);
}
/**
@ -298,12 +301,12 @@ public class OIDCSignedRequestFilter extends AbstractOIDCAuthenticationFilter im
* @return
*/
public ModelAndView publishClientx509() {
// map from key id to signer
Map<String, JwtSigner> signers = signingAndValidationService.getAllSigners();
// map from key id to key
Map<String, PublicKey> keys = signingAndValidationService.getAllPublicKeys();
// TODO: check if keys are empty, return a 404 here or just an empty list?
return new ModelAndView(x509ViewName, "signers", signers);
return new ModelAndView(x509ViewName, "keys", keys);
}
/**

View File

@ -53,6 +53,11 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
}
public DefaultJwtSigningAndValidationService(Map<String, JWSSigner> signers, Map<String, JWSVerifier> verifiers) {
this.signers = signers;
this.verifiers = verifiers;
}
public DefaultJwtSigningAndValidationService(Map<String, RSASSASignerVerifierBuilder> builders) {
for (Entry<String, RSASSASignerVerifierBuilder> e : builders.entrySet()) {

View File

@ -15,6 +15,10 @@
******************************************************************************/
package org.mitre.openid.connect.config;
import javax.persistence.Basic;
import com.nimbusds.jose.JWSAlgorithm;
/**
* @author nemonik
@ -41,6 +45,8 @@ public class OIDCServerConfiguration {
private String jwkSigningUrl;
private String userInfoUrl;
private JWSAlgorithm signingAlgorithm;
public String getAuthorizationEndpointUrl() {
return authorizationEndpointUrl;
@ -137,4 +143,44 @@ public class OIDCServerConfiguration {
+ x509SigningUrl + ", jwkEncryptUrl=" + jwkEncryptUrl + ", jwkSigningUrl=" + jwkSigningUrl + ", userInfoUrl=" + userInfoUrl + "]";
}
/**
* @return the signingAlgorithm
*/
public JWSAlgorithm getSigningAlgorithm() {
return signingAlgorithm;
}
/**
* @param signingAlgorithm the signingAlgorithm to set
*/
public void setSigningAlgorithm(JWSAlgorithm signingAlgorithm) {
this.signingAlgorithm = signingAlgorithm;
}
/**
* Get the name of this algorithm, return null if no algorithm set.
* @return
*/
@Basic
public String getAlgorithmName() {
if (signingAlgorithm != null) {
return signingAlgorithm.getName();
} else {
return null;
}
}
/**
* Set the name of this algorithm.
* Calls JWSAlgorithm.parse()
* @param algorithmName
*/
public void setAlgorithmName(String algorithmName) {
if (algorithmName != null) {
signingAlgorithm = JWSAlgorithm.parse(algorithmName);
} else {
signingAlgorithm = null;
}
}
}