diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerAssertionAuthenticationToken.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerAssertionAuthenticationToken.java new file mode 100644 index 000000000..5813124c2 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerAssertionAuthenticationToken.java @@ -0,0 +1,90 @@ +/** + * + */ +package org.mitre.openid.connect.assertion; + +import java.util.Collection; + +import org.mitre.jwt.model.Jwt; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +/** + * @author jricher + * + */ +public class JwtBearerAssertionAuthenticationToken extends AbstractAuthenticationToken { + + private String clientId; + private Jwt jwt; + + public JwtBearerAssertionAuthenticationToken(String clientId, Jwt jwt) { + super(null); + this.clientId = clientId; + this.jwt = jwt; + setAuthenticated(false); + } + + public JwtBearerAssertionAuthenticationToken(String clientId, Jwt jwt, Collection authorities) { + super(authorities); + this.clientId = clientId; + this.jwt = jwt; + setAuthenticated(true); + } + + /* (non-Javadoc) + * @see org.springframework.security.core.Authentication#getCredentials() + */ + @Override + public Object getCredentials() { + return jwt; + } + + /* (non-Javadoc) + * @see org.springframework.security.core.Authentication#getPrincipal() + */ + @Override + public Object getPrincipal() { + return clientId; + } + + /** + * @return the clientId + */ + public String getClientId() { + return clientId; + } + + /** + * @param clientId the clientId to set + */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * @return the jwt + */ + public Jwt getJwt() { + return jwt; + } + + /** + * @param jwt the jwt to set + */ + public void setJwt(Jwt jwt) { + this.jwt = jwt; + } + + /** + * Clear out the JWT that this token holds. + */ + @Override + public void eraseCredentials() { + super.eraseCredentials(); + setJwt(null); + } + + + +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerAuthenticationProvider.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerAuthenticationProvider.java new file mode 100644 index 000000000..37e535637 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerAuthenticationProvider.java @@ -0,0 +1,123 @@ +/** + * + */ +package org.mitre.openid.connect.assertion; + +import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.HashMap; +import java.util.Map; + +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.oauth2.exception.ClientNotFoundException; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.service.ClientDetailsEntityService; +import org.mitre.openid.connect.config.OIDCServerConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * @author jricher + * + */ +public class JwtBearerAuthenticationProvider implements AuthenticationProvider { + + private static final Logger logger = LoggerFactory.getLogger(JwtBearerAuthenticationProvider.class); + + private Map validators = new HashMap(); + + @Autowired + private ClientDetailsEntityService clientService; + + /** + * Try to validate the client credentials by parsing and validating the JWT. + */ + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + + JwtBearerAssertionAuthenticationToken jwtAuth = (JwtBearerAssertionAuthenticationToken)authentication; + + + try { + ClientDetailsEntity client = clientService.loadClientByClientId(jwtAuth.getClientId()); + + JwtSigningAndValidationService validator = getValidatorForClient(client); + + + } catch (ClientNotFoundException e) { + throw new UsernameNotFoundException("Could not find client: " + jwtAuth.getClientId()); + } + + + } + + /** + * We support {@link JwtBearerAssertionAuthenticationToken}s only. + */ + @Override + public boolean supports(Class authentication) { + return (JwtBearerAssertionAuthenticationToken.class.isAssignableFrom(authentication)); + } + + protected JwtSigningAndValidationService getValidatorForClient(ClientDetailsEntity client) { + + if(validators.containsKey(client)){ + return validators.get(client); + } else { + + KeyFetcher keyFetch = new KeyFetcher(); + PublicKey signingKey = null; + + if (client.getJwkUrl() != null) { + // prefer the JWK + signingKey = keyFetch.retrieveJwkKey(client); + } else if (client.getX509Url() != null) { + // use the x509 only if JWK isn't configured + signingKey = keyFetch.retrieveX509Key(client); + } else { + // no keys configured + logger.warn("No server key URLs configured for " + client.getIssuer()); + } + + if (signingKey != null) { + Map signers = new HashMap(); + + 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(client.getIssuer() + JwsAlgorithm.RS256.getJwaName(), signer256); + signers.put(client.getIssuer() + JwsAlgorithm.RS384.getJwaName(), signer384); + signers.put(client.getIssuer() + JwsAlgorithm.RS512.getJwaName(), signer512); + } + + JwtSigningAndValidationService signingAndValidationService = new DefaultJwtSigningAndValidationService(signers); + + validationServices.put(client, signingAndValidationService); + + return signingAndValidationService; + + } else { + // there were either no keys returned or no URLs configured to fetch them, assume no checking on key signatures + return null; + } + } +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerClientAssertionTokenEndpointFilter.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerClientAssertionTokenEndpointFilter.java new file mode 100644 index 000000000..5f85df91f --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerClientAssertionTokenEndpointFilter.java @@ -0,0 +1,86 @@ +/** + * + */ +package org.mitre.openid.connect.assertion; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.mitre.jwt.model.Jwt; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; + +import com.google.common.base.Strings; + +/** + * Filter to check client authentication via JWT Bearer assertions. + * + * @author jricher + * + */ +public class JwtBearerClientAssertionTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter { + + public JwtBearerClientAssertionTokenEndpointFilter() { + super(); + // TODO Auto-generated constructor stub + } + + public JwtBearerClientAssertionTokenEndpointFilter(String path) { + super(path); + // TODO Auto-generated constructor stub + } + + /** + * Pull the assertion out of the request and send it up to the auth manager for processing. + */ + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { + + // check for appropriate parameters + String assertionType = request.getParameter("client_assertion_type"); + String assertion = request.getParameter("client_assertion"); + + try { + Jwt jwt = Jwt.parse(assertion); + + String clientId = jwt.getClaims().getPrincipal(); + + Authentication authRequest = new JwtBearerAssertionAuthenticationToken(clientId, jwt); + + return this.getAuthenticationManager().authenticate(authRequest); + } catch (IllegalArgumentException e) { + throw new BadCredentialsException("Invalid JWT credential: " + assertion); + } + } + + /** + * Check to see if the "client_assertion_type" and "client_assertion" parameters are present and contain the right values. + */ + @Override + protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) { + // check for appropriate parameters + String assertionType = request.getParameter("client_assertion_type"); + String assertion = request.getParameter("client_assertion"); + + if (Strings.isNullOrEmpty(assertionType) || Strings.isNullOrEmpty(assertion)) { + return false; + } else if (!assertionType.equals("urn:ietf:params:oauth:client-assertion-type:jwt-bearer")) { + return false; + } + + + + return super.requiresAuthentication(request, response); + } + + + + +}