This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch 3.1.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 44216c8e5d081cfea61874281cb7f73f9f8e5b23 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Wed Aug 22 12:49:51 2018 +0100 CXF-7806 - Add option to create JWT access tokens without persisting them --- .../cxf/rs/security/oauth2/common/AccessToken.java | 39 ++++--- .../oauth2/provider/AbstractOAuthDataProvider.java | 123 ++++++++++++--------- .../services/AbstractAccessTokenValidator.java | 62 ++++++++--- .../oauth2/services/TokenIntrospectionService.java | 47 ++++++-- .../cxf/rs/security/oauth2/utils/OAuthUtils.java | 96 ++++++++-------- .../security/oidc/idp/IdTokenResponseFilter.java | 23 ++-- 6 files changed, 241 insertions(+), 149 deletions(-) diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/common/AccessToken.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/common/AccessToken.java index 39699d4..5bee11e 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/common/AccessToken.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/common/AccessToken.java @@ -27,6 +27,7 @@ import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.MapKeyColumn; import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; /** * Base Access Token representation @@ -35,33 +36,34 @@ import javax.persistence.MappedSuperclass; public abstract class AccessToken implements Serializable { private static final long serialVersionUID = -5750544301887053480L; - + private String tokenKey; private String tokenType; private String refreshToken; private long expiresIn = -1; private long issuedAt = -1; private String issuer; - - + private String encodedToken; + + private Map<String, String> parameters = new LinkedHashMap<String, String>(); - + protected AccessToken() { - + } - + protected AccessToken(String tokenType, String tokenKey) { this.tokenType = tokenType; this.tokenKey = tokenKey; } - + protected AccessToken(String tokenType, String tokenKey, long expiresIn, long issuedAt) { this(tokenType, tokenKey); this.expiresIn = expiresIn; this.issuedAt = issuedAt; } - + protected AccessToken(String tokenType, String tokenKey, long expiresIn, long issuedAt, String refreshToken, @@ -78,11 +80,11 @@ public abstract class AccessToken implements Serializable { public String getTokenType() { return tokenType; } - + public void setTokenType(String type) { this.tokenType = type; } - + /** * Returns the token key * @return the key @@ -91,7 +93,7 @@ public abstract class AccessToken implements Serializable { public String getTokenKey() { return tokenKey; } - + public void setTokenKey(String key) { this.tokenKey = key; } @@ -113,9 +115,9 @@ public abstract class AccessToken implements Serializable { public String getRefreshToken() { return refreshToken; } - + /** - * Gets token parameters + * Gets token parameters * @return */ @ElementCollection(fetch = FetchType.EAGER) @@ -143,7 +145,7 @@ public abstract class AccessToken implements Serializable { public void setIssuedAt(long issuedAt) { this.issuedAt = issuedAt; } - + /** * Sets additional token parameters * @param parameters the token parameters @@ -159,4 +161,13 @@ public abstract class AccessToken implements Serializable { public void setIssuer(String issuer) { this.issuer = issuer; } + + @Transient + public String getEncodedToken() { + return encodedToken; + } + + public void setEncodedToken(String encodedToken) { + this.encodedToken = encodedToken; + } } diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java index 209e574..a699912 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java @@ -54,15 +54,16 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl private List<String> requiredScopes; private List<String> invisibleToClientScopes; private boolean supportPreauthorizedTokens; - + private boolean useJwtFormatForAccessTokens; + private boolean persistJwtEncoding = true; private OAuthJoseJwtProducer jwtAccessTokenProducer; private Map<String, String> jwtAccessTokenClaimMap; private ProviderAuthenticationStrategy authenticationStrategy; protected AbstractOAuthDataProvider() { } - + @Override public ServerAccessToken createAccessToken(AccessTokenRegistration reg) throws OAuthServiceException { @@ -73,13 +74,13 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl } return at; } - + protected ServerAccessToken doCreateAccessToken(AccessTokenRegistration atReg) { ServerAccessToken at = createNewAccessToken(atReg.getClient(), atReg.getSubject()); at.setAudiences(atReg.getAudiences()); at.setGrantType(atReg.getGrantType()); List<String> theScopes = atReg.getApprovedScope(); - List<OAuthPermission> thePermissions = + List<OAuthPermission> thePermissions = convertScopeToPermissions(atReg.getClient(), theScopes); at.setScopes(thePermissions); at.setSubject(atReg.getSubject()); @@ -100,19 +101,23 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl if (isUseJwtFormatForAccessTokens()) { JwtClaims claims = createJwtAccessToken(at); String jose = processJwtAccessToken(claims); - at.setTokenKey(jose); + if (isPersistJwtEncoding()) { + at.setTokenKey(jose); + } else { + at.setEncodedToken(jose); + } } - + return at; } - + protected JwtClaims createJwtAccessToken(ServerAccessToken at) { JwtClaims claims = new JwtClaims(); claims.setTokenId(at.getTokenKey()); - + // 'client_id' or 'cid', default client_id - String clientIdClaimName = - JwtTokenUtils.getClaimName(OAuthConstants.CLIENT_ID, OAuthConstants.CLIENT_ID, + String clientIdClaimName = + JwtTokenUtils.getClaimName(OAuthConstants.CLIENT_ID, OAuthConstants.CLIENT_ID, getJwtAccessTokenClaimMap()); claims.setClaim(clientIdClaimName, at.getClient().getClientId()); claims.setIssuedAt(at.getIssuedAt()); @@ -124,10 +129,10 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl if (userSubject.getId() != null) { claims.setSubject(userSubject.getId()); } - + // 'username' by default to be consistent with the token introspection response final String usernameProp = "username"; - String usernameClaimName = + String usernameClaimName = JwtTokenUtils.getClaimName(usernameProp, usernameProp, getJwtAccessTokenClaimMap()); claims.setClaim(usernameClaimName, userSubject.getLogin()); } @@ -135,7 +140,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl claims.setIssuer(at.getIssuer()); } if (!at.getScopes().isEmpty()) { - claims.setClaim(OAuthConstants.SCOPE, + claims.setClaim(OAuthConstants.SCOPE, OAuthUtils.convertPermissionsToScopeList(at.getScopes())); } // OAuth2 resource indicators (resource server audience) @@ -180,22 +185,22 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl } return claims; } - + protected ServerAccessToken createNewAccessToken(Client client, UserSubject userSub) { return new BearerAccessToken(client, accessTokenLifetime); } - + @Override public void removeAccessToken(ServerAccessToken token) throws OAuthServiceException { revokeAccessToken(token.getTokenKey()); } - + @Override public ServerAccessToken refreshAccessToken(Client client, String refreshTokenKey, List<String> restrictedScopes) throws OAuthServiceException { - RefreshToken currentRefreshToken = recycleRefreshTokens + RefreshToken currentRefreshToken = recycleRefreshTokens ? revokeRefreshToken(refreshTokenKey) : getRefreshToken(refreshTokenKey); - if (currentRefreshToken == null) { + if (currentRefreshToken == null) { throw new OAuthServiceException(OAuthConstants.ACCESS_DENIED); } if (OAuthUtils.isExpired(currentRefreshToken.getIssuedAt(), currentRefreshToken.getExpiresIn())) { @@ -207,7 +212,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl if (recycleRefreshTokens) { revokeAccessTokens(currentRefreshToken); } - + ServerAccessToken at = doRefreshAccessToken(client, currentRefreshToken, restrictedScopes); saveAccessToken(at); if (recycleRefreshTokens) { @@ -217,11 +222,11 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl } return at; } - + @Override public void revokeToken(Client client, String tokenKey, String tokenTypeHint) throws OAuthServiceException { ServerAccessToken accessToken = null; - if (!OAuthConstants.REFRESH_TOKEN.equals(tokenTypeHint)) { + if (!OAuthConstants.REFRESH_TOKEN.equals(tokenTypeHint)) { accessToken = revokeAccessToken(tokenKey); } if (accessToken != null) { @@ -237,7 +242,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl if (rt == null) { return; } - + unlinkRefreshAccessToken(rt, accessToken.getTokenKey()); if (rt.getAccessTokens().isEmpty()) { revokeRefreshToken(rt.getTokenKey()); @@ -245,7 +250,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl saveRefreshToken(rt); } } - + } protected void revokeAccessTokens(RefreshToken currentRefreshToken) { @@ -266,8 +271,8 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl } } - - + + @Override public List<OAuthPermission> convertScopeToPermissions(Client client, List<String> requestedScopes) { checkRequestedScopes(client, requestedScopes); @@ -281,9 +286,9 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl if (!list.isEmpty()) { return list; } - } + } throw new OAuthServiceException("Requested scopes can not be mapped"); - + } protected void checkRequestedScopes(Client client, List<String> requestedScopes) { @@ -292,7 +297,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl } } - protected void convertSingleScopeToPermission(Client client, + protected void convertSingleScopeToPermission(Client client, String scope, List<OAuthPermission> perms) { OAuthPermission permission = permissionMap.get(scope); @@ -303,9 +308,9 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl } @Override - public ServerAccessToken getPreauthorizedToken(Client client, + public ServerAccessToken getPreauthorizedToken(Client client, List<String> requestedScopes, - UserSubject sub, + UserSubject sub, String grantType) throws OAuthServiceException { if (!isSupportPreauthorizedTokens()) { return null; @@ -321,15 +326,15 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl break; } } - if (token != null + if (token != null && OAuthUtils.isExpired(token.getIssuedAt(), token.getExpiresIn())) { revokeToken(client, token.getTokenKey(), OAuthConstants.ACCESS_TOKEN); token = null; } return token; - + } - + protected boolean isRefreshTokenSupported(List<String> theScopes) { return theScopes.contains(OAuthConstants.REFRESH_TOKEN_SCOPE); } @@ -384,7 +389,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl rt.setClientCodeVerifier(at.getClientCodeVerifier()); return rt; } - + protected void linkAccessTokenToRefreshToken(RefreshToken rt, ServerAccessToken at) { if (!rt.getAccessTokens().contains(at.getTokenKey())) { rt.getAccessTokens().add(at.getTokenKey()); @@ -394,8 +399,8 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl at.setRefreshToken(rt.getTokenKey()); } - protected ServerAccessToken doRefreshAccessToken(Client client, - RefreshToken oldRefreshToken, + protected ServerAccessToken doRefreshAccessToken(Client client, + RefreshToken oldRefreshToken, List<String> restrictedScopes) { ServerAccessToken at = createNewAccessToken(client, oldRefreshToken.getSubject()); at.setAudiences(oldRefreshToken.getAudiences() != null @@ -421,12 +426,16 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl if (isUseJwtFormatForAccessTokens()) { JwtClaims claims = createJwtAccessToken(at); String jose = processJwtAccessToken(claims); - at.setTokenKey(jose); + if (isPersistJwtEncoding()) { + at.setTokenKey(jose); + } else { + at.setEncodedToken(jose); + } } return at; } - + public void setAccessTokenLifetime(long accessTokenLifetime) { this.accessTokenLifetime = accessTokenLifetime; } @@ -434,7 +443,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl public void setRefreshTokenLifetime(long refreshTokenLifetime) { this.refreshTokenLifetime = refreshTokenLifetime; } - + public void setRecycleRefreshTokens(boolean recycleRefreshTokens) { this.recycleRefreshTokens = recycleRefreshTokens; this.refreshTokenLock = recycleRefreshTokens ? null : new Object(); @@ -454,10 +463,10 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl } } } - + public void close() { } - + public Map<String, OAuthPermission> getPermissionMap() { return permissionMap; } @@ -465,7 +474,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl public void setPermissionMap(Map<String, OAuthPermission> permissionMap) { this.permissionMap = permissionMap; } - + public void setSupportedScopes(Map<String, String> scopes) { for (Map.Entry<String, String> entry : scopes.entrySet()) { OAuthPermission permission = new OAuthPermission(entry.getKey(), entry.getValue()); @@ -483,7 +492,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl OAuthUtils.injectContextIntoOAuthProvider(messageContext, authenticationStrategy); } } - + protected void removeClientTokens(Client c) { List<RefreshToken> refreshTokens = getRefreshTokens(c, null); if (refreshTokens != null) { @@ -498,7 +507,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl } } } - + @Override public Client removeClient(String clientId) { Client c = doGetClient(clientId); @@ -549,15 +558,15 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl } return at; } - protected RefreshToken revokeRefreshToken(String refreshTokenKey) { + protected RefreshToken revokeRefreshToken(String refreshTokenKey) { RefreshToken refreshToken = getRefreshToken(refreshTokenKey); if (refreshToken != null) { doRevokeRefreshToken(refreshToken); } return refreshToken; } - - + + protected abstract void saveAccessToken(ServerAccessToken serverToken); protected abstract void saveRefreshToken(RefreshToken refreshToken); protected abstract void doRevokeAccessToken(ServerAccessToken accessToken); @@ -600,8 +609,8 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl this.supportPreauthorizedTokens = supportPreauthorizedTokens; } protected static boolean isClientMatched(Client c, UserSubject resourceOwner) { - return resourceOwner == null - || c.getResourceOwnerSubject() != null + return resourceOwner == null + || c.getResourceOwnerSubject() != null && c.getResourceOwnerSubject().getLogin().equals(resourceOwner.getLogin()); } protected static boolean isTokenMatched(ServerAccessToken token, Client c, UserSubject sub) { @@ -613,7 +622,7 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl } return false; } - public void setClients(List<Client> clients) { + public void setClients(List<Client> clients) { for (Client c : clients) { setClient(c); } @@ -634,11 +643,11 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl public void setJwtAccessTokenProducer(OAuthJoseJwtProducer jwtAccessTokenProducer) { this.jwtAccessTokenProducer = jwtAccessTokenProducer; } - + protected String processJwtAccessToken(JwtClaims jwtCliams) { // It will JWS-sign (default) and/or JWE-encrypt - OAuthJoseJwtProducer processor = - getJwtAccessTokenProducer() == null ? new OAuthJoseJwtProducer() : getJwtAccessTokenProducer(); + OAuthJoseJwtProducer processor = + getJwtAccessTokenProducer() == null ? new OAuthJoseJwtProducer() : getJwtAccessTokenProducer(); return processor.processJwt(new JwtToken(jwtCliams)); } @@ -649,4 +658,12 @@ public abstract class AbstractOAuthDataProvider implements OAuthDataProvider, Cl public void setJwtAccessTokenClaimMap(Map<String, String> jwtAccessTokenClaimMap) { this.jwtAccessTokenClaimMap = jwtAccessTokenClaimMap; } + + public boolean isPersistJwtEncoding() { + return persistJwtEncoding; + } + + public void setPersistJwtEncoding(boolean persistJwtEncoding) { + this.persistJwtEncoding = persistJwtEncoding; + } } diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractAccessTokenValidator.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractAccessTokenValidator.java index 4d80d89..f8ab2ae 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractAccessTokenValidator.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractAccessTokenValidator.java @@ -31,6 +31,9 @@ import org.apache.cxf.jaxrs.ext.MessageContext; import org.apache.cxf.jaxrs.ext.MessageContextImpl; import org.apache.cxf.jaxrs.utils.ExceptionUtils; import org.apache.cxf.phase.PhaseInterceptorChain; +import org.apache.cxf.rs.security.jose.jwt.JoseJwtConsumer; +import org.apache.cxf.rs.security.jose.jwt.JwtException; +import org.apache.cxf.rs.security.jose.jwt.JwtToken; import org.apache.cxf.rs.security.oauth2.common.AccessTokenValidation; import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken; import org.apache.cxf.rs.security.oauth2.provider.AccessTokenValidator; @@ -41,41 +44,43 @@ import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants; import org.apache.cxf.rs.security.oauth2.utils.OAuthUtils; public abstract class AbstractAccessTokenValidator { - + private static final String DEFAULT_AUTH_SCHEME = OAuthConstants.BEARER_AUTHORIZATION_SCHEME; - + protected Set<String> supportedSchemes = new HashSet<String>(); protected String realm; - + private MessageContext mc; private List<AccessTokenValidator> tokenHandlers = Collections.emptyList(); private OAuthDataProvider dataProvider; - + private int maxValidationDataCacheSize; private ConcurrentHashMap<String, AccessTokenValidation> accessTokenValidations = new ConcurrentHashMap<String, AccessTokenValidation>(); - + private JoseJwtConsumer jwtTokenConsumer; + private boolean persistJwtEncoding = true; + public void setTokenValidator(AccessTokenValidator validator) { setTokenValidators(Collections.singletonList(validator)); } - + public void setTokenValidators(List<AccessTokenValidator> validators) { tokenHandlers = validators; for (AccessTokenValidator handler : validators) { supportedSchemes.addAll(handler.getSupportedAuthorizationSchemes()); } } - + public void setDataProvider(OAuthDataProvider provider) { dataProvider = provider; } - + @Context public void setMessageContext(MessageContext context) { this.mc = context; } - + public MessageContext getMessageContext() { return mc != null ? mc : new MessageContextImpl(PhaseInterceptorChain.getCurrentMessage()); } @@ -88,9 +93,9 @@ public abstract class AbstractAccessTokenValidator { return handler; } } - return null; + return null; } - + /** * Get the access token */ @@ -100,10 +105,10 @@ public abstract class AbstractAccessTokenValidator { if (dataProvider == null && tokenHandlers.isEmpty()) { throw ExceptionUtils.toInternalServerErrorException(null, null); } - + if (maxValidationDataCacheSize > 0) { accessTokenV = accessTokenValidations.get(authSchemeData); - } + } ServerAccessToken localAccessToken = null; if (accessTokenV == null) { // Get the registered handler capable of processing the token @@ -111,7 +116,7 @@ public abstract class AbstractAccessTokenValidator { if (handler != null) { try { // Convert the HTTP Authorization scheme data into a token - accessTokenV = handler.validateAccessToken(getMessageContext(), authScheme, authSchemeData, + accessTokenV = handler.validateAccessToken(getMessageContext(), authScheme, authSchemeData, extraProps); } catch (OAuthServiceException ex) { AuthorizationUtils.throwAuthorizationFailure(Collections.singleton(authScheme), realm); @@ -122,8 +127,16 @@ public abstract class AbstractAccessTokenValidator { // Default processing if no registered providers available if (accessTokenV == null && dataProvider != null && authScheme.equals(DEFAULT_AUTH_SCHEME)) { try { - localAccessToken = dataProvider.getAccessToken(authSchemeData); - } catch (OAuthServiceException ex) { + String cacheKey = authSchemeData; + if (!persistJwtEncoding) { + JoseJwtConsumer theConsumer = + jwtTokenConsumer == null ? new JoseJwtConsumer() : jwtTokenConsumer; + JwtToken token = theConsumer.getJwtToken(authSchemeData); + cacheKey = token.getClaims().getTokenId(); + } + + localAccessToken = dataProvider.getAccessToken(cacheKey); + } catch (JwtException | OAuthServiceException ex) { // to be handled next } if (localAccessToken == null) { @@ -168,5 +181,20 @@ public abstract class AbstractAccessTokenValidator { this.maxValidationDataCacheSize = maxValidationDataCacheSize; } - + public JoseJwtConsumer getJwtTokenConsumer() { + return jwtTokenConsumer; + } + + public void setJwtTokenConsumer(JoseJwtConsumer jwtTokenConsumer) { + this.jwtTokenConsumer = jwtTokenConsumer; + } + + public boolean isPersistJwtEncoding() { + return persistJwtEncoding; + } + + public void setPersistJwtEncoding(boolean persistJwtEncoding) { + this.persistJwtEncoding = persistJwtEncoding; + } + } diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/TokenIntrospectionService.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/TokenIntrospectionService.java index c21d43e..3fe5461 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/TokenIntrospectionService.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/TokenIntrospectionService.java @@ -34,6 +34,9 @@ import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.jaxrs.ext.MessageContext; import org.apache.cxf.jaxrs.utils.ExceptionUtils; +import org.apache.cxf.rs.security.jose.jwt.JoseJwtConsumer; +import org.apache.cxf.rs.security.jose.jwt.JwtException; +import org.apache.cxf.rs.security.jose.jwt.JwtToken; import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken; import org.apache.cxf.rs.security.oauth2.common.TokenIntrospection; import org.apache.cxf.rs.security.oauth2.common.UserSubject; @@ -49,14 +52,27 @@ public class TokenIntrospectionService { private boolean reportExtraTokenProperties = true; private MessageContext mc; private OAuthDataProvider dataProvider; + private JoseJwtConsumer jwtTokenConsumer; + private boolean persistJwtEncoding = true; + @POST @Produces({MediaType.APPLICATION_JSON }) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public TokenIntrospection getTokenIntrospection(@Encoded MultivaluedMap<String, String> params) { checkSecurityContext(); String tokenId = params.getFirst(OAuthConstants.TOKEN_ID); + if (!persistJwtEncoding) { + try { + JoseJwtConsumer theConsumer = jwtTokenConsumer == null ? new JoseJwtConsumer() : jwtTokenConsumer; + JwtToken token = theConsumer.getJwtToken(tokenId); + tokenId = token.getClaims().getTokenId(); + } catch (JwtException ex) { + return new TokenIntrospection(false); + } + } + ServerAccessToken at = dataProvider.getAccessToken(tokenId); - if (at == null || OAuthUtils.isExpired(at.getIssuedAt(), at.getExpiresIn())) { + if (at == null || OAuthUtils.isExpired(at.getIssuedAt(), at.getExpiresIn())) { return new TokenIntrospection(false); } TokenIntrospection response = new TokenIntrospection(true); @@ -77,18 +93,18 @@ public class TokenIntrospectionService { if (at.getIssuer() != null) { response.setIss(at.getIssuer()); } - + response.setIat(at.getIssuedAt()); if (at.getExpiresIn() > 0) { response.setExp(at.getIssuedAt() + at.getExpiresIn()); } - + response.setTokenType(at.getTokenType()); - + if (reportExtraTokenProperties) { response.getExtensions().putAll(at.getExtraProperties()); } - + return response; } @@ -102,7 +118,7 @@ public class TokenIntrospectionService { LOG.warning("Authenticated Principal is not available"); ExceptionUtils.toNotAuthorizedException(null, null); } - + } public void setBlockUnsecureRequests(boolean blockUnsecureRequests) { @@ -116,7 +132,7 @@ public class TokenIntrospectionService { public void setDataProvider(OAuthDataProvider dataProvider) { this.dataProvider = dataProvider; } - + @Context public void setMessageContext(MessageContext context) { this.mc = context; @@ -125,4 +141,21 @@ public class TokenIntrospectionService { public void setReportExtraTokenProperties(boolean reportExtraTokenProperties) { this.reportExtraTokenProperties = reportExtraTokenProperties; } + + public JoseJwtConsumer getJwtTokenConsumer() { + return jwtTokenConsumer; + } + + public void setJwtTokenConsumer(JoseJwtConsumer jwtTokenConsumer) { + this.jwtTokenConsumer = jwtTokenConsumer; + } + + public boolean isPersistJwtEncoding() { + return persistJwtEncoding; + } + + public void setPersistJwtEncoding(boolean persistJwtEncoding) { + this.persistJwtEncoding = persistJwtEncoding; + } + } diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/OAuthUtils.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/OAuthUtils.java index ffe0180..18412d1 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/OAuthUtils.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/OAuthUtils.java @@ -68,7 +68,7 @@ import org.apache.cxf.security.SecurityContext; import org.apache.cxf.security.transport.TLSSessionInfo; /** - * Various utility methods + * Various utility methods */ public final class OAuthUtils { @@ -88,7 +88,7 @@ public final class OAuthUtils { throw new OAuthServiceException(ex); } } - + public static boolean compareCertificateThumbprints(X509Certificate cert, String encodedThumbprint) { try { byte[] thumbprint = createCertificateThumbprint(cert); @@ -98,7 +98,7 @@ public final class OAuthUtils { return false; } } - + public static boolean compareTlsCertificates(TLSSessionInfo tlsInfo, List<String> base64EncodedCerts) { @@ -120,24 +120,24 @@ public final class OAuthUtils { } return false; } - + public static boolean isMutualTls(javax.ws.rs.core.SecurityContext sc, TLSSessionInfo tlsSessionInfo) { // Pure 2-way TLS authentication - return tlsSessionInfo != null + return tlsSessionInfo != null && StringUtils.isEmpty(sc.getAuthenticationScheme()) && getRootTLSCertificate(tlsSessionInfo) != null; } - + public static String getSubjectDnFromTLSCertificates(X509Certificate cert) { X500Principal x509Principal = cert.getSubjectX500Principal(); return x509Principal.getName(); } - + public static String getIssuerDnFromTLSCertificates(X509Certificate cert) { X500Principal x509Principal = cert.getIssuerX500Principal(); return x509Principal.getName(); } - + public static X509Certificate getRootTLSCertificate(TLSSessionInfo tlsInfo) { Certificate[] clientCerts = tlsInfo.getPeerCertificates(); if (clientCerts != null && clientCerts.length > 0) { @@ -145,7 +145,7 @@ public final class OAuthUtils { } return null; } - + public static void injectContextIntoOAuthProvider(MessageContext context, Object provider) { Method dataProviderContextMethod = null; try { @@ -162,7 +162,7 @@ public final class OAuthUtils { } } } - + public static String setSessionToken(MessageContext mc) { return setSessionToken(mc, 0); } @@ -175,8 +175,8 @@ public final class OAuthUtils { public static String setSessionToken(MessageContext mc, String sessionToken, int maxInactiveInterval) { return setSessionToken(mc, sessionToken, null, 0); } - public static String setSessionToken(MessageContext mc, String sessionToken, - String attribute, int maxInactiveInterval) { + public static String setSessionToken(MessageContext mc, String sessionToken, + String attribute, int maxInactiveInterval) { HttpSession session = mc.getHttpServletRequest().getSession(); if (maxInactiveInterval > 0) { session.setMaxInactiveInterval(maxInactiveInterval); @@ -192,12 +192,12 @@ public final class OAuthUtils { public static String getSessionToken(MessageContext mc, String attribute) { return getSessionToken(mc, attribute, true); } - public static String getSessionToken(MessageContext mc, String attribute, boolean remove) { + public static String getSessionToken(MessageContext mc, String attribute, boolean remove) { HttpSession session = mc.getHttpServletRequest().getSession(); - String theAttribute = attribute == null ? OAuthConstants.SESSION_AUTHENTICITY_TOKEN : attribute; + String theAttribute = attribute == null ? OAuthConstants.SESSION_AUTHENTICITY_TOKEN : attribute; String sessionToken = (String)session.getAttribute(theAttribute); if (sessionToken != null && remove) { - session.removeAttribute(theAttribute); + session.removeAttribute(theAttribute); } return sessionToken; } @@ -225,7 +225,7 @@ public final class OAuthUtils { } return subject; } - + public static String convertPermissionsToScope(List<OAuthPermission> perms) { StringBuilder sb = new StringBuilder(); for (OAuthPermission perm : perms) { @@ -239,7 +239,7 @@ public final class OAuthUtils { } return sb.toString(); } - + public static List<String> convertPermissionsToScopeList(List<OAuthPermission> perms) { List<String> list = new LinkedList<String>(); for (OAuthPermission perm : perms) { @@ -247,9 +247,9 @@ public final class OAuthUtils { } return list; } - - public static boolean isGrantSupportedForClient(Client client, - boolean canSupportPublicClients, + + public static boolean isGrantSupportedForClient(Client client, + boolean canSupportPublicClients, String grantType) { if (grantType == null || !client.isConfidential() && !canSupportPublicClients) { return false; @@ -257,13 +257,13 @@ public final class OAuthUtils { List<String> allowedGrants = client.getAllowedGrantTypes(); return allowedGrants.isEmpty() || allowedGrants.contains(grantType); } - + public static List<String> parseScope(String requestedScope) { List<String> list = new LinkedList<String>(); if (requestedScope != null) { String[] scopeValues = requestedScope.split(" "); for (String scope : scopeValues) { - if (!StringUtils.isEmpty(scope)) { + if (!StringUtils.isEmpty(scope)) { list.add(scope); } } @@ -280,34 +280,34 @@ public final class OAuthUtils { } return StringUtils.toHexString(CryptoUtils.generateSecureRandomBytes(byteSize)); } - + public static long getIssuedAt() { return System.currentTimeMillis() / 1000L; } - + public static boolean isExpired(Long issuedAt, Long lifetime) { // At some point -1 was used to indicate an unlimited lifetime - // with 0 being introduced instead at a later stage. - // In theory there still could be a code around initializing the tokens with -1. + // with 0 being introduced instead at a later stage. + // In theory there still could be a code around initializing the tokens with -1. // Treating -1 and 0 the same way is reasonable and it also makes it easier to // deal with the token introspection responses with no issuedAt time reported return lifetime == null || lifetime < -1 || lifetime > 0L && issuedAt + lifetime < System.currentTimeMillis() / 1000L; } - - public static boolean validateAudience(String providedAudience, + + public static boolean validateAudience(String providedAudience, List<String> allowedAudiences) { - return providedAudience == null + return providedAudience == null || validateAudiences(Collections.singletonList(providedAudience), allowedAudiences); } - public static boolean validateAudiences(List<String> providedAudiences, + public static boolean validateAudiences(List<String> providedAudiences, List<String> allowedAudiences) { - return StringUtils.isEmpty(providedAudiences) + return StringUtils.isEmpty(providedAudiences) && StringUtils.isEmpty(allowedAudiences) || allowedAudiences.containsAll(providedAudiences); } - + public static boolean checkRequestURI(String servletPath, String uri) { boolean wildcard = uri.endsWith("*"); String theURI = wildcard ? uri.substring(0, uri.length() - 1) : uri; @@ -325,8 +325,8 @@ public final class OAuthUtils { } return false; } - - public static List<String> getRequestedScopes(Client client, + + public static List<String> getRequestedScopes(Client client, String scopeParameter, boolean useAllClientScopes, boolean partialMatchScopeValidation) { @@ -346,21 +346,21 @@ public final class OAuthUtils { } } } - + return requestScopes; } - + public static boolean validateScopes(List<String> requestScopes, List<String> registeredScopes, boolean partialMatchScopeValidation) { if (!registeredScopes.isEmpty()) { - // if it is a strict validation then pre-registered scopes have to contains all + // if it is a strict validation then pre-registered scopes have to contains all // the current request scopes if (!partialMatchScopeValidation) { return registeredScopes.containsAll(requestScopes); } else { for (String requestScope : requestScopes) { boolean match = false; - for (String registeredScope : registeredScopes) { + for (String registeredScope : registeredScopes) { if (requestScope.startsWith(registeredScope)) { match = true; break; @@ -376,15 +376,17 @@ public final class OAuthUtils { } public static ClientAccessToken toClientAccessToken(ServerAccessToken serverToken, boolean supportOptionalParams) { + String tokenKey = + serverToken.getEncodedToken() != null ? serverToken.getEncodedToken() : serverToken.getTokenKey(); ClientAccessToken clientToken = new ClientAccessToken(serverToken.getTokenType(), - serverToken.getTokenKey()); + tokenKey); clientToken.setRefreshToken(serverToken.getRefreshToken()); if (supportOptionalParams) { clientToken.setExpiresIn(serverToken.getExpiresIn()); List<OAuthPermission> perms = serverToken.getScopes(); String scopeString = OAuthUtils.convertPermissionsToScope(perms); if (!StringUtils.isEmpty(scopeString)) { - clientToken.setApprovedScope(scopeString); + clientToken.setApprovedScope(scopeString); } clientToken.setParameters(new HashMap<String, String>(serverToken.getParameters())); } @@ -393,27 +395,27 @@ public final class OAuthUtils { public static JwsSignatureProvider getClientSecretSignatureProvider(String clientSecret) { Properties sigProps = JwsUtils.loadSignatureOutProperties(false); - return JwsUtils.getHmacSignatureProvider(clientSecret, + return JwsUtils.getHmacSignatureProvider(clientSecret, getClientSecretSignatureAlgorithm(sigProps)); } public static JwsSignatureVerifier getClientSecretSignatureVerifier(String clientSecret) { Properties sigProps = JwsUtils.loadSignatureOutProperties(false); - return JwsUtils.getHmacSignatureVerifier(clientSecret, + return JwsUtils.getHmacSignatureVerifier(clientSecret, getClientSecretSignatureAlgorithm(sigProps)); } - + public static JweDecryptionProvider getClientSecretDecryptionProvider(String clientSecret) { Properties props = JweUtils.loadEncryptionInProperties(false); byte[] key = StringUtils.toBytesUTF8(clientSecret); return JweUtils.getDirectKeyJweDecryption(key, getClientSecretContentAlgorithm(props)); } - + public static JweEncryptionProvider getClientSecretEncryptionProvider(String clientSecret) { Properties props = JweUtils.loadEncryptionInProperties(false); byte[] key = StringUtils.toBytesUTF8(clientSecret); return JweUtils.getDirectKeyJweEncryption(key, getClientSecretContentAlgorithm(props)); } - + private static ContentAlgorithm getClientSecretContentAlgorithm(Properties props) { String ctAlgoProp = props.getProperty(OAuthConstants.CLIENT_SECRET_CONTENT_ENCRYPTION_ALGORITHM); if (ctAlgoProp == null) { @@ -423,9 +425,9 @@ public final class OAuthUtils { ctAlgo = ctAlgo != null ? ctAlgo : ContentAlgorithm.A128GCM; return ctAlgo; } - + public static SignatureAlgorithm getClientSecretSignatureAlgorithm(Properties sigProps) { - + String clientSecretSigProp = sigProps.getProperty(OAuthConstants.CLIENT_SECRET_SIGNATURE_ALGORITHM); if (clientSecretSigProp == null) { String sigProp = sigProps.getProperty(JoseConstants.RSSEC_SIGNATURE_ALGORITHM); diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/IdTokenResponseFilter.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/IdTokenResponseFilter.java index f7ed11f..7fe1e89 100644 --- a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/IdTokenResponseFilter.java +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/IdTokenResponseFilter.java @@ -59,13 +59,13 @@ public class IdTokenResponseFilter extends OAuthServerJoseJwtProducer implements String idToken = getProcessedIdToken(st); if (idToken != null) { ct.getParameters().put(OidcUtils.ID_TOKEN, idToken); - } - + } + } private String getProcessedIdToken(ServerAccessToken st) { if (idTokenProvider != null) { - IdToken idToken = - idTokenProvider.getIdToken(st.getClient().getClientId(), st.getSubject(), + IdToken idToken = + idTokenProvider.getIdToken(st.getClient().getClientId(), st.getSubject(), OAuthUtils.convertPermissionsToScopeList(st.getScopes())); setAtHashAndNonce(idToken, st); return processJwt(new JwtToken(idToken), st.getClient()); @@ -84,17 +84,17 @@ public class IdTokenResponseFilter extends OAuthServerJoseJwtProducer implements } } return null; - + } private void setAtHashAndNonce(IdToken idToken, ServerAccessToken st) { String rType = st.getResponseType(); boolean atHashRequired = idToken.getAccessTokenHash() == null && (rType == null || !rType.equals(OidcUtils.ID_TOKEN_RESPONSE_TYPE)); - boolean cHashRequired = idToken.getAuthorizationCodeHash() == null - && rType != null + boolean cHashRequired = idToken.getAuthorizationCodeHash() == null + && rType != null && (rType.equals(OidcUtils.CODE_ID_TOKEN_AT_RESPONSE_TYPE) || rType.equals(OidcUtils.CODE_ID_TOKEN_RESPONSE_TYPE)); - + Message m = JAXRSUtils.getCurrentMessage(); if (atHashRequired || cHashRequired) { Properties props = JwsUtils.loadSignatureOutProperties(false); @@ -106,7 +106,8 @@ public class IdTokenResponseFilter extends OAuthServerJoseJwtProducer implements } if (sigAlgo != SignatureAlgorithm.NONE) { if (atHashRequired) { - String atHash = OidcUtils.calculateAccessTokenHash(st.getTokenKey(), sigAlgo); + String tokenKey = st.getEncodedToken() != null ? st.getEncodedToken() : st.getTokenKey(); + String atHash = OidcUtils.calculateAccessTokenHash(tokenKey, sigAlgo); idToken.setAccessTokenHash(atHash); } if (cHashRequired) { @@ -125,13 +126,13 @@ public class IdTokenResponseFilter extends OAuthServerJoseJwtProducer implements } } } - + if (m != null && m.getExchange().containsKey(OAuthConstants.NONCE)) { idToken.setNonce((String)m.getExchange().get(OAuthConstants.NONCE)); } else if (st.getNonce() != null) { idToken.setNonce(st.getNonce()); } - + } public void setIdTokenProvider(IdTokenProvider idTokenProvider) { this.idTokenProvider = idTokenProvider;
