Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.cloudfoundry.identity.uaa.oauth.UaaTokenServices Maven / Gradle / Ivy
/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] 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;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.cloudfoundry.identity.uaa.approval.ApprovalService;
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.jwt.JwtHelper;
import org.cloudfoundry.identity.uaa.oauth.openid.IdTokenCreationException;
import org.cloudfoundry.identity.uaa.oauth.openid.IdTokenCreator;
import org.cloudfoundry.identity.uaa.oauth.openid.IdTokenGranter;
import org.cloudfoundry.identity.uaa.oauth.openid.UserAuthenticationData;
import org.cloudfoundry.identity.uaa.oauth.refresh.CompositeExpiringOAuth2RefreshToken;
import org.cloudfoundry.identity.uaa.oauth.refresh.RefreshTokenCreator;
import org.cloudfoundry.identity.uaa.oauth.refresh.RefreshTokenRequestData;
import org.cloudfoundry.identity.uaa.oauth.token.CompositeToken;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableToken;
import org.cloudfoundry.identity.uaa.oauth.token.RevocableTokenProvisioning;
import org.cloudfoundry.identity.uaa.provider.oauth.XOAuthUserAuthority;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
import org.cloudfoundry.identity.uaa.user.UserInfo;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.TimeService;
import org.cloudfoundry.identity.uaa.util.TokenValidation;
import org.cloudfoundry.identity.uaa.util.UaaTokenUtils;
import org.cloudfoundry.identity.uaa.zone.ClientServicesExtension;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.TokenPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
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.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
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.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
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 java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Optional.ofNullable;
import static org.cloudfoundry.identity.uaa.oauth.client.ClientConstants.REQUIRED_USER_GROUPS;
import static org.cloudfoundry.identity.uaa.oauth.openid.IdToken.ACR_VALUES_KEY;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ACR;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ADDITIONAL_AZ_ATTR;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AMR;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AUD;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AUTHORITIES;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AUTH_TIME;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AZP;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.CID;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.CLIENT_ID;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EMAIL;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EXP;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.GRANTED_SCOPES;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.GRANT_TYPE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.IAT;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ISS;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.JTI;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.NONCE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ORIGIN;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.REVOCABLE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.REVOCATION_SIGNATURE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SCOPE;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SUB;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ID;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_NAME;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ZONE_ID;
import static org.cloudfoundry.identity.uaa.oauth.token.RevocableToken.TokenType.ACCESS_TOKEN;
import static org.cloudfoundry.identity.uaa.oauth.token.RevocableToken.TokenType.REFRESH_TOKEN;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_CLIENT_CREDENTIALS;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_REFRESH_TOKEN;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_USER_TOKEN;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.REQUEST_AUTHORITIES;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.REQUEST_TOKEN_FORMAT;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.TokenFormat.JWT;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.TokenFormat.OPAQUE;
import static org.springframework.security.oauth2.common.util.OAuth2Utils.RESPONSE_TYPE;
import static org.springframework.util.StringUtils.hasText;
/**
* This class provides token services for the UAA. It handles the production and
* consumption of UAA tokens.
*
*/
public class UaaTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, ApplicationEventPublisherAware {
private static final String CODE = "code";
private static final String OPENID = "openid";
private static final List NON_ADDITIONAL_ROOT_CLAIMS = Arrays.asList(
JTI, SUB, AUTHORITIES, OAuth2AccessToken.SCOPE,
CLIENT_ID, CID, AZP, REVOCABLE,
GRANT_TYPE, USER_ID, ORIGIN, USER_NAME,
EMAIL, AUTH_TIME, REVOCATION_SIGNATURE, IAT,
EXP, ISS, ZONE_ID, AUD
);
private final Logger logger = LoggerFactory.getLogger(UaaTokenServices.class);
private UaaUserDatabase userDatabase;
private ClientServicesExtension clientDetailsService;
private ApprovalService approvalService;
private ApplicationEventPublisher applicationEventPublisher;
private TokenPolicy tokenPolicy;
private RevocableTokenProvisioning tokenProvisioning;
private Set excludedClaims;
private UaaTokenEnhancer uaaTokenEnhancer = null;
private IdTokenCreator idTokenCreator;
private RefreshTokenCreator refreshTokenCreator;
private TokenEndpointBuilder tokenEndpointBuilder;
private TimeService timeService;
private TokenValidityResolver accessTokenValidityResolver;
private TokenValidationService tokenValidationService;
private KeyInfoService keyInfoService;
private IdTokenGranter idTokenGranter;
public UaaTokenServices(IdTokenCreator idTokenCreator,
TokenEndpointBuilder tokenEndpointBuilder,
ClientServicesExtension clientDetailsService,
RevocableTokenProvisioning revocableTokenProvisioning,
TokenValidationService tokenValidationService,
RefreshTokenCreator refreshTokenCreator,
TimeService timeService,
TokenValidityResolver accessTokenValidityResolver,
UaaUserDatabase userDatabase,
Set excludedClaims,
TokenPolicy globalTokenPolicy,
KeyInfoService keyInfoService,
IdTokenGranter idTokenGranter,
ApprovalService approvalService){
this.idTokenCreator = idTokenCreator;
this.tokenEndpointBuilder = tokenEndpointBuilder;
this.clientDetailsService = clientDetailsService;
this.tokenProvisioning = revocableTokenProvisioning;
this.tokenValidationService = tokenValidationService;
this.refreshTokenCreator = refreshTokenCreator;
this.timeService = timeService;
this.accessTokenValidityResolver = accessTokenValidityResolver;
this.userDatabase = userDatabase;
this.approvalService = approvalService;
this.excludedClaims = excludedClaims;
this.tokenPolicy = globalTokenPolicy;
this.idTokenGranter = idTokenGranter;
this.keyInfoService = keyInfoService;
}
public Set getExcludedClaims() {
return excludedClaims;
}
public void setExcludedClaims(Set excludedClaims) {
this.excludedClaims = excludedClaims;
}
public RevocableTokenProvisioning getTokenProvisioning() {
return tokenProvisioning;
}
public void setTokenProvisioning(RevocableTokenProvisioning tokenProvisioning) {
this.tokenProvisioning = tokenProvisioning;
}
public void setUaaTokenEnhancer(UaaTokenEnhancer uaaTokenEnhancer) {
this.uaaTokenEnhancer = uaaTokenEnhancer;
}
@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)");
}
TokenValidation tokenValidation = tokenValidationService
.validateToken(refreshTokenValue, false)
.checkJti();
Map refreshTokenClaims = tokenValidation.getClaims();
ArrayList tokenScopes = getScopesFromRefreshToken(refreshTokenClaims);
refreshTokenCreator.ensureRefreshTokenCreationNotRestricted(tokenScopes);
String userId = (String) refreshTokenClaims.get(USER_ID);
String refreshTokenId = (String) refreshTokenClaims.get(JTI);
Integer refreshTokenExpirySeconds = (Integer) refreshTokenClaims.get(EXP);
String clientId = (String) refreshTokenClaims.get(CID);
Boolean revocableClaim = (Boolean) refreshTokenClaims.get(REVOCABLE);
String refreshGrantType = refreshTokenClaims.get(GRANT_TYPE).toString();
String nonce = (String) refreshTokenClaims.get(NONCE);
String revocableHashSignature = (String) refreshTokenClaims.get(REVOCATION_SIGNATURE);
Map additionalAuthorizationInfo = (Map) refreshTokenClaims.get(ADDITIONAL_AZ_ATTR);
Set audience = new HashSet<>((ArrayList) refreshTokenClaims.get(AUD));
Integer authTime = (Integer) refreshTokenClaims.get(AUTH_TIME);
// default request scopes to what is in the refresh token
Set requestedScopes = request.getScope().isEmpty() ? Sets.newHashSet(tokenScopes) : request.getScope();
Map requestParams = request.getRequestParameters();
String requestedTokenFormat = requestParams.get(REQUEST_TOKEN_FORMAT);
String requestedClientId = request.getClientId();
if (clientId == null || !clientId.equals(requestedClientId)) {
throw new InvalidGrantException("Wrong client for this refresh token: " + clientId);
}
boolean isOpaque = OPAQUE.getStringValue().equals(requestedTokenFormat);
boolean isRevocable = isOpaque || (revocableClaim == null ? false : revocableClaim);
UaaUser user = userDatabase.retrieveUserById(userId);
BaseClientDetails client = (BaseClientDetails) clientDetailsService.loadClientByClientId(clientId);
long refreshTokenExpireMillis = refreshTokenExpirySeconds.longValue() * 1000L;
if (new Date(refreshTokenExpireMillis).before(timeService.getCurrentDate())) {
throw new InvalidTokenException("Invalid refresh token expired at " + new Date(refreshTokenExpireMillis));
}
// 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<>(tokenScopes)
);
}
// ensure all requested scopes are approved: either automatically or
// explicitly by the user
approvalService.ensureRequiredApprovals(
userId,
requestedScopes,
refreshGrantType,
client);
throwIfInvalidRevocationHashSignature(revocableHashSignature, user, client);
Map additionalRootClaims = new HashMap<>();
if (uaaTokenEnhancer != null) {
refreshTokenClaims.entrySet()
.stream()
.filter(entry -> !NON_ADDITIONAL_ROOT_CLAIMS.contains(entry.getKey()))
.forEach(
entry -> additionalRootClaims.put(entry.getKey(), entry.getValue())
);
// `granted_scopes` claim should not be present in an access token
refreshTokenClaims.remove(GRANTED_SCOPES);
}
UserAuthenticationData authenticationData = new UserAuthenticationData(
AuthTimeDateConverter.authTimeToDate(authTime),
authenticationMethodsAsSet(refreshTokenClaims),
getAcrAsSet(refreshTokenClaims),
requestedScopes,
rolesAsSet(userId),
getUserAttributes(userId),
nonce,
refreshGrantType,
generateUniqueTokenId()
);
String accessTokenId = generateUniqueTokenId();
refreshTokenValue = tokenValidation.getJwt().getEncoded();
CompositeToken compositeToken =
createCompositeToken(
accessTokenId,
user.getId(),
user,
AuthTimeDateConverter.authTimeToDate(authTime),
getClientPermissions(client),
clientId,
audience,
refreshTokenValue,
additionalAuthorizationInfo,
additionalRootClaims,
revocableHashSignature,
isRevocable,
authenticationData
);
CompositeExpiringOAuth2RefreshToken expiringRefreshToken = new CompositeExpiringOAuth2RefreshToken(
refreshTokenValue, new Date(refreshTokenExpireMillis), refreshTokenId
);
return persistRevocableToken(accessTokenId, compositeToken, expiringRefreshToken, clientId, user.getId(), isOpaque, isRevocable);
}
private void throwIfInvalidRevocationHashSignature(String revocableHashSignature, UaaUser user, ClientDetails client) {
if (hasText(revocableHashSignature)) {
String clientSecretForHash = client.getClientSecret();
if(clientSecretForHash != null && clientSecretForHash.split(" ").length > 1){
clientSecretForHash = clientSecretForHash.split(" ")[1];
}
String newRevocableHashSignature = UaaTokenUtils.getRevocableTokenSignature(client, clientSecretForHash, user);
if (!revocableHashSignature.equals(newRevocableHashSignature)) {
throw new TokenRevokedException("Invalid refresh token: revocable signature mismatch");
}
}
}
private Set getAcrAsSet(Map refreshTokenClaims) {
Map acrFromRefreshToken = (Map) refreshTokenClaims.get(ACR);
if (acrFromRefreshToken == null) {
return null;
}
return new HashSet<>((Collection) acrFromRefreshToken.get(ACR_VALUES_KEY));
}
private MultiValueMap getUserAttributes(String userId) {
UserInfo userInfo = userDatabase.getUserInfo(userId);
if (userInfo != null) {
return userInfo.getUserAttributes();
} else {
return new LinkedMultiValueMap<>();
}
}
private HashSet rolesAsSet(String userId) {
UserInfo userInfo = userDatabase.getUserInfo(userId);
if (userInfo != null) {
ArrayList roles = (ArrayList) userInfo.getRoles();
return roles == null ? Sets.newHashSet() : Sets.newHashSet(roles);
} else {
return Sets.newHashSet();
}
}
private HashSet authenticationMethodsAsSet(Map refreshTokenClaims) {
ArrayList authenticationMethods = (ArrayList) refreshTokenClaims.get(AMR);
return authenticationMethods == null ? Sets.newHashSet() : Sets.newHashSet(authenticationMethods);
}
private CompositeToken createCompositeToken(String tokenId,
String userId,
UaaUser user,
Date userAuthenticationTime,
Collection clientScopes,
String clientId,
Set resourceIds,
String refreshToken,
Map additionalAuthorizationAttributes,
Map additionalRootClaims,
String revocableHashSignature,
boolean isRevocable,
UserAuthenticationData userAuthenticationData) throws AuthenticationException {
CompositeToken compositeToken = new CompositeToken(tokenId);
compositeToken.setExpiration(accessTokenValidityResolver.resolve(clientId));
compositeToken.setRefreshToken(refreshToken == null ? null : new DefaultOAuth2RefreshToken(refreshToken));
Set requestedScopes = userAuthenticationData.scopes;
String grantType = userAuthenticationData.grantType;
if (null == requestedScopes || requestedScopes.size() == 0) {
logger.debug("No scopes were granted");
throw new InvalidTokenException("No scopes were granted");
}
compositeToken.setScope(requestedScopes);
ConcurrentMap info = new ConcurrentHashMap<>();
info.put(JTI, compositeToken.getValue());
if (null != additionalAuthorizationAttributes) {
info.put(ADDITIONAL_AZ_ATTR, additionalAuthorizationAttributes);
}
String nonce = userAuthenticationData.nonce;
if (nonce != null) {
info.put(NONCE, nonce);
}
compositeToken.setAdditionalInformation(info);
String content;
Map jwtAccessToken = createJWTAccessToken(
compositeToken,
userId,
user,
userAuthenticationTime,
clientScopes,
requestedScopes,
clientId,
resourceIds,
grantType,
revocableHashSignature,
isRevocable,
additionalRootClaims);
try {
content = JsonUtils.writeValueAsString(jwtAccessToken);
} catch (JsonUtils.JsonUtilException e) {
throw new IllegalStateException("Cannot convert access token to JSON", e);
}
String token = JwtHelper.encode(content, getActiveKeyInfo()).getEncoded();
compositeToken.setValue(token);
BaseClientDetails clientDetails = (BaseClientDetails) clientDetailsService.loadClientByClientId(clientId);
if (idTokenGranter.shouldSendIdToken(userId, clientDetails, requestedScopes, grantType)) {
String idTokenContent;
try {
idTokenContent = JsonUtils.writeValueAsString(idTokenCreator.create(clientId, userId, userAuthenticationData));
} catch (RuntimeException | IdTokenCreationException e) {
throw new IllegalStateException("Cannot convert id token to JSON");
}
String encodedIdTokenContent = JwtHelper.encode(idTokenContent, keyInfoService.getActiveKey()).getEncoded();
compositeToken.setIdTokenValue(encodedIdTokenContent);
}
publish(new TokenIssuedEvent(compositeToken, SecurityContextHolder.getContext().getAuthentication()));
return compositeToken;
}
private KeyInfo getActiveKeyInfo() {
return ofNullable(keyInfoService.getActiveKey())
.orElseThrow(() -> new InternalAuthenticationServiceException("Unable to sign token, misconfigured JWT signing keys"));
}
private Map createJWTAccessToken(OAuth2AccessToken token,
String userId,
UaaUser user,
Date userAuthenticationTime,
Collection clientScopes,
Set requestedScopes,
String clientId,
Set resourceIds,
String grantType,
String revocableHashSignature,
boolean isRevocable,
Map additionalRootClaims) {
Map claims = new LinkedHashMap<>();
claims.put(JTI, token.getAdditionalInformation().get(JTI));
claims.putAll(token.getAdditionalInformation());
if(additionalRootClaims != null) {
claims.putAll(additionalRootClaims);
}
claims.put(SUB, clientId);
if (GRANT_TYPE_CLIENT_CREDENTIALS.equals(grantType)) {
claims.put(AUTHORITIES, AuthorityUtils.authorityListToSet(clientScopes));
}
claims.put(OAuth2AccessToken.SCOPE, requestedScopes);
claims.put(CLIENT_ID, clientId);
claims.put(CID, clientId);
claims.put(AZP, clientId);
if (isRevocable) {
claims.put(REVOCABLE, true);
}
if (null != grantType) {
claims.put(GRANT_TYPE, grantType);
}
if (user!=null && userId!=null) {
claims.put(USER_ID, userId);
String origin = user.getOrigin();
if (StringUtils.hasLength(origin)) {
claims.put(ORIGIN, origin);
}
String username = user.getUsername();
claims.put(USER_NAME, username == null ? userId : username);
String userEmail = user.getEmail();
if (userEmail != null) {
claims.put(EMAIL, userEmail);
}
if (userAuthenticationTime!=null) {
claims.put(AUTH_TIME, userAuthenticationTime.getTime() / 1000);
}
claims.put(SUB, userId);
}
if (StringUtils.hasText(revocableHashSignature)) {
claims.put(REVOCATION_SIGNATURE, revocableHashSignature);
}
claims.put(IAT, timeService.getCurrentTimeMillis() / 1000);
claims.put(EXP, token.getExpiration().getTime() / 1000);
if (tokenEndpointBuilder.getTokenEndpoint() != null) {
claims.put(ISS, tokenEndpointBuilder.getTokenEndpoint());
claims.put(ZONE_ID,IdentityZoneHolder.get().getId());
}
claims.put(AUD, resourceIds);
for (String excludedClaim : getExcludedClaims()) {
claims.remove(excludedClaim);
}
return claims;
}
@Override
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
String userId = null;
Date userAuthenticationTime = null;
UaaUser user = null;
Set authenticationMethods = null;
Set authNContextClassRef = null;
OAuth2Request oAuth2Request = authentication.getOAuth2Request();
BaseClientDetails client = (BaseClientDetails) clientDetailsService.loadClientByClientId(oAuth2Request.getClientId(), IdentityZoneHolder.get().getId());
Collection clientScopes = null;
// Clients should really by different kinds of users
if (authentication.isClientOnly()) {
clientScopes = client.getAuthorities();
} else {
clientScopes = getClientPermissions(client);
userId = getUserId(authentication);
user = userDatabase.retrieveUserById(userId);
if (authentication.getUserAuthentication() instanceof UaaAuthentication) {
userAuthenticationTime = new Date(((UaaAuthentication)authentication.getUserAuthentication()).getAuthenticatedTime());
authenticationMethods = ((UaaAuthentication) authentication.getUserAuthentication()).getAuthenticationMethods();
authNContextClassRef = ((UaaAuthentication) authentication.getUserAuthentication()).getAuthContextClassRef();
}
validateRequiredUserGroups(user, client);
}
String clientSecretForHash = client.getClientSecret();
if(clientSecretForHash != null && clientSecretForHash.split(" ").length > 1){
clientSecretForHash = clientSecretForHash.split(" ")[1];
}
String revocableHashSignature = UaaTokenUtils.getRevocableTokenSignature(client, clientSecretForHash, user);
String tokenId = generateUniqueTokenId();
boolean isOpaque = isOpaqueTokenRequired(authentication);
boolean isAccessTokenRevocable = isOpaque || getActiveTokenPolicy().isJwtRevocable();
boolean isRefreshTokenRevocable = isAccessTokenRevocable || OPAQUE.getStringValue().equals(getActiveTokenPolicy().getRefreshTokenFormat());
Map additionalRootClaims = null;
if (uaaTokenEnhancer != null) {
additionalRootClaims = new HashMap<>(uaaTokenEnhancer.enhance(emptyMap(), authentication));
}
CompositeExpiringOAuth2RefreshToken refreshToken = null;
if(client.getAuthorizedGrantTypes().contains(GRANT_TYPE_REFRESH_TOKEN)){
RefreshTokenRequestData refreshTokenRequestData = new RefreshTokenRequestData(
oAuth2Request.getGrantType(),
oAuth2Request.getScope(),
authenticationMethods,
oAuth2Request.getRequestParameters().get(REQUEST_AUTHORITIES),
oAuth2Request.getResourceIds(),
oAuth2Request.getClientId(),
isRefreshTokenRevocable,
userAuthenticationTime,
authNContextClassRef,
additionalRootClaims
);
refreshToken = refreshTokenCreator.createRefreshToken(user, refreshTokenRequestData, revocableHashSignature);
}
String clientId = oAuth2Request.getClientId();
Set userScopes = oAuth2Request.getScope();
Map requestParameters = oAuth2Request.getRequestParameters();
String grantType = requestParameters.get(GRANT_TYPE);
Set modifiableUserScopes = new LinkedHashSet<>(userScopes);
Set externalGroupsForIdToken = Sets.newHashSet();
Map> userAttributesForIdToken = Maps.newHashMap();
if (authentication.getUserAuthentication() instanceof UaaAuthentication) {
externalGroupsForIdToken = ((UaaAuthentication)authentication.getUserAuthentication()).getExternalGroups();
userAttributesForIdToken = ((UaaAuthentication)authentication.getUserAuthentication()).getUserAttributes();
}
String nonce = requestParameters.get(NONCE);
Map additionalAuthorizationAttributes =
new AuthorizationAttributesParser().getAdditionalAuthorizationAttributes(
requestParameters.get(REQUEST_AUTHORITIES)
);
UserAuthenticationData authenticationData = new UserAuthenticationData(userAuthenticationTime,
authenticationMethods,
authNContextClassRef,
modifiableUserScopes,
externalGroupsForIdToken,
userAttributesForIdToken,
nonce,
grantType,
tokenId);
String refreshTokenValue = refreshToken != null ? refreshToken.getValue() : null;
CompositeToken accessToken =
createCompositeToken(
tokenId,
userId,
user,
userAuthenticationTime,
clientScopes,
clientId,
oAuth2Request.getResourceIds(),
refreshTokenValue,
additionalAuthorizationAttributes,
additionalRootClaims,
revocableHashSignature,
isAccessTokenRevocable,
authenticationData);
return persistRevocableToken(tokenId, accessToken, refreshToken, clientId, userId, isOpaque, isAccessTokenRevocable);
}
private TokenPolicy getActiveTokenPolicy() {
return IdentityZoneHolder.get().getConfig().getTokenPolicy();
}
private Collection getClientPermissions(ClientDetails client) {
Collection clientScopes;
clientScopes = new ArrayList<>();
for(String scope : client.getScope()) {
clientScopes.add(new XOAuthUserAuthority(scope));
}
return clientScopes;
}
private void validateRequiredUserGroups(UaaUser user, ClientDetails client) {
Collection requiredUserGroups = ofNullable((Collection) client.getAdditionalInformation().get(REQUIRED_USER_GROUPS)).orElse(emptySet());
if (!UaaTokenUtils.hasRequiredUserAuthorities(requiredUserGroups, user.getAuthorities())) {
throw new InvalidTokenException("User does not meet the client's required group criteria.");
}
}
CompositeToken persistRevocableToken(String tokenId,
CompositeToken token,
CompositeExpiringOAuth2RefreshToken refreshToken,
String clientId,
String userId,
boolean isOpaque,
boolean isRevocable) {
String scope = token.getScope().toString();
long now = timeService.getCurrentTimeMillis();
if (isRevocable) {
RevocableToken revocableAccessToken = new RevocableToken()
.setTokenId(tokenId)
.setClientId(clientId)
.setExpiresAt(token.getExpiration().getTime())
.setIssuedAt(now)
.setFormat(isOpaque ? OPAQUE.getStringValue() : JWT.getStringValue())
.setResponseType(ACCESS_TOKEN)
.setZoneId(IdentityZoneHolder.get().getId())
.setUserId(userId)
.setScope(scope)
.setValue(token.getValue());
try {
tokenProvisioning.create(revocableAccessToken, IdentityZoneHolder.get().getId());
} catch (DuplicateKeyException updateInstead) {
tokenProvisioning.update(tokenId, revocableAccessToken, IdentityZoneHolder.get().getId());
}
}
boolean isRefreshTokenOpaque = isOpaque || OPAQUE.getStringValue().equals(getActiveTokenPolicy().getRefreshTokenFormat());
boolean refreshTokenRevocable = isRefreshTokenOpaque || getActiveTokenPolicy().isJwtRevocable();
boolean refreshTokenUnique = getActiveTokenPolicy().isRefreshTokenUnique();
if (refreshToken != null && refreshTokenRevocable) {
RevocableToken revocableRefreshToken = new RevocableToken()
.setTokenId(refreshToken.getJti())
.setClientId(clientId)
.setExpiresAt(refreshToken.getExpiration().getTime())
.setIssuedAt(now)
.setFormat(isRefreshTokenOpaque ? OPAQUE.getStringValue() : JWT.getStringValue())
.setResponseType(REFRESH_TOKEN)
.setZoneId(IdentityZoneHolder.get().getId())
.setUserId(userId)
.setScope(scope)
.setValue(refreshToken.getValue());
try {
if(refreshTokenUnique) {
tokenProvisioning.deleteRefreshTokensForClientAndUserId(clientId, userId, IdentityZoneHolder.get().getId());
}
tokenProvisioning.create(revocableRefreshToken, IdentityZoneHolder.get().getId());
} catch (DuplicateKeyException ignore) {
//no need to store refresh tokens again
}
}
CompositeToken result = new CompositeToken(isOpaque ? tokenId : token.getValue());
result.setIdTokenValue(token.getIdTokenValue());
result.setExpiration(token.getExpiration());
result.setAdditionalInformation(token.getAdditionalInformation());
result.setScope(token.getScope());
result.setTokenType(token.getTokenType());
result.setRefreshToken(buildRefreshTokenResponse(refreshToken, isRefreshTokenOpaque));
return result;
}
private OAuth2RefreshToken buildRefreshTokenResponse(CompositeExpiringOAuth2RefreshToken refreshToken, boolean isRefreshTokenOpaque) {
if (refreshToken == null) {
return null;
} else {
if (isRefreshTokenOpaque) {
return new DefaultOAuth2RefreshToken(refreshToken.getJti());
} else {
return new DefaultOAuth2RefreshToken(refreshToken.getValue());
}
}
}
boolean isOpaqueTokenRequired(OAuth2Authentication authentication) {
Map parameters = authentication.getOAuth2Request().getRequestParameters();
return OPAQUE.getStringValue().equals(parameters.get(REQUEST_TOKEN_FORMAT)) ||
GRANT_TYPE_USER_TOKEN.equals(parameters.get(GRANT_TYPE));
}
private String getUserId(OAuth2Authentication authentication) {
return Origin.getUserId(authentication.getUserAuthentication());
}
private String generateUniqueTokenId() {
return UUID.randomUUID().toString().replace("-", "");
}
public void setUserDatabase(UaaUserDatabase userDatabase) {
this.userDatabase = userDatabase;
}
@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException {
if (StringUtils.isEmpty(accessToken)) {
throw new InvalidTokenException("Invalid access token value, must be at least 30 characters");
}
TokenValidation tokenValidation =
tokenValidationService.validateToken(accessToken, true)
.checkJti();
Map claims = tokenValidation.getClaims();
accessToken = tokenValidation.getJwt().getEncoded();
// Check token expiry
Long expiration = Long.valueOf(claims.get(EXP).toString());
if (new Date(expiration * 1000L).before(timeService.getCurrentDate())) {
throw new InvalidTokenException("Invalid access token: expired at " + new Date(expiration * 1000L));
}
@SuppressWarnings("unchecked")
ArrayList scopes = (ArrayList) claims.get(SCOPE);
AuthorizationRequest authorizationRequest = new AuthorizationRequest((String) claims.get(CLIENT_ID),
scopes);
ArrayList rids = (ArrayList) claims.get(AUD);
Set resourceIds = Collections.unmodifiableSet(rids==null?new HashSet<>():new HashSet<>(rids));
authorizationRequest.setResourceIds(resourceIds);
authorizationRequest.setApproved(true);
Collection defaultUserAuthorities = IdentityZoneHolder.get().getConfig().getUserConfig().getDefaultGroups();
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 - minimum info is user_id
if (claims.containsKey(USER_ID)) {
UaaUser user = userDatabase.retrieveUserById((String)claims.get(USER_ID));
UaaPrincipal principal = new UaaPrincipal(user);
userAuthentication = new UaaAuthentication(principal, UaaAuthority.USER_AUTHORITIES, null);
} else {
authorizationRequest.setAuthorities(authorities);
}
OAuth2Authentication authentication = new UaaOauth2Authentication(accessToken,
IdentityZoneHolder.get().getId(),
authorizationRequest.createOAuth2Request(),
userAuthentication);
authentication.setAuthenticated(true);
return authentication;
}
private ArrayList getScopesFromRefreshToken(Map claims) {
if (claims.containsKey(GRANTED_SCOPES)) {
return (ArrayList) claims.get(GRANTED_SCOPES);
}
return (ArrayList) claims.get(SCOPE);
}
/**
* This method is implemented to support older API calls that assume the
* presence of a token store
*/
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
TokenValidation tokenValidation =
tokenValidationService.validateToken(accessToken, true).checkJti();
Map claims = tokenValidation.getClaims();
accessToken = tokenValidation.getJwt().getEncoded();
// Expiry is verified by check_token
CompositeToken token = new CompositeToken(accessToken);
token.setTokenType(OAuth2AccessToken.BEARER_TYPE);
token.setExpiration(new Date(Long.valueOf(claims.get(EXP).toString()) * 1000L));
@SuppressWarnings("unchecked")
ArrayList scopes = (ArrayList) claims.get(SCOPE);
if (null != scopes && scopes.size() > 0) {
token.setScope(new HashSet<>(scopes));
}
String clientId = (String)claims.get(CID);
String userId = (String)claims.get(USER_ID);
BaseClientDetails client = (BaseClientDetails) clientDetailsService.loadClientByClientId(clientId, IdentityZoneHolder.get().getId());
// Only check user access tokens
if (null != userId) {
@SuppressWarnings("unchecked")
ArrayList tokenScopes = (ArrayList) claims.get(SCOPE);
approvalService.ensureRequiredApprovals(userId, tokenScopes, (String) claims.get(GRANT_TYPE), client);
}
return token;
}
/**
* 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 setClientDetailsService(ClientServicesExtension clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
private void publish(TokenIssuedEvent event) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(event);
}
}
public void setTokenPolicy(TokenPolicy tokenPolicy) {
this.tokenPolicy = tokenPolicy;
}
public TokenPolicy getTokenPolicy() {
return tokenPolicy;
}
public void setTokenEndpointBuilder(TokenEndpointBuilder tokenEndpointBuilder) {
this.tokenEndpointBuilder = tokenEndpointBuilder;
}
public void setTimeService(TimeService timeService) {
this.timeService = timeService;
}
public void setKeyInfoService(KeyInfoService keyInfoService) {
this.keyInfoService = keyInfoService;
}
}