added signed authentication assertion support to client, closes #558

pull/576/head
Justin Richer 2014-03-06 16:44:31 +00:00
parent b67121f0cd
commit 3629ff5e2f
1 changed files with 107 additions and 4 deletions

View File

@ -16,12 +16,14 @@
******************************************************************************/
package org.mitre.openid.connect.client;
import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC;
import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.*;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;
@ -35,7 +37,9 @@ import javax.servlet.http.HttpSession;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.SystemDefaultHttpClient;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.RegisteredClient;
import org.mitre.openid.connect.client.model.IssuerServiceResponse;
import org.mitre.openid.connect.client.service.AuthRequestOptionsService;
@ -61,10 +65,19 @@ import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.Use;
import com.nimbusds.jose.util.Base64;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.ReadOnlyJWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
@ -90,6 +103,9 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi
@Autowired
private JWKSetCacheService validationServices;
@Autowired(required=false)
private JwtSigningAndValidationService authenticationSignerService;
// modular services to build out client filter
private ServerConfigurationService servers;
@ -291,11 +307,61 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi
return httpRequest;
}
};
} else { //Alternatively use form based auth
} else {
// we're not doing basic auth, figure out what other flavor we have
restTemplate = new RestTemplate(factory);
form.add("client_id", clientConfig.getClientId());
form.add("client_secret", clientConfig.getClientSecret());
if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod()) || PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) {
// do a symmetric secret signed JWT for auth
JwtSigningAndValidationService signer = null;
JWSAlgorithm alg = clientConfig.getTokenEndpointAuthSigningAlg();
if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod()) &&
(alg.equals(JWSAlgorithm.HS256)
|| alg.equals(JWSAlgorithm.HS384)
|| alg.equals(JWSAlgorithm.HS512))) {
// generate one based on client secret
signer = getSymmetricValidtor(clientConfig.getClient());
} else if (PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) {
// needs to be wired in to the bean
signer = authenticationSignerService;
}
if (signer == null) {
throw new AuthenticationServiceException("Couldn't find required signer service for use with private key auth.");
}
JWTClaimsSet claimsSet = new JWTClaimsSet();
claimsSet.setIssuer(clientConfig.getClientId());
claimsSet.setSubject(clientConfig.getClientId());
claimsSet.setAudience(Lists.newArrayList(serverConfig.getIssuer()));
// TODO: make this configurable
Date exp = new Date(System.currentTimeMillis() + (60 * 1000)); // auth good for 60 seconds
claimsSet.setExpirationTime(exp);
Date now = new Date(System.currentTimeMillis());
claimsSet.setIssueTime(now);
claimsSet.setNotBeforeTime(now);
SignedJWT jwt = new SignedJWT(new JWSHeader(alg), claimsSet);
signer.signJwt(jwt, alg);
form.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
form.add("client_assertion", jwt.serialize());
} else {
//Alternatively use form based auth
form.add("client_id", clientConfig.getClientId());
form.add("client_secret", clientConfig.getClientSecret());
}
}
logger.debug("tokenEndpointURI = " + serverConfig.getTokenEndpointUri());
@ -678,4 +744,41 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi
this.authOptions = authOptions;
}
/**
* Create a symmetric signing and validation service for the given client
*
* @param client
* @return
*/
private JwtSigningAndValidationService getSymmetricValidtor(ClientDetailsEntity client) {
// TODO: cache
if (client == null) {
logger.error("Couldn't create symmetric validator for null client");
return null;
}
if (Strings.isNullOrEmpty(client.getClientSecret())) {
logger.error("Couldn't create symmetric validator for client " + client.getClientId() + " without a client secret");
return null;
}
try {
JWK jwk = new OctetSequenceKey(Base64URL.encode(client.getClientSecret()), Use.SIGNATURE, null, client.getClientId(), null, null, null);
Map<String, JWK> keys = ImmutableMap.of(client.getClientId(), jwk);
JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(keys);
return service;
} catch (NoSuchAlgorithmException e) {
logger.error("Couldn't create symmetric validator for client " + client.getClientId(), e);
} catch (InvalidKeySpecException e) {
logger.error("Couldn't create symmetric validator for client " + client.getClientId(), e);
}
return null;
}
}