org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices.java

Source

/*******************************************************************************
 *     Cloud Foundry 
 *     Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved.
 *
 *     This product is licensed to you under the Apache License, Version 2.0 (the "License").
 *     You may not use this product except in compliance with the License.
 *
 *     This product includes a number of subcomponents with
 *     separate copyright notices and license terms. Your use of these
 *     subcomponents is subject to the terms and conditions of the
 *     subcomponent's license, as noted in the LICENSE file.
 *******************************************************************************/
package org.cloudfoundry.identity.uaa.oauth.token;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.audit.event.TokenIssuedEvent;
import org.cloudfoundry.identity.uaa.authentication.Origin;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.oauth.approval.Approval;
import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus;
import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import static org.cloudfoundry.identity.uaa.oauth.Claims.ADDITIONAL_AZ_ATTR;
import static org.cloudfoundry.identity.uaa.oauth.Claims.AUD;
import static org.cloudfoundry.identity.uaa.oauth.Claims.AZP;
import static org.cloudfoundry.identity.uaa.oauth.Claims.AUTHORITIES;
import static org.cloudfoundry.identity.uaa.oauth.Claims.CID;
import static org.cloudfoundry.identity.uaa.oauth.Claims.CLIENT_ID;
import static org.cloudfoundry.identity.uaa.oauth.Claims.EMAIL;
import static org.cloudfoundry.identity.uaa.oauth.Claims.EXP;
import static org.cloudfoundry.identity.uaa.oauth.Claims.GRANT_TYPE;
import static org.cloudfoundry.identity.uaa.oauth.Claims.IAT;
import static org.cloudfoundry.identity.uaa.oauth.Claims.ISS;
import static org.cloudfoundry.identity.uaa.oauth.Claims.JTI;
import static org.cloudfoundry.identity.uaa.oauth.Claims.SCOPE;
import static org.cloudfoundry.identity.uaa.oauth.Claims.SUB;
import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_ID;
import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_NAME;

/**
 * This class provides token services for the UAA. It handles the production and
 * consumption of UAA tokens.
 * 
 */
