org.mitre.openid.connect.assertion.JWTBearerAuthenticationProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.mitre.openid.connect.assertion.JWTBearerAuthenticationProvider.java

Source

/*******************************************************************************
 * Copyright 2016 The MITRE Corporation
 *   and the MIT Internet Trust Consortium
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
/**
 * 
 */
package org.mitre.openid.connect.assertion;

import java.text.ParseException;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
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.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

/**
 * @author jricher
 *
 */
public class JWTBearerAuthenticationProvider implements AuthenticationProvider {

    /**
     * Logger for this class
     */
    private static final Logger logger = LoggerFactory.getLogger(JWTBearerAuthenticationProvider.class);

    private static final GrantedAuthority ROLE_CLIENT = new SimpleGrantedAuthority("ROLE_CLIENT");

    // map of verifiers, load keys for clients
    @Autowired
    private ClientKeyCacheService validators;

    // Allow for time sync issues by having a window of X seconds.
    private int timeSkewAllowance = 300;

    // to load clients
    @Autowired
    private ClientDetailsEntityService clientService;

    // to get our server's issuer url
    @Autowired
    private ConfigurationPropertiesBean config;

    /**
     * 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());

            JWT jwt = jwtAuth.getJwt();
            JWTClaimsSet jwtClaims = jwt.getJWTClaimsSet();

            // check the signature with nimbus
            if (jwt instanceof SignedJWT) {
                SignedJWT jws = (SignedJWT) jwt;

                JWSAlgorithm alg = jws.getHeader().getAlgorithm();

                if (client.getTokenEndpointAuthSigningAlg() != null
                        && !client.getTokenEndpointAuthSigningAlg().equals(alg)) {
                    throw new InvalidClientException("Client's registered request object signing algorithm ("
                            + client.getRequestObjectSigningAlg()
                            + ") does not match request object's actual algorithm (" + alg.getName() + ")");
                }

                if (client.getTokenEndpointAuthMethod() == null
                        || client.getTokenEndpointAuthMethod().equals(AuthMethod.NONE)
                        || client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_BASIC)
                        || client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_POST)) {

                    // this client doesn't support this type of authentication
                    throw new AuthenticationServiceException("Client does not support this authentication method.");

                } else if ((client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY)
                        && (alg.equals(JWSAlgorithm.RS256) || alg.equals(JWSAlgorithm.RS384)
                                || alg.equals(JWSAlgorithm.RS512) || alg.equals(JWSAlgorithm.ES256)
                                || alg.equals(JWSAlgorithm.ES384) || alg.equals(JWSAlgorithm.ES512)
                                || alg.equals(JWSAlgorithm.PS256) || alg.equals(JWSAlgorithm.PS384)
                                || alg.equals(JWSAlgorithm.PS512)))
                        || (client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT)
                                && (alg.equals(JWSAlgorithm.HS256) || alg.equals(JWSAlgorithm.HS384)
                                        || alg.equals(JWSAlgorithm.HS512)))) {

                    // double-check the method is asymmetrical if we're in HEART mode
                    if (config.isHeartMode()
                            && !client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY)) {
                        throw new AuthenticationServiceException("[HEART mode] Invalid authentication method");
                    }

                    JWTSigningAndValidationService validator = validators.getValidator(client, alg);

                    if (validator == null) {
                        throw new AuthenticationServiceException("Unable to create signature validator for client "
                                + client + " and algorithm " + alg);
                    }

                    if (!validator.validateSignature(jws)) {
                        throw new AuthenticationServiceException(
                                "Signature did not validate for presented JWT authentication.");
                    }
                } else {
                    throw new AuthenticationServiceException("Unable to create signature validator for method "
                            + client.getTokenEndpointAuthMethod() + " and algorithm " + alg);
                }
            }

            // check the issuer
            if (jwtClaims.getIssuer() == null) {
                throw new AuthenticationServiceException("Assertion Token Issuer is null");
            } else if (!jwtClaims.getIssuer().equals(client.getClientId())) {
                throw new AuthenticationServiceException(
                        "Issuers do not match, expected " + client.getClientId() + " got " + jwtClaims.getIssuer());
            }

            // check expiration
            if (jwtClaims.getExpirationTime() == null) {
                throw new AuthenticationServiceException("Assertion 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(jwtClaims.getExpirationTime())) {
                    throw new AuthenticationServiceException(
                            "Assertion Token is expired: " + jwtClaims.getExpirationTime());
                }
            }

            // check not before
            if (jwtClaims.getNotBeforeTime() != null) {
                Date now = new Date(System.currentTimeMillis() + (timeSkewAllowance * 1000));
                if (now.before(jwtClaims.getNotBeforeTime())) {
                    throw new AuthenticationServiceException(
                            "Assertion Token not valid untill: " + jwtClaims.getNotBeforeTime());
                }
            }

            // check issued at
            if (jwtClaims.getIssueTime() != null) {
                // since it's not null, see if it was issued in the future
                Date now = new Date(System.currentTimeMillis() + (timeSkewAllowance * 1000));
                if (now.before(jwtClaims.getIssueTime())) {
                    throw new AuthenticationServiceException(
                            "Assertion Token was issued in the future: " + jwtClaims.getIssueTime());
                }
            }

            // check audience
            if (jwtClaims.getAudience() == null) {
                throw new AuthenticationServiceException("Assertion token audience is null");
            } else if (!(jwtClaims.getAudience().contains(config.getIssuer())
                    || jwtClaims.getAudience().contains(config.getIssuer() + "token"))) {
                throw new AuthenticationServiceException("Audience does not match, expected " + config.getIssuer()
                        + " or " + (config.getIssuer() + "token") + " got " + jwtClaims.getAudience());
            }

            // IFF we managed to get all the way down here, the token is valid

            // add in the ROLE_CLIENT authority
            Set<GrantedAuthority> authorities = new HashSet<>(client.getAuthorities());
            authorities.add(ROLE_CLIENT);

            return new JWTBearerAssertionAuthenticationToken(client.getClientId(), jwt, authorities);

        } catch (InvalidClientException e) {
            throw new UsernameNotFoundException("Could not find client: " + jwtAuth.getClientId());
        } catch (ParseException e) {

            logger.error("Failure during authentication, error was: ", e);

            throw new AuthenticationServiceException("Invalid JWT format");
        }
    }

    /**
     * We support {@link JWTBearerAssertionAuthenticationToken}s only.
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return (JWTBearerAssertionAuthenticationToken.class.isAssignableFrom(authentication));
    }

}