public class UaaTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
        InitializingBean, ApplicationEventPublisherAware {

    private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30
                                                                 // days.

    private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.

    private final Log logger = LogFactory.getLog(getClass());

    private UaaUserDatabase userDatabase = null;

    private ObjectMapper mapper = new ObjectMapper();

    private ClientDetailsService clientDetailsService = null;

    private SignerProvider signerProvider = new SignerProvider();

    private String issuer = null;

    private String tokenEndpoint = null;

    private Set<String> defaultUserAuthorities = new HashSet<String>();

    private ApprovalStore approvalStore = null;

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    @Override
    public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest request)
            throws AuthenticationException {
        if (null == refreshTokenValue) {
            throw new InvalidTokenException("Invalid refresh token (empty token)");
        }

        if (!"refresh_token".equals(request.getRequestParameters().get("grant_type"))) {
            throw new InvalidGrantException(
                    "Invalid grant type: " + request.getRequestParameters().get("grant_type"));
        }

        Map<String, Object> claims = getClaimsForToken(refreshTokenValue);

        // TODO: Should reuse the access token you get after the first
        // successful authentication.
        // You will get an invalid_grant error if your previous token has not
        // expired yet.
        // OAuth2RefreshToken refreshToken =
        // tokenStore.readRefreshToken(refreshTokenValue);
        // if (refreshToken == null) {
        // throw new InvalidGrantException("Invalid refresh token: " +
        // refreshTokenValue);
        // }

        String clientId = (String) claims.get(CID);
        if (clientId == null || !clientId.equals(request.getClientId())) {
            throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);
        }

        String userid = (String) claims.get(USER_ID);

        // TODO: Need to add a lookup by id so that the refresh token does not
        // need to contain a name
        UaaUser user = userDatabase.retrieveUserById(userid);

        Integer refreshTokenIssuedAt = (Integer) claims.get(IAT);
        long refreshTokenIssueDate = refreshTokenIssuedAt.longValue() * 1000l;

        // If the user changed their password, expire the refresh token
        if (user.getModified().after(new Date(refreshTokenIssueDate))) {
            logger.debug("User was last modified at " + user.getModified() + " refresh token was issued at "
                    + new Date(refreshTokenIssueDate));
            throw new InvalidTokenException("Invalid refresh token (password changed): " + refreshTokenValue);
        }

        Integer refreshTokenExpiry = (Integer) claims.get(EXP);
        long refreshTokenExpireDate = refreshTokenExpiry.longValue() * 1000l;

        if (new Date(refreshTokenExpireDate).before(new Date())) {
            throw new InvalidTokenException("Invalid refresh token (expired): " + refreshTokenValue + " expired at "
                    + new Date(refreshTokenExpireDate));
        }

        @SuppressWarnings("unchecked")
        ArrayList<String> tokenScopes = (ArrayList<String>) claims.get(SCOPE);

        // default request scopes to what is in the refresh token
        Set<String> requestedScopes = request.getScope();
        if (requestedScopes.isEmpty()) {
            requestedScopes = new HashSet<String>(tokenScopes);
        }

        // The user may not request scopes that were not part of the refresh
        // token
        if (tokenScopes.isEmpty() || !tokenScopes.containsAll(requestedScopes)) {
            throw new InvalidScopeException(
                    "Unable to narrow the scope of the client authentication to " + requestedScopes + ".",
                    new HashSet<String>(tokenScopes));
        }

        // from this point on, we only care about the scopes requested, not what
        // is in the refresh token
        // ensure all requested scopes are approved: either automatically or
        // explicitly by the user
        ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
        String grantType = claims.get(GRANT_TYPE).toString();
        checkForApproval(userid, clientId, requestedScopes, getAutoApprovedScopes(grantType, tokenScopes, client),
                new Date(refreshTokenIssueDate));

        // if we have reached so far, issue an access token
        Integer validity = client.getAccessTokenValiditySeconds();

        @SuppressWarnings("unchecked")
        Map<String, String> additionalAuthorizationInfo = (Map<String, String>) claims.get(ADDITIONAL_AZ_ATTR);

        Set<String> audience = new HashSet<>((ArrayList<String>) claims.get(AUD));

        OAuth2AccessToken accessToken = createAccessToken(user.getId(), user.getUsername(), user.getEmail(),
                validity != null ? validity.intValue() : accessTokenValiditySeconds, null, requestedScopes,
                clientId, audience /*request.createOAuth2Request(client).getResourceIds()*/, grantType,
                refreshTokenValue, additionalAuthorizationInfo, new HashSet<String>()); //TODO populate response types

        return accessToken;
    }

    private void checkForApproval(String userid, String clientId, Collection<String> requestedScopes,
            Collection<String> autoApprovedScopes, Date updateCutOff) {
        Set<String> approvedScopes = new HashSet<String>();
        approvedScopes.addAll(autoApprovedScopes);

        // Search through the users approvals for scopes that are requested, not
        // auto approved, not expired,
        // not DENIED and not approved more recently than when this access token
        // was issued.
        List<Approval> approvals = approvalStore.getApprovals(userid, clientId);
        for (Approval approval : approvals) {
            if (requestedScopes.contains(approval.getScope()) && approval.getStatus() == ApprovalStatus.APPROVED) {
                if (!approval.isCurrentlyActive()) {
                    logger.debug("Approval " + approval + " has expired. Need to re-approve.");
                    throw new InvalidTokenException("Invalid token (approvals expired)");
                }
                if (updateCutOff.before(approval.getLastUpdatedAt())) {
                    logger.debug("At least one approval " + approval + " was updated more recently at "
                            + approval.getLastUpdatedAt() + " access token was issued at " + updateCutOff);
                    throw new InvalidTokenException(
                            "Invalid token (approvals updated): " + approval.getLastUpdatedAt());
                }
                approvedScopes.add(approval.getScope());
            }
        }

        // Only issue the token if all the requested scopes have unexpired
        // approvals made before the refresh token was
        // issued OR if those scopes are auto approved
        if (!approvedScopes.containsAll(requestedScopes)) {
            logger.debug("All requested scopes " + requestedScopes + " were not approved " + approvedScopes);
            Set<String> unapprovedScopes = new HashSet<String>(requestedScopes);
            unapprovedScopes.removeAll(approvedScopes);
            throw new InvalidTokenException(
                    "Invalid token (some requested scopes are not approved): " + unapprovedScopes);
        }
    }

    private OAuth2AccessToken createAccessToken(String userId, String username, String userEmail,
            int validitySeconds, Collection<GrantedAuthority> clientScopes, Set<String> requestedScopes,
            String clientId, Set<String> resourceIds, String grantType, String refreshToken,
            Map<String, String> additionalAuthorizationAttributes, Set<String> responseTypes)
            throws AuthenticationException {
        String tokenId = UUID.randomUUID().toString();
        OpenIdToken accessToken = new OpenIdToken(tokenId);
        if (validitySeconds > 0) {
            accessToken.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
        }
        accessToken.setRefreshToken(refreshToken == null ? null : new DefaultOAuth2RefreshToken(refreshToken));

        if (null == requestedScopes || requestedScopes.size() == 0) {
            logger.debug("No scopes were granted");
            throw new InvalidTokenException("No scopes were granted");
        }

        accessToken.setScope(requestedScopes);

        Map<String, Object> info = new HashMap<String, Object>();
        info.put(JTI, accessToken.getValue());
        if (null != additionalAuthorizationAttributes) {
            info.put(ADDITIONAL_AZ_ATTR, additionalAuthorizationAttributes);
        }
        accessToken.setAdditionalInformation(info);

        String content;
        try {
            content = mapper.writeValueAsString(createJWTAccessToken(accessToken, userId, username, userEmail,
                    clientScopes, requestedScopes, clientId, resourceIds, grantType, refreshToken));
        } catch (Exception e) {
            throw new IllegalStateException("Cannot convert access token to JSON", e);
        }
        String token = JwtHelper.encode(content, signerProvider.getSigner()).getEncoded();

        // This setter copies the value and returns. Don't change.
        accessToken.setValue(token);
        populateIdToken(accessToken, requestedScopes, responseTypes);
        publish(new TokenIssuedEvent(accessToken, SecurityContextHolder.getContext().getAuthentication()));

        return accessToken;
    }

    private void populateIdToken(OpenIdToken token, Set<String> scopes, Set<String> responseTypes) {
        if (scopes.contains("openid") && responseTypes.contains(OpenIdToken.ID_TOKEN)) {
            token.setIdTokenValue(token.getValue());
        }
    }

    private Map<String, ?> createJWTAccessToken(OAuth2AccessToken token, String userId, String username,
            String userEmail, Collection<GrantedAuthority> clientScopes, Set<String> requestedScopes,
            String clientId, Set<String> resourceIds, String grantType, String refreshToken) {

        Map<String, Object> response = new LinkedHashMap<String, Object>();

        response.put(JTI, token.getAdditionalInformation().get(JTI));
        response.putAll(token.getAdditionalInformation());

        response.put(SUB, userId);
        if (null != clientScopes) {
            response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(clientScopes));
        }

        response.put(OAuth2AccessToken.SCOPE, requestedScopes);
        response.put(CLIENT_ID, clientId);
        response.put(CID, clientId);
        response.put(AZP, clientId); //openId Connect

        if (null != grantType) {
            response.put(GRANT_TYPE, grantType);
        }
        if (!"client_credentials".equals(grantType)) {
            response.put(USER_ID, userId);
            response.put(USER_NAME, username == null ? userId : username);
            if (null != userEmail) {
                response.put(EMAIL, userEmail);
            }
        }

        response.put(IAT, System.currentTimeMillis() / 1000);
        if (token.getExpiration() != null) {
            response.put(EXP, token.getExpiration().getTime() / 1000);
        }

        if (tokenEndpoint != null) {
            response.put(ISS, tokenEndpoint);
        }

        // TODO: different values for audience in the AT and RT. Need to sync
        // them up
        response.put(AUD, resourceIds);

        return response;
    }

    @Override
    public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

        OAuth2RefreshToken refreshToken = createRefreshToken(authentication);

        String userId = null;
        String username = null;
        String userEmail = null;

        Collection<GrantedAuthority> clientScopes = null;
        // Clients should really by different kinds of users
        if (authentication.isClientOnly()) {
            ClientDetails client = clientDetailsService.loadClientByClientId(authentication.getName());
            userId = client.getClientId();
            clientScopes = client.getAuthorities();
        } else {
            userId = getUserId(authentication);
            UaaUser user = userDatabase.retrieveUserById(userId);
            username = user.getUsername();
            userEmail = user.getEmail();
        }

        String clientId = authentication.getOAuth2Request().getClientId();
        Set<String> userScopes = authentication.getOAuth2Request().getScope();
        String grantType = authentication.getOAuth2Request().getRequestParameters().get("grant_type");

        Set<String> modifiableUserScopes = new LinkedHashSet<String>();
        modifiableUserScopes.addAll(userScopes);
        String externalScopes = authentication.getOAuth2Request().getRequestParameters().get("external_scopes");
        if (null != externalScopes && StringUtils.hasLength(externalScopes)) {
            modifiableUserScopes.addAll(OAuth2Utils.parseParameterList(externalScopes));
        }

        Map<String, String> additionalAuthorizationAttributes = getAdditionalAuthorizationAttributes(
                authentication.getOAuth2Request().getRequestParameters().get("authorities"));

        ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
        Integer validity = client.getAccessTokenValiditySeconds();
        Set<String> responseTypes = extractResponseTypes(authentication);
        OAuth2AccessToken accessToken = createAccessToken(userId, username, userEmail,
                validity != null ? validity.intValue() : accessTokenValiditySeconds, clientScopes,
                modifiableUserScopes, clientId, authentication.getOAuth2Request().getResourceIds(), grantType,
                refreshToken != null ? refreshToken.getValue() : null, additionalAuthorizationAttributes,
                responseTypes);

        return accessToken;
    }

    /**
     * If an only if the stored request has response_type=code AND
     * the request parameters override it using another response_type parameter
     * this method will return the requested response_type rather than the stored
     * @param authentication
     * @return
     */
    protected Set<String> extractResponseTypes(OAuth2Authentication authentication) {
        Set<String> responseTypes = authentication.getOAuth2Request().getResponseTypes();
        if (responseTypes != null && responseTypes.size() == 1) {
            String storedResponseType = responseTypes.iterator().next();
            String requesedResponseType = authentication.getOAuth2Request().getRequestParameters()
                    .get(OAuth2Utils.RESPONSE_TYPE);
            if ("code".equals(storedResponseType) && requesedResponseType != null) {
                responseTypes = OAuth2Utils.parseParameterList(requesedResponseType);
            }
        }
        return responseTypes;
    }

    /**
     * This method searches the authorities in the request for
     * additionalAuthorizationAttributes
     * and returns a map of these attributes that will later be added to the
     * token
     * 
     * @param authoritiesJson
     * @return
     */
    private Map<String, String> getAdditionalAuthorizationAttributes(String authoritiesJson) {
        if (StringUtils.hasLength(authoritiesJson)) {
            try {
                @SuppressWarnings("unchecked")
                Map<String, Object> authorities = mapper.readValue(authoritiesJson.getBytes(), Map.class);
                @SuppressWarnings("unchecked")
                Map<String, String> additionalAuthorizationAttributes = (Map<String, String>) authorities
                        .get("az_attr");

                return additionalAuthorizationAttributes;
            } catch (Throwable t) {
                logger.error("Unable to read additionalAuthorizationAttributes", t);
            }
        }

        return null;
    }

    private ExpiringOAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {

        String grantType = authentication.getOAuth2Request().getRequestParameters().get("grant_type");
        if (!isRefreshTokenSupported(grantType)) {
            return null;
        }

        Map<String, String> additionalAuthorizationAttributes = getAdditionalAuthorizationAttributes(
                authentication.getOAuth2Request().getRequestParameters().get("authorities"));

        int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
        ExpiringOAuth2RefreshToken token = new DefaultExpiringOAuth2RefreshToken(UUID.randomUUID().toString(),
                new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));

        String userId = getUserId(authentication);
        UaaUser user = userDatabase.retrieveUserById(userId);

        String content;
        try {
            content = mapper.writeValueAsString(
                    createJWTRefreshToken(token, user, authentication.getOAuth2Request().getScope(),
                            authentication.getOAuth2Request().getClientId(), grantType,
                            additionalAuthorizationAttributes, authentication.getOAuth2Request().getResourceIds()));
        } catch (Exception e) {
            throw new IllegalStateException("Cannot convert access token to JSON", e);
        }
        String jwtToken = JwtHelper.encode(content, signerProvider.getSigner()).getEncoded();

        ExpiringOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken(jwtToken,
                token.getExpiration());

        return refreshToken;
    }

    protected String getUserId(OAuth2Authentication authentication) {
        return Origin.getUserId(authentication.getUserAuthentication());
    }

    private Map<String, ?> createJWTRefreshToken(OAuth2RefreshToken token, UaaUser user, Set<String> scopes,
            String clientId, String grantType, Map<String, String> additionalAuthorizationAttributes,
            Set<String> resourceIds) {

        Map<String, Object> response = new LinkedHashMap<String, Object>();

        response.put(JTI, UUID.randomUUID().toString());
        response.put(SUB, user.getId());
        response.put(SCOPE, scopes);
        if (null != additionalAuthorizationAttributes) {
            response.put(ADDITIONAL_AZ_ATTR, additionalAuthorizationAttributes);
        }

        response.put(IAT, System.currentTimeMillis() / 1000);
        if (((ExpiringOAuth2RefreshToken) token).getExpiration() != null) {
            response.put(EXP, ((ExpiringOAuth2RefreshToken) token).getExpiration().getTime() / 1000);
        }

        response.put(CID, clientId);
        if (tokenEndpoint != null) {
            response.put(ISS, tokenEndpoint);
        }

        if (null != grantType) {
            response.put(GRANT_TYPE, grantType);
        }
        if (!"client_credentials".equals(grantType)) {
            response.put(USER_NAME, user.getUsername());
            response.put(USER_ID, user.getId());
        }

        response.put(AUD, resourceIds);

        return response;
    }

    /**
     * Check the current authorization request to indicate whether a refresh
     * token should be issued or not.
     * 
     * @param grantType the current grant type
     * @return boolean to indicate if refresh token is supported
     */
    protected boolean isRefreshTokenSupported(String grantType) {
        return "authorization_code".equals(grantType) || "password".equals(grantType)
                || "refresh_token".equals(grantType);
    }

    /**
     * The refresh token validity period in seconds
     * 
     * @param authorizationRequest the current authorization request
     * @return the refresh token validity period in seconds
     */
    protected int getRefreshTokenValiditySeconds(OAuth2Request authorizationRequest) {
        ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
        Integer validity = client.getRefreshTokenValiditySeconds();
        if (validity != null) {
            return validity;
        }
        return refreshTokenValiditySeconds;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(clientDetailsService, "clientDetailsService must be set");
        Assert.notNull(issuer, "issuer must be set");
        Assert.notNull(approvalStore, "approvalStore must be set");
    }

    public void setUserDatabase(UaaUserDatabase userDatabase) {
        this.userDatabase = userDatabase;
    }

    private void validateClient(String clientId) throws AuthenticationException {
        if (clientId != null) {
            try {
                clientDetailsService.loadClientByClientId(clientId);
            } catch (NoSuchClientException x) {
                throw new OAuth2AccessDeniedException("Invalid client:" + clientId);
            } catch (ClientRegistrationException x) {
                throw new OAuth2AccessDeniedException("Invalid client:" + clientId);
            } catch (InvalidClientException x) {
                throw new OAuth2AccessDeniedException("Invalid client:" + clientId);
            }
        }
    }

    @Override
    public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException {
        Map<String, Object> claims = getClaimsForToken(accessToken);

        // Check token expiry
        Integer expiration = (Integer) claims.get(EXP);
        if (expiration != null && new Date(expiration * 1000l).before(new Date())) {
            throw new InvalidTokenException("Invalid access token (expired): " + accessToken + " expired at "
                    + new Date(expiration * 1000l));
        }

        // Check client ID is valid
        validateClient((String) claims.get(CLIENT_ID));
        validateClient((String) claims.get(CID));

        @SuppressWarnings("unchecked")
        ArrayList<String> scopes = (ArrayList<String>) claims.get(SCOPE);

        AuthorizationRequest authorizationRequest = new AuthorizationRequest((String) claims.get(CLIENT_ID),
                scopes);

        ArrayList<String> rids = (ArrayList<String>) claims.get(AUD);
        //TODO - Fix null resource IDs for a client_credentials request to /oauth/token
        Set<String> resourceIds = Collections
                .unmodifiableSet(rids == null ? new HashSet<String>() : new HashSet<>(rids));
        authorizationRequest.setResourceIds(resourceIds);

        authorizationRequest.setApproved(true);

        Collection<? extends GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(
                StringUtils.collectionToCommaDelimitedString(defaultUserAuthorities));
        if (claims.containsKey("authorities")) {
            Object authoritiesFromClaims = claims.get("authorities");
            if (authoritiesFromClaims instanceof String) {
                authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) authoritiesFromClaims);
            }
            if (authoritiesFromClaims instanceof Collection) {
                authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(
                        StringUtils.collectionToCommaDelimitedString((Collection<?>) authoritiesFromClaims));
            }
        }

        Authentication userAuthentication = null;
        // Is this a user token?
        if (claims.containsKey(EMAIL)) {
            UaaUser user = new UaaUser((String) claims.get(USER_ID), (String) claims.get(USER_NAME), null,
                    (String) claims.get(EMAIL), UaaAuthority.USER_AUTHORITIES, null, null, null, null, null, null,
                    false);

            UaaPrincipal principal = new UaaPrincipal(user);
            userAuthentication = new UaaAuthentication(principal, UaaAuthority.USER_AUTHORITIES, null);
        } else {
            authorizationRequest.setAuthorities(authorities);
        }

        OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(),
                userAuthentication);
        authentication.setAuthenticated(true);
        return authentication;
    }

    /**
     * This method is implemented to support older API calls that assume the
     * presence of a token store
     */
    @Override
    public OAuth2AccessToken readAccessToken(String accessToken) {
        Map<String, Object> claims = getClaimsForToken(accessToken);

        // Expiry is verified by check_token
        OpenIdToken token = new OpenIdToken(accessToken);
        token.setTokenType(OAuth2AccessToken.BEARER_TYPE);
        Integer exp = (Integer) claims.get(EXP);
        if (null != exp) {
            token.setExpiration(new Date(exp.longValue() * 1000l));
        }

        @SuppressWarnings("unchecked")
        ArrayList<String> scopes = (ArrayList<String>) claims.get(SCOPE);
        if (null != scopes && scopes.size() > 0) {
            token.setScope(new HashSet<String>(scopes));
        }

        String email = (String) claims.get(EMAIL);

        // Only check user access tokens
        if (null != email) {
            String userId = (String) claims.get(USER_ID);

            UaaUser user = userDatabase.retrieveUserById(userId);

            Integer accessTokenIssuedAt = (Integer) claims.get(IAT);
            long accessTokenIssueDate = accessTokenIssuedAt.longValue() * 1000l;

            // If the user changed their password, expire the access token
            if (user.getModified().after(new Date(accessTokenIssueDate))) {
                logger.debug("User was last modified at " + user.getModified() + " access token was issued at "
                        + new Date(accessTokenIssueDate));
                throw new InvalidTokenException("Invalid access token (password changed): " + accessToken);
            }

            // Check approvals to make sure they're all valid, approved and not
            // more recent
            // than the token itself
            String clientId = (String) claims.get(CLIENT_ID);
            ClientDetails client = clientDetailsService.loadClientByClientId(clientId);

            @SuppressWarnings("unchecked")
            ArrayList<String> tokenScopes = (ArrayList<String>) claims.get(SCOPE);
            Set<String> autoApprovedScopes = getAutoApprovedScopes(claims.get(GRANT_TYPE), tokenScopes, client);
            if (autoApprovedScopes.containsAll(tokenScopes)) {
                return token;
            }
            checkForApproval(userId, clientId, tokenScopes, autoApprovedScopes, new Date(accessTokenIssueDate));
        }

        return token;
    }

    private Set<String> getAutoApprovedScopes(Object grantType, Collection<String> tokenScopes,
            ClientDetails client) {
        // ALL requested scopes are considered auto-approved for password grant
        if (grantType != null && "password".equals(grantType.toString())) {
            return new HashSet<String>(tokenScopes);
        }

        // start with scopes listed as autoapprove in client config
        Object autoApproved = client.getAdditionalInformation().get("autoapprove");
        Set<String> autoApprovedScopes = new HashSet<String>();
        if (autoApproved instanceof Collection<?>) {
            @SuppressWarnings("unchecked")
            Collection<? extends String> approvedScopes = (Collection<? extends String>) autoApproved;
            autoApprovedScopes.addAll(approvedScopes);
        } else if (autoApproved instanceof Boolean && (Boolean) autoApproved || "true".equals(autoApproved)) {
            autoApprovedScopes.addAll(client.getScope());
        }

        // retain only the requested scopes
        autoApprovedScopes.retainAll(tokenScopes);
        return autoApprovedScopes;
    }

    private Map<String, Object> getClaimsForToken(String token) {
        Jwt tokenJwt = null;
        try {
            tokenJwt = JwtHelper.decodeAndVerify(token, signerProvider.getVerifier());
        } catch (Throwable t) {
            logger.debug("Invalid token (could not decode)");
            throw new InvalidTokenException("Invalid token (could not decode): " + token);
        }

        Map<String, Object> claims = null;
        try {
            claims = mapper.readValue(tokenJwt.getClaims(), new TypeReference<Map<String, Object>>() {
            });
        } catch (Exception e) {
            throw new IllegalStateException("Cannot read token claims", e);
        }

        if (tokenEndpoint != null && !tokenEndpoint.equals(claims.get(ISS))) {
            throw new InvalidTokenException("Invalid issuer for token:" + claims.get(ISS));
        }

        return claims;
    }

    /**
     * This method is implemented only to support older API calls that assume
     * the presence of a token store
     */
    @Override
    public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
        return null;
    }

    public void setIssuer(String issuer) {
        this.issuer = issuer;
        if (issuer == null) {
            tokenEndpoint = null;
        } else {
            tokenEndpoint = issuer + "/oauth/token";
        }
    }

    public String getTokenEndpoint() {
        return tokenEndpoint;
    }

    public void setClientDetailsService(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
    }

    public void setSignerProvider(SignerProvider signerProvider) {
        this.signerProvider = signerProvider;
    }

    public void setDefaultUserAuthorities(Set<String> defaultUserAuthorities) {
        this.defaultUserAuthorities = defaultUserAuthorities;
    }

    public void setApprovalStore(ApprovalStore approvalStore) {
        this.approvalStore = approvalStore;
    }

    private void publish(TokenIssuedEvent event) {
        if (applicationEventPublisher != null) {
            applicationEventPublisher.publishEvent(event);
        }
    }

}