From 5410744352ea0907c2eb5866069d54ef2f131600 Mon Sep 17 00:00:00 2001 From: Ryan Murfitt Date: Sun, 8 Jan 2017 14:11:13 +1000 Subject: [PATCH 01/15] Initial commit enabling token-exchange flow for the auth server --- ...uthorizationServerEndpointsConfigurer.java | 48 +++----- .../TokenExchangeAuthenticationToken.java | 32 +++++ .../exchange/TokenExchangeService.java | 14 +++ .../exchange/TokenExchangeTokenGranter.java | 78 ++++++++++++ .../TokenExchangeTokenGranterTests.java | 112 ++++++++++++++++++ 5 files changed, 252 insertions(+), 32 deletions(-) create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java index dc780e878..0dd9bc5dd 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java @@ -15,15 +15,6 @@ */ package org.springframework.security.oauth2.config.annotation.web.configurers; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.http.HttpMethod; @@ -36,17 +27,8 @@ import org.springframework.security.oauth2.common.util.ProxyCreator; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.CompositeTokenGranter; -import org.springframework.security.oauth2.provider.OAuth2RequestFactory; -import org.springframework.security.oauth2.provider.OAuth2RequestValidator; -import org.springframework.security.oauth2.provider.TokenGranter; -import org.springframework.security.oauth2.provider.TokenRequest; -import org.springframework.security.oauth2.provider.approval.ApprovalStore; -import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler; -import org.springframework.security.oauth2.provider.approval.TokenApprovalStore; -import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler; -import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.approval.*; import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter; import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; @@ -55,19 +37,14 @@ import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.exchange.TokenExchangeService; +import org.springframework.security.oauth2.provider.exchange.TokenExchangeTokenGranter; import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter; import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter; import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator; -import org.springframework.security.oauth2.provider.token.AccessTokenConverter; -import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; -import org.springframework.security.oauth2.provider.token.ConsumerTokenServices; -import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; -import org.springframework.security.oauth2.provider.token.TokenEnhancer; -import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.*; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; @@ -76,9 +53,11 @@ import org.springframework.web.context.request.WebRequestInterceptor; import org.springframework.web.servlet.HandlerInterceptor; +import java.util.*; + /** * Configure the properties and enhanced functionality of the Authorization Server endpoints. - * + * * @author Rob Winch * @author Dave Syer * @since 2.0 @@ -111,6 +90,8 @@ public final class AuthorizationServerEndpointsConfigurer { private AuthenticationManager authenticationManager; + private TokenExchangeService tokenExchangeService; + private ClientDetailsService clientDetailsService; private String prefix; @@ -242,7 +223,7 @@ public AuthorizationServerEndpointsConfigurer approvalStore(ApprovalStore approv * Explicitly disable the approval store, even if one would normally be added automatically (usually when JWT is not * used). Without an approval store the user can only be asked to approve or deny a grant without any more granular * decisions. - * + * * @return this for fluent builder */ public AuthorizationServerEndpointsConfigurer approvalStoreDisabled() { @@ -277,7 +258,7 @@ public AuthorizationServerEndpointsConfigurer exceptionTranslator(WebResponseExc /** * The AuthenticationManager for the password grant. - * + * * @param authenticationManager an AuthenticationManager, fully initialized * @return this for a fluent style */ @@ -545,7 +526,10 @@ private List getDefaultTokenGranters() { tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory)); } - return tokenGranters; + if (tokenExchangeService != null) { + tokenGranters.add(new TokenExchangeTokenGranter(tokenExchangeService, tokenServices, clientDetails, requestFactory)); + } + return tokenGranters; } private TokenGranter tokenGranter() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java new file mode 100644 index 000000000..bf81a0be5 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java @@ -0,0 +1,32 @@ +package org.springframework.security.oauth2.provider.exchange; + +import org.springframework.security.authentication.AbstractAuthenticationToken; + +/** + * @author Ryan Murfitt + */ +public class TokenExchangeAuthenticationToken extends AbstractAuthenticationToken { + + private final Object principal; + private final String principalType; + + TokenExchangeAuthenticationToken(Object principal, String principalType) { + super(null); + this.principal = principal; + this.principalType = principalType; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getPrincipal() { + return this.principal; + } + + public String getPrincipalType() { + return this.principalType; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java new file mode 100644 index 000000000..89886c686 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java @@ -0,0 +1,14 @@ +package org.springframework.security.oauth2.provider.exchange; + +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; + +/** + * Created on 8/1/17. + * + * @author Ryan Murfitt (ryan.murfitt@console.com.au) + */ +public interface TokenExchangeService { + Authentication loadUserAuthFromToken(TokenExchangeAuthenticationToken tokenAuth) throws AccountStatusException, InvalidTokenException; +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java new file mode 100644 index 000000000..03bf7b3cd --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.provider.exchange; + +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author Ryan Murfitt + */ +public class TokenExchangeTokenGranter extends AbstractTokenGranter { + + private static final String GRANT_TYPE = "token-exchange"; + + private final TokenExchangeService tokenExchangeService; + + public TokenExchangeTokenGranter(TokenExchangeService tokenExchangeService, + AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { + this(tokenExchangeService, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); + } + + protected TokenExchangeTokenGranter(TokenExchangeService tokenExchangeService, AuthorizationServerTokenServices tokenServices, + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { + super(tokenServices, clientDetailsService, requestFactory, grantType); + this.tokenExchangeService = tokenExchangeService; + } + + @Override + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { + + Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters()); + String subjectToken = parameters.get("subject_token"); + String subjectTokenType = parameters.get("subject_token_type"); + String actorToken = parameters.get("actor_token"); //TODO + String actorTokenType = parameters.get("actor_token_type"); //TODO + + TokenExchangeAuthenticationToken tokenAuth = new TokenExchangeAuthenticationToken(subjectToken, subjectTokenType); + tokenAuth.setDetails(parameters); + Authentication userAuth; + try { + userAuth = tokenExchangeService.loadUserAuthFromToken(tokenAuth); + } catch (AccountStatusException ase) { + //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) + throw new InvalidGrantException(ase.getMessage()); + } catch (InvalidTokenException e) { + // If the supplied subject token is invalid + throw new InvalidGrantException(e.getMessage()); + } + if (userAuth == null || !userAuth.isAuthenticated()) { + throw new InvalidGrantException("Could not authenticate user"); + } + + OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); + return new OAuth2Authentication(storedOAuth2Request, userAuth); + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java new file mode 100644 index 000000000..205e56e5a --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java @@ -0,0 +1,112 @@ +package org.springframework.security.oauth2.provider.exchange; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; + +/** + * Created on 8/1/17. + * + * @author Ryan Murfitt (ryan.murfitt@console.com.au) + */ +@RunWith(MockitoJUnitRunner.class) +public class TokenExchangeTokenGranterTests { + + private Authentication validUser = new UsernamePasswordAuthenticationToken("foo", "bar", + Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))); + + private BaseClientDetails client = new BaseClientDetails("foo", "resource", "scope", "token-exchange", "ROLE_USER"); + + @Mock + private TokenExchangeService tokenExchangeService; + + private DefaultTokenServices providerTokenServices = new DefaultTokenServices(); + + private ClientDetailsService clientDetailsService = new ClientDetailsService() { + public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception { + return client; + } + }; + + private OAuth2RequestFactory requestFactory = new DefaultOAuth2RequestFactory(clientDetailsService); + + private TokenExchangeTokenGranter granter; + + private TokenRequest tokenRequest; + + @Before + public void setup() { + when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); + + String clientId = "client"; + BaseClientDetails clientDetails = new BaseClientDetails(); + clientDetails.setClientId(clientId); + + providerTokenServices.setTokenStore(new InMemoryTokenStore()); + Map parameters = new HashMap(); + parameters.put("subject_token", "token123"); + parameters.put("subject_type", "bearer"); + + granter = new TokenExchangeTokenGranter(tokenExchangeService, + providerTokenServices, clientDetailsService, requestFactory); + + tokenRequest = requestFactory.createTokenRequest(parameters, clientDetails); + } + + @Test + public void testSuccessfulGrant() { + OAuth2AccessToken token = granter.grant("token-exchange", tokenRequest); + OAuth2Authentication authentication = providerTokenServices.loadAuthentication(token.getValue()); + assertTrue(authentication.isAuthenticated()); + } + + @Test(expected = InvalidClientException.class) + public void testGrantTypeNotSupported() { + client.setAuthorizedGrantTypes(Collections.singleton("client_credentials")); + granter.grant("token-exchange", tokenRequest); + } + + @Test(expected = InvalidGrantException.class) + public void testInvalidToken() { + when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenThrow(new InvalidTokenException("invalid token")); + granter.grant("token-exchange", tokenRequest); + } + + @Test(expected = InvalidGrantException.class) + public void testAccountLocked() { + when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenThrow(new LockedException("locked")); + granter.grant("token-exchange", tokenRequest); + } + + @Test(expected = InvalidGrantException.class) + public void testUnauthenticated() { + validUser = new UsernamePasswordAuthenticationToken("foo", "bar"); + when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); + granter.grant("token-exchange", tokenRequest); + } +} \ No newline at end of file From c3485a36bc857a8354c48ba3cfd53f83f5d91434 Mon Sep 17 00:00:00 2001 From: Ryan Murfitt Date: Sun, 8 Jan 2017 23:18:35 +1000 Subject: [PATCH 02/15] Refactor leveraging the authentication providers --- ...uthorizationServerEndpointsConfigurer.java | 7 +-- ...ltTokenExchangeAuthenticationProvider.java | 45 +++++++++++++++++++ .../TokenExchangeAuthenticationToken.java | 13 ++++-- .../exchange/TokenExchangeService.java | 9 ++-- .../exchange/TokenExchangeTokenGranter.java | 17 ++++--- .../TokenExchangeTokenGranterTests.java | 13 +++--- 6 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java index 0dd9bc5dd..0529e0849 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java @@ -37,7 +37,6 @@ import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; -import org.springframework.security.oauth2.provider.exchange.TokenExchangeService; import org.springframework.security.oauth2.provider.exchange.TokenExchangeTokenGranter; import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter; import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter; @@ -90,8 +89,6 @@ public final class AuthorizationServerEndpointsConfigurer { private AuthenticationManager authenticationManager; - private TokenExchangeService tokenExchangeService; - private ClientDetailsService clientDetailsService; private String prefix; @@ -525,10 +522,8 @@ private List getDefaultTokenGranters() { if (authenticationManager != null) { tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory)); + tokenGranters.add(new TokenExchangeTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory)); } - if (tokenExchangeService != null) { - tokenGranters.add(new TokenExchangeTokenGranter(tokenExchangeService, tokenServices, clientDetails, requestFactory)); - } return tokenGranters; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java new file mode 100644 index 000000000..87df85583 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java @@ -0,0 +1,45 @@ +package org.springframework.security.oauth2.provider.exchange; + +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.util.Assert; + +/** + * @author Ryan Murfitt + */ +public class DefaultTokenExchangeAuthenticationProvider implements AuthenticationProvider { + + private TokenExchangeService tokenExchangeService; + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Assert.isInstanceOf(TokenExchangeAuthenticationToken.class, authentication, "Only TokenExchangeAuthenticationToken is supported"); + UserDetails user = this.tokenExchangeService.loadUserDetailsFromToken((TokenExchangeAuthenticationToken) authentication); + return createSuccessAuthentication(user, authentication, user); + } + + protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { + UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities())); + result.setDetails(authentication.getDetails()); + return result; + } + + @Override + public boolean supports(Class authentication) { + return TokenExchangeAuthenticationToken.class.isAssignableFrom(authentication); + } + + public void setTokenExchangeService(TokenExchangeService tokenExchangeService) { + this.tokenExchangeService = tokenExchangeService; + } + + public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { + this.authoritiesMapper = authoritiesMapper; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java index bf81a0be5..5dca552db 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java @@ -1,6 +1,7 @@ package org.springframework.security.oauth2.provider.exchange; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.oauth2.provider.ClientDetails; /** * @author Ryan Murfitt @@ -9,11 +10,13 @@ public class TokenExchangeAuthenticationToken extends AbstractAuthenticationToke private final Object principal; private final String principalType; + private final ClientDetails clientDetails; - TokenExchangeAuthenticationToken(Object principal, String principalType) { + TokenExchangeAuthenticationToken(Object principal, String principalType, ClientDetails clientDetails) { super(null); this.principal = principal; this.principalType = principalType; + this.clientDetails = clientDetails; } @Override @@ -23,10 +26,14 @@ public Object getCredentials() { @Override public Object getPrincipal() { - return this.principal; + return principal; } public String getPrincipalType() { - return this.principalType; + return principalType; + } + + public ClientDetails getClientDetails() { + return clientDetails; } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java index 89886c686..30edfb1c1 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java @@ -1,14 +1,15 @@ package org.springframework.security.oauth2.provider.exchange; import org.springframework.security.authentication.AccountStatusException; -import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; /** - * Created on 8/1/17. + * TokenExchangeService deals with validating the supplied subject_token, and returning the user associated with it. * - * @author Ryan Murfitt (ryan.murfitt@console.com.au) + * @author Ryan Murfitt */ public interface TokenExchangeService { - Authentication loadUserAuthFromToken(TokenExchangeAuthenticationToken tokenAuth) throws AccountStatusException, InvalidTokenException; + + UserDetails loadUserDetailsFromToken(TokenExchangeAuthenticationToken tokenAuth) throws AccountStatusException, InvalidTokenException; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java index 03bf7b3cd..d1f240591 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java @@ -17,6 +17,7 @@ package org.springframework.security.oauth2.provider.exchange; import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; @@ -28,23 +29,25 @@ import java.util.Map; /** + * Supports the proposed token-exchange grant flow from draft-ietf-oauth-token-exchange + * * @author Ryan Murfitt */ public class TokenExchangeTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "token-exchange"; - private final TokenExchangeService tokenExchangeService; + private final AuthenticationManager authenticationManager; - public TokenExchangeTokenGranter(TokenExchangeService tokenExchangeService, + public TokenExchangeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { - this(tokenExchangeService, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); + this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); } - protected TokenExchangeTokenGranter(TokenExchangeService tokenExchangeService, AuthorizationServerTokenServices tokenServices, + protected TokenExchangeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { super(tokenServices, clientDetailsService, requestFactory, grantType); - this.tokenExchangeService = tokenExchangeService; + this.authenticationManager = authenticationManager; } @Override @@ -56,11 +59,11 @@ protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, Tok String actorToken = parameters.get("actor_token"); //TODO String actorTokenType = parameters.get("actor_token_type"); //TODO - TokenExchangeAuthenticationToken tokenAuth = new TokenExchangeAuthenticationToken(subjectToken, subjectTokenType); + TokenExchangeAuthenticationToken tokenAuth = new TokenExchangeAuthenticationToken(subjectToken, subjectTokenType, client); tokenAuth.setDetails(parameters); Authentication userAuth; try { - userAuth = tokenExchangeService.loadUserAuthFromToken(tokenAuth); + userAuth = authenticationManager.authenticate(tokenAuth); } catch (AccountStatusException ase) { //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) throw new InvalidGrantException(ase.getMessage()); diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java index 205e56e5a..0f2a8f2bb 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java @@ -5,6 +5,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -43,7 +44,7 @@ public class TokenExchangeTokenGranterTests { private BaseClientDetails client = new BaseClientDetails("foo", "resource", "scope", "token-exchange", "ROLE_USER"); @Mock - private TokenExchangeService tokenExchangeService; + private AuthenticationManager authenticationManager; private DefaultTokenServices providerTokenServices = new DefaultTokenServices(); @@ -61,7 +62,7 @@ public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exceptio @Before public void setup() { - when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); + when(this.authenticationManager.authenticate(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); String clientId = "client"; BaseClientDetails clientDetails = new BaseClientDetails(); @@ -72,7 +73,7 @@ public void setup() { parameters.put("subject_token", "token123"); parameters.put("subject_type", "bearer"); - granter = new TokenExchangeTokenGranter(tokenExchangeService, + granter = new TokenExchangeTokenGranter(authenticationManager, providerTokenServices, clientDetailsService, requestFactory); tokenRequest = requestFactory.createTokenRequest(parameters, clientDetails); @@ -93,20 +94,20 @@ public void testGrantTypeNotSupported() { @Test(expected = InvalidGrantException.class) public void testInvalidToken() { - when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenThrow(new InvalidTokenException("invalid token")); + when(this.authenticationManager.authenticate(any(TokenExchangeAuthenticationToken.class))).thenThrow(new InvalidTokenException("invalid token")); granter.grant("token-exchange", tokenRequest); } @Test(expected = InvalidGrantException.class) public void testAccountLocked() { - when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenThrow(new LockedException("locked")); + when(this.authenticationManager.authenticate(any(TokenExchangeAuthenticationToken.class))).thenThrow(new LockedException("locked")); granter.grant("token-exchange", tokenRequest); } @Test(expected = InvalidGrantException.class) public void testUnauthenticated() { validUser = new UsernamePasswordAuthenticationToken("foo", "bar"); - when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); + when(this.authenticationManager.authenticate(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); granter.grant("token-exchange", tokenRequest); } } \ No newline at end of file From 7351ba66c518ba6fdda91add7582464b899cfdb1 Mon Sep 17 00:00:00 2001 From: Ryan Murfitt Date: Sun, 8 Jan 2017 14:11:13 +1000 Subject: [PATCH 03/15] Initial commit enabling token-exchange flow for the auth server --- ...uthorizationServerEndpointsConfigurer.java | 48 +++----- .../TokenExchangeAuthenticationToken.java | 32 +++++ .../exchange/TokenExchangeService.java | 14 +++ .../exchange/TokenExchangeTokenGranter.java | 78 ++++++++++++ .../TokenExchangeTokenGranterTests.java | 112 ++++++++++++++++++ 5 files changed, 252 insertions(+), 32 deletions(-) create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java index dc780e878..0dd9bc5dd 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java @@ -15,15 +15,6 @@ */ package org.springframework.security.oauth2.config.annotation.web.configurers; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.http.HttpMethod; @@ -36,17 +27,8 @@ import org.springframework.security.oauth2.common.util.ProxyCreator; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.CompositeTokenGranter; -import org.springframework.security.oauth2.provider.OAuth2RequestFactory; -import org.springframework.security.oauth2.provider.OAuth2RequestValidator; -import org.springframework.security.oauth2.provider.TokenGranter; -import org.springframework.security.oauth2.provider.TokenRequest; -import org.springframework.security.oauth2.provider.approval.ApprovalStore; -import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler; -import org.springframework.security.oauth2.provider.approval.TokenApprovalStore; -import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler; -import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.approval.*; import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter; import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; @@ -55,19 +37,14 @@ import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.exchange.TokenExchangeService; +import org.springframework.security.oauth2.provider.exchange.TokenExchangeTokenGranter; import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter; import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter; import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator; -import org.springframework.security.oauth2.provider.token.AccessTokenConverter; -import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; -import org.springframework.security.oauth2.provider.token.ConsumerTokenServices; -import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; -import org.springframework.security.oauth2.provider.token.TokenEnhancer; -import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.*; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; @@ -76,9 +53,11 @@ import org.springframework.web.context.request.WebRequestInterceptor; import org.springframework.web.servlet.HandlerInterceptor; +import java.util.*; + /** * Configure the properties and enhanced functionality of the Authorization Server endpoints. - * + * * @author Rob Winch * @author Dave Syer * @since 2.0 @@ -111,6 +90,8 @@ public final class AuthorizationServerEndpointsConfigurer { private AuthenticationManager authenticationManager; + private TokenExchangeService tokenExchangeService; + private ClientDetailsService clientDetailsService; private String prefix; @@ -242,7 +223,7 @@ public AuthorizationServerEndpointsConfigurer approvalStore(ApprovalStore approv * Explicitly disable the approval store, even if one would normally be added automatically (usually when JWT is not * used). Without an approval store the user can only be asked to approve or deny a grant without any more granular * decisions. - * + * * @return this for fluent builder */ public AuthorizationServerEndpointsConfigurer approvalStoreDisabled() { @@ -277,7 +258,7 @@ public AuthorizationServerEndpointsConfigurer exceptionTranslator(WebResponseExc /** * The AuthenticationManager for the password grant. - * + * * @param authenticationManager an AuthenticationManager, fully initialized * @return this for a fluent style */ @@ -545,7 +526,10 @@ private List getDefaultTokenGranters() { tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory)); } - return tokenGranters; + if (tokenExchangeService != null) { + tokenGranters.add(new TokenExchangeTokenGranter(tokenExchangeService, tokenServices, clientDetails, requestFactory)); + } + return tokenGranters; } private TokenGranter tokenGranter() { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java new file mode 100644 index 000000000..bf81a0be5 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java @@ -0,0 +1,32 @@ +package org.springframework.security.oauth2.provider.exchange; + +import org.springframework.security.authentication.AbstractAuthenticationToken; + +/** + * @author Ryan Murfitt + */ +public class TokenExchangeAuthenticationToken extends AbstractAuthenticationToken { + + private final Object principal; + private final String principalType; + + TokenExchangeAuthenticationToken(Object principal, String principalType) { + super(null); + this.principal = principal; + this.principalType = principalType; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getPrincipal() { + return this.principal; + } + + public String getPrincipalType() { + return this.principalType; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java new file mode 100644 index 000000000..89886c686 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java @@ -0,0 +1,14 @@ +package org.springframework.security.oauth2.provider.exchange; + +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; + +/** + * Created on 8/1/17. + * + * @author Ryan Murfitt (ryan.murfitt@console.com.au) + */ +public interface TokenExchangeService { + Authentication loadUserAuthFromToken(TokenExchangeAuthenticationToken tokenAuth) throws AccountStatusException, InvalidTokenException; +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java new file mode 100644 index 000000000..03bf7b3cd --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.provider.exchange; + +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author Ryan Murfitt + */ +public class TokenExchangeTokenGranter extends AbstractTokenGranter { + + private static final String GRANT_TYPE = "token-exchange"; + + private final TokenExchangeService tokenExchangeService; + + public TokenExchangeTokenGranter(TokenExchangeService tokenExchangeService, + AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { + this(tokenExchangeService, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); + } + + protected TokenExchangeTokenGranter(TokenExchangeService tokenExchangeService, AuthorizationServerTokenServices tokenServices, + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { + super(tokenServices, clientDetailsService, requestFactory, grantType); + this.tokenExchangeService = tokenExchangeService; + } + + @Override + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { + + Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters()); + String subjectToken = parameters.get("subject_token"); + String subjectTokenType = parameters.get("subject_token_type"); + String actorToken = parameters.get("actor_token"); //TODO + String actorTokenType = parameters.get("actor_token_type"); //TODO + + TokenExchangeAuthenticationToken tokenAuth = new TokenExchangeAuthenticationToken(subjectToken, subjectTokenType); + tokenAuth.setDetails(parameters); + Authentication userAuth; + try { + userAuth = tokenExchangeService.loadUserAuthFromToken(tokenAuth); + } catch (AccountStatusException ase) { + //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) + throw new InvalidGrantException(ase.getMessage()); + } catch (InvalidTokenException e) { + // If the supplied subject token is invalid + throw new InvalidGrantException(e.getMessage()); + } + if (userAuth == null || !userAuth.isAuthenticated()) { + throw new InvalidGrantException("Could not authenticate user"); + } + + OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); + return new OAuth2Authentication(storedOAuth2Request, userAuth); + } +} diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java new file mode 100644 index 000000000..205e56e5a --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java @@ -0,0 +1,112 @@ +package org.springframework.security.oauth2.provider.exchange; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; + +/** + * Created on 8/1/17. + * + * @author Ryan Murfitt (ryan.murfitt@console.com.au) + */ +@RunWith(MockitoJUnitRunner.class) +public class TokenExchangeTokenGranterTests { + + private Authentication validUser = new UsernamePasswordAuthenticationToken("foo", "bar", + Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))); + + private BaseClientDetails client = new BaseClientDetails("foo", "resource", "scope", "token-exchange", "ROLE_USER"); + + @Mock + private TokenExchangeService tokenExchangeService; + + private DefaultTokenServices providerTokenServices = new DefaultTokenServices(); + + private ClientDetailsService clientDetailsService = new ClientDetailsService() { + public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception { + return client; + } + }; + + private OAuth2RequestFactory requestFactory = new DefaultOAuth2RequestFactory(clientDetailsService); + + private TokenExchangeTokenGranter granter; + + private TokenRequest tokenRequest; + + @Before + public void setup() { + when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); + + String clientId = "client"; + BaseClientDetails clientDetails = new BaseClientDetails(); + clientDetails.setClientId(clientId); + + providerTokenServices.setTokenStore(new InMemoryTokenStore()); + Map parameters = new HashMap(); + parameters.put("subject_token", "token123"); + parameters.put("subject_type", "bearer"); + + granter = new TokenExchangeTokenGranter(tokenExchangeService, + providerTokenServices, clientDetailsService, requestFactory); + + tokenRequest = requestFactory.createTokenRequest(parameters, clientDetails); + } + + @Test + public void testSuccessfulGrant() { + OAuth2AccessToken token = granter.grant("token-exchange", tokenRequest); + OAuth2Authentication authentication = providerTokenServices.loadAuthentication(token.getValue()); + assertTrue(authentication.isAuthenticated()); + } + + @Test(expected = InvalidClientException.class) + public void testGrantTypeNotSupported() { + client.setAuthorizedGrantTypes(Collections.singleton("client_credentials")); + granter.grant("token-exchange", tokenRequest); + } + + @Test(expected = InvalidGrantException.class) + public void testInvalidToken() { + when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenThrow(new InvalidTokenException("invalid token")); + granter.grant("token-exchange", tokenRequest); + } + + @Test(expected = InvalidGrantException.class) + public void testAccountLocked() { + when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenThrow(new LockedException("locked")); + granter.grant("token-exchange", tokenRequest); + } + + @Test(expected = InvalidGrantException.class) + public void testUnauthenticated() { + validUser = new UsernamePasswordAuthenticationToken("foo", "bar"); + when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); + granter.grant("token-exchange", tokenRequest); + } +} \ No newline at end of file From f023b4bc5f666196202d3d983e95e5f0cf2f0f3b Mon Sep 17 00:00:00 2001 From: Ryan Murfitt Date: Sun, 8 Jan 2017 23:18:35 +1000 Subject: [PATCH 04/15] Refactor leveraging the authentication providers --- ...uthorizationServerEndpointsConfigurer.java | 7 +-- ...ltTokenExchangeAuthenticationProvider.java | 45 +++++++++++++++++++ .../TokenExchangeAuthenticationToken.java | 13 ++++-- .../exchange/TokenExchangeService.java | 9 ++-- .../exchange/TokenExchangeTokenGranter.java | 17 ++++--- .../TokenExchangeTokenGranterTests.java | 13 +++--- 6 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java index 0dd9bc5dd..0529e0849 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configurers/AuthorizationServerEndpointsConfigurer.java @@ -37,7 +37,6 @@ import org.springframework.security.oauth2.provider.endpoint.FrameworkEndpointHandlerMapping; import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; -import org.springframework.security.oauth2.provider.exchange.TokenExchangeService; import org.springframework.security.oauth2.provider.exchange.TokenExchangeTokenGranter; import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter; import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter; @@ -90,8 +89,6 @@ public final class AuthorizationServerEndpointsConfigurer { private AuthenticationManager authenticationManager; - private TokenExchangeService tokenExchangeService; - private ClientDetailsService clientDetailsService; private String prefix; @@ -525,10 +522,8 @@ private List getDefaultTokenGranters() { if (authenticationManager != null) { tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory)); + tokenGranters.add(new TokenExchangeTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory)); } - if (tokenExchangeService != null) { - tokenGranters.add(new TokenExchangeTokenGranter(tokenExchangeService, tokenServices, clientDetails, requestFactory)); - } return tokenGranters; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java new file mode 100644 index 000000000..87df85583 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java @@ -0,0 +1,45 @@ +package org.springframework.security.oauth2.provider.exchange; + +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.util.Assert; + +/** + * @author Ryan Murfitt + */ +public class DefaultTokenExchangeAuthenticationProvider implements AuthenticationProvider { + + private TokenExchangeService tokenExchangeService; + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Assert.isInstanceOf(TokenExchangeAuthenticationToken.class, authentication, "Only TokenExchangeAuthenticationToken is supported"); + UserDetails user = this.tokenExchangeService.loadUserDetailsFromToken((TokenExchangeAuthenticationToken) authentication); + return createSuccessAuthentication(user, authentication, user); + } + + protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { + UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities())); + result.setDetails(authentication.getDetails()); + return result; + } + + @Override + public boolean supports(Class authentication) { + return TokenExchangeAuthenticationToken.class.isAssignableFrom(authentication); + } + + public void setTokenExchangeService(TokenExchangeService tokenExchangeService) { + this.tokenExchangeService = tokenExchangeService; + } + + public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { + this.authoritiesMapper = authoritiesMapper; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java index bf81a0be5..5dca552db 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java @@ -1,6 +1,7 @@ package org.springframework.security.oauth2.provider.exchange; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.oauth2.provider.ClientDetails; /** * @author Ryan Murfitt @@ -9,11 +10,13 @@ public class TokenExchangeAuthenticationToken extends AbstractAuthenticationToke private final Object principal; private final String principalType; + private final ClientDetails clientDetails; - TokenExchangeAuthenticationToken(Object principal, String principalType) { + TokenExchangeAuthenticationToken(Object principal, String principalType, ClientDetails clientDetails) { super(null); this.principal = principal; this.principalType = principalType; + this.clientDetails = clientDetails; } @Override @@ -23,10 +26,14 @@ public Object getCredentials() { @Override public Object getPrincipal() { - return this.principal; + return principal; } public String getPrincipalType() { - return this.principalType; + return principalType; + } + + public ClientDetails getClientDetails() { + return clientDetails; } } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java index 89886c686..30edfb1c1 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java @@ -1,14 +1,15 @@ package org.springframework.security.oauth2.provider.exchange; import org.springframework.security.authentication.AccountStatusException; -import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; /** - * Created on 8/1/17. + * TokenExchangeService deals with validating the supplied subject_token, and returning the user associated with it. * - * @author Ryan Murfitt (ryan.murfitt@console.com.au) + * @author Ryan Murfitt */ public interface TokenExchangeService { - Authentication loadUserAuthFromToken(TokenExchangeAuthenticationToken tokenAuth) throws AccountStatusException, InvalidTokenException; + + UserDetails loadUserDetailsFromToken(TokenExchangeAuthenticationToken tokenAuth) throws AccountStatusException, InvalidTokenException; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java index 03bf7b3cd..d1f240591 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java @@ -17,6 +17,7 @@ package org.springframework.security.oauth2.provider.exchange; import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; @@ -28,23 +29,25 @@ import java.util.Map; /** + * Supports the proposed token-exchange grant flow from draft-ietf-oauth-token-exchange + * * @author Ryan Murfitt */ public class TokenExchangeTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "token-exchange"; - private final TokenExchangeService tokenExchangeService; + private final AuthenticationManager authenticationManager; - public TokenExchangeTokenGranter(TokenExchangeService tokenExchangeService, + public TokenExchangeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { - this(tokenExchangeService, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); + this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); } - protected TokenExchangeTokenGranter(TokenExchangeService tokenExchangeService, AuthorizationServerTokenServices tokenServices, + protected TokenExchangeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { super(tokenServices, clientDetailsService, requestFactory, grantType); - this.tokenExchangeService = tokenExchangeService; + this.authenticationManager = authenticationManager; } @Override @@ -56,11 +59,11 @@ protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, Tok String actorToken = parameters.get("actor_token"); //TODO String actorTokenType = parameters.get("actor_token_type"); //TODO - TokenExchangeAuthenticationToken tokenAuth = new TokenExchangeAuthenticationToken(subjectToken, subjectTokenType); + TokenExchangeAuthenticationToken tokenAuth = new TokenExchangeAuthenticationToken(subjectToken, subjectTokenType, client); tokenAuth.setDetails(parameters); Authentication userAuth; try { - userAuth = tokenExchangeService.loadUserAuthFromToken(tokenAuth); + userAuth = authenticationManager.authenticate(tokenAuth); } catch (AccountStatusException ase) { //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) throw new InvalidGrantException(ase.getMessage()); diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java index 205e56e5a..0f2a8f2bb 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java @@ -5,6 +5,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -43,7 +44,7 @@ public class TokenExchangeTokenGranterTests { private BaseClientDetails client = new BaseClientDetails("foo", "resource", "scope", "token-exchange", "ROLE_USER"); @Mock - private TokenExchangeService tokenExchangeService; + private AuthenticationManager authenticationManager; private DefaultTokenServices providerTokenServices = new DefaultTokenServices(); @@ -61,7 +62,7 @@ public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exceptio @Before public void setup() { - when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); + when(this.authenticationManager.authenticate(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); String clientId = "client"; BaseClientDetails clientDetails = new BaseClientDetails(); @@ -72,7 +73,7 @@ public void setup() { parameters.put("subject_token", "token123"); parameters.put("subject_type", "bearer"); - granter = new TokenExchangeTokenGranter(tokenExchangeService, + granter = new TokenExchangeTokenGranter(authenticationManager, providerTokenServices, clientDetailsService, requestFactory); tokenRequest = requestFactory.createTokenRequest(parameters, clientDetails); @@ -93,20 +94,20 @@ public void testGrantTypeNotSupported() { @Test(expected = InvalidGrantException.class) public void testInvalidToken() { - when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenThrow(new InvalidTokenException("invalid token")); + when(this.authenticationManager.authenticate(any(TokenExchangeAuthenticationToken.class))).thenThrow(new InvalidTokenException("invalid token")); granter.grant("token-exchange", tokenRequest); } @Test(expected = InvalidGrantException.class) public void testAccountLocked() { - when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenThrow(new LockedException("locked")); + when(this.authenticationManager.authenticate(any(TokenExchangeAuthenticationToken.class))).thenThrow(new LockedException("locked")); granter.grant("token-exchange", tokenRequest); } @Test(expected = InvalidGrantException.class) public void testUnauthenticated() { validUser = new UsernamePasswordAuthenticationToken("foo", "bar"); - when(this.tokenExchangeService.loadUserAuthFromToken(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); + when(this.authenticationManager.authenticate(any(TokenExchangeAuthenticationToken.class))).thenReturn(this.validUser); granter.grant("token-exchange", tokenRequest); } } \ No newline at end of file From da68fd89322a65bc7008deeb46954cecd2016d7a Mon Sep 17 00:00:00 2001 From: Ryan Murfitt Date: Sun, 22 Jan 2017 18:21:51 +1000 Subject: [PATCH 05/15] Basic cleanup and test cases ready for PR --- ...thorizationServerBeanDefinitionParser.java | 14 +++- ...ltTokenExchangeAuthenticationProvider.java | 11 +-- .../TokenExchangeAuthenticationToken.java | 30 ++++++-- .../exchange/TokenExchangeService.java | 3 +- .../exchange/TokenExchangeTokenGranter.java | 10 +-- ...enExchangeAuthenticationProviderTests.java | 75 +++++++++++++++++++ .../TokenExchangeTokenGranterTests.java | 4 +- 7 files changed, 122 insertions(+), 25 deletions(-) create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProviderTests.java diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/AuthorizationServerBeanDefinitionParser.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/AuthorizationServerBeanDefinitionParser.java index 391d74a8c..b0f6216aa 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/AuthorizationServerBeanDefinitionParser.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/xml/AuthorizationServerBeanDefinitionParser.java @@ -28,6 +28,7 @@ import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter; import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices; import org.springframework.security.oauth2.provider.endpoint.*; +import org.springframework.security.oauth2.provider.exchange.TokenExchangeTokenGranter; import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter; import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter; import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter; @@ -209,13 +210,13 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, "password"); if (clientPasswordElement != null && !"true" .equalsIgnoreCase(clientPasswordElement.getAttribute("disabled"))) { - BeanDefinitionBuilder clientPasswordTokenGranter = BeanDefinitionBuilder - .rootBeanDefinition(ResourceOwnerPasswordTokenGranter.class); String authenticationManagerRef = clientPasswordElement .getAttribute("authentication-manager-ref"); if (!StringUtils.hasText(authenticationManagerRef)) { authenticationManagerRef = BeanIds.AUTHENTICATION_MANAGER; } + BeanDefinitionBuilder clientPasswordTokenGranter = BeanDefinitionBuilder + .rootBeanDefinition(ResourceOwnerPasswordTokenGranter.class); clientPasswordTokenGranter .addConstructorArgReference(authenticationManagerRef); clientPasswordTokenGranter.addConstructorArgReference(tokenServicesRef); @@ -223,6 +224,15 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, clientPasswordTokenGranter .addConstructorArgReference(oAuth2RequestFactoryRef); tokenGranters.add(clientPasswordTokenGranter.getBeanDefinition()); + BeanDefinitionBuilder tokenExchangeTokenGranter = BeanDefinitionBuilder + .rootBeanDefinition(TokenExchangeTokenGranter.class); + tokenExchangeTokenGranter + .addConstructorArgReference(authenticationManagerRef); + tokenExchangeTokenGranter.addConstructorArgReference(tokenServicesRef); + tokenExchangeTokenGranter.addConstructorArgReference(clientDetailsRef); + tokenExchangeTokenGranter + .addConstructorArgReference(oAuth2RequestFactoryRef); + tokenGranters.add(tokenExchangeTokenGranter.getBeanDefinition()); } List customGrantElements = DomUtils .getChildElementsByTagName(element, "custom-grant"); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java index 87df85583..37ba8cfd1 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProvider.java @@ -1,7 +1,6 @@ package org.springframework.security.oauth2.provider.exchange; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; @@ -10,6 +9,8 @@ import org.springframework.util.Assert; /** + * {@link AuthenticationProvider} that supports {@link TokenExchangeAuthenticationToken}. + * * @author Ryan Murfitt */ public class DefaultTokenExchangeAuthenticationProvider implements AuthenticationProvider { @@ -21,12 +22,12 @@ public class DefaultTokenExchangeAuthenticationProvider implements Authenticatio public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(TokenExchangeAuthenticationToken.class, authentication, "Only TokenExchangeAuthenticationToken is supported"); UserDetails user = this.tokenExchangeService.loadUserDetailsFromToken((TokenExchangeAuthenticationToken) authentication); - return createSuccessAuthentication(user, authentication, user); + return createSuccessAuthentication(user, (TokenExchangeAuthenticationToken) authentication); } - protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { - UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities())); - result.setDetails(authentication.getDetails()); + private Authentication createSuccessAuthentication(UserDetails user, TokenExchangeAuthenticationToken token) { + TokenExchangeAuthenticationToken result = new TokenExchangeAuthenticationToken(user, token.getPrincipal(), token.getClientDetails(), this.authoritiesMapper.mapAuthorities(user.getAuthorities())); + result.setDetails(token.getDetails()); return result; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java index 5dca552db..300fe0631 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeAuthenticationToken.java @@ -1,27 +1,45 @@ package org.springframework.security.oauth2.provider.exchange; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.provider.ClientDetails; +import java.util.Collection; + /** + * Token that represents a token-exchange authentication request. + * + * Similar to {@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken} where the principal + * represents the 'subject_token', but once authenticated, the principal represents the user, and the credentials represents + * the 'subject_token'. + * * @author Ryan Murfitt */ public class TokenExchangeAuthenticationToken extends AbstractAuthenticationToken { private final Object principal; - private final String principalType; private final ClientDetails clientDetails; + private final Object credentials; - TokenExchangeAuthenticationToken(Object principal, String principalType, ClientDetails clientDetails) { + TokenExchangeAuthenticationToken(Object principal, ClientDetails clientDetails) { super(null); this.principal = principal; - this.principalType = principalType; + this.credentials = null; this.clientDetails = clientDetails; + this.setAuthenticated(false); + } + + TokenExchangeAuthenticationToken(Object principal, Object credentials, ClientDetails clientDetails, Collection authorities) { + super(authorities); + this.principal = principal; + this.credentials = credentials; + this.clientDetails = clientDetails; + this.setAuthenticated(true); } @Override public Object getCredentials() { - return null; + return credentials; } @Override @@ -29,10 +47,6 @@ public Object getPrincipal() { return principal; } - public String getPrincipalType() { - return principalType; - } - public ClientDetails getClientDetails() { return clientDetails; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java index 30edfb1c1..16dda8707 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeService.java @@ -5,7 +5,8 @@ import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; /** - * TokenExchangeService deals with validating the supplied subject_token, and returning the user associated with it. + * {@link TokenExchangeService} deals with validating the supplied {@link TokenExchangeAuthenticationToken} which details the + * token-exchange request, and returning the user associated with it. * * @author Ryan Murfitt */ diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java index d1f240591..66e951391 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranter.java @@ -29,13 +29,14 @@ import java.util.Map; /** - * Supports the proposed token-exchange grant flow from draft-ietf-oauth-token-exchange + * Supports the proposed token-exchange grant flow from draft-ietf-oauth-token-exchange * * @author Ryan Murfitt */ public class TokenExchangeTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "token-exchange"; + private static final String SUBJECT_TOKEN_CLAIM = "subject_token"; private final AuthenticationManager authenticationManager; @@ -54,12 +55,9 @@ protected TokenExchangeTokenGranter(AuthenticationManager authenticationManager, protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters()); - String subjectToken = parameters.get("subject_token"); - String subjectTokenType = parameters.get("subject_token_type"); - String actorToken = parameters.get("actor_token"); //TODO - String actorTokenType = parameters.get("actor_token_type"); //TODO + String subjectToken = parameters.get(SUBJECT_TOKEN_CLAIM); - TokenExchangeAuthenticationToken tokenAuth = new TokenExchangeAuthenticationToken(subjectToken, subjectTokenType, client); + TokenExchangeAuthenticationToken tokenAuth = new TokenExchangeAuthenticationToken(subjectToken, client); tokenAuth.setDetails(parameters); Authentication userAuth; try { diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProviderTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProviderTests.java new file mode 100644 index 000000000..26f572ead --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/DefaultTokenExchangeAuthenticationProviderTests.java @@ -0,0 +1,75 @@ +package org.springframework.security.oauth2.provider.exchange; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collections; + +import static org.mockito.Mockito.*; + +import static org.junit.Assert.*; + +/** + * @author Ryan Murfitt + */ +@RunWith(MockitoJUnitRunner.class) +public class DefaultTokenExchangeAuthenticationProviderTests { + + @Mock + private TokenExchangeService tokenExchangeService; + + @InjectMocks + private DefaultTokenExchangeAuthenticationProvider provider = new DefaultTokenExchangeAuthenticationProvider(); + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @Test + public void authenticateIncorrectTokenType() { + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( + "rod", "KOala"); + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Only TokenExchangeAuthenticationToken is supported"); + + provider.authenticate(token); + + verifyZeroInteractions(this.tokenExchangeService); + } + + @Test + public void authenticateSuccess() { + TokenExchangeAuthenticationToken token = new TokenExchangeAuthenticationToken( + "token", null); + UserDetails user = new User("bob", "password", Collections.emptyList()); + + when(tokenExchangeService.loadUserDetailsFromToken(token)).thenReturn(user); + + Authentication result = provider.authenticate(token); + + assertTrue(result instanceof TokenExchangeAuthenticationToken); + assertTrue(result.isAuthenticated()); + assertSame(result.getPrincipal(), user); + assertSame(result.getCredentials(), token.getPrincipal()); + } + + @Test + public void supportsIncorrectTokenType() { + assertFalse(provider.supports(UsernamePasswordAuthenticationToken.class)); + } + + @Test + public void supportsSuccess() { + assertTrue(provider.supports(TokenExchangeAuthenticationToken.class)); + } + +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java index 0f2a8f2bb..3ff11ad04 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/exchange/TokenExchangeTokenGranterTests.java @@ -31,9 +31,7 @@ import static org.mockito.Mockito.when; /** - * Created on 8/1/17. - * - * @author Ryan Murfitt (ryan.murfitt@console.com.au) + * @author Ryan Murfitt */ @RunWith(MockitoJUnitRunner.class) public class TokenExchangeTokenGranterTests { From 7a6beee1c43e28ae0b4115fdbb74d31efc7ed14c Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 13 Feb 2017 15:16:13 -0500 Subject: [PATCH 06/15] Add TokenStore supporting Jwk verification Fixes gh-271 --- .../token/store/jwk/JwkAttributes.java | 35 ++++ .../token/store/jwk/JwkDefinition.java | 168 ++++++++++++++++++ .../token/store/jwk/JwkDefinitionSource.java | 117 ++++++++++++ .../token/store/jwk/JwkException.java | 42 +++++ .../token/store/jwk/JwkSetConverter.java | 144 +++++++++++++++ .../token/store/jwk/JwkTokenStore.java | 109 ++++++++++++ .../JwkVerifyingJwtAccessTokenConverter.java | 91 ++++++++++ .../token/store/jwk/JwtHeaderConverter.java | 68 +++++++ .../token/store/jwk/RSAJwkDefinition.java | 43 +++++ 9 files changed, 817 insertions(+) create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java new file mode 100644 index 000000000..72769bbc0 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +/** + * @author Joe Grandja + */ +final class JwkAttributes { + static final String KEY_ID = "kid"; + + static final String KEY_TYPE = "kty"; + + static final String ALGORITHM = "alg"; + + static final String PUBLIC_KEY_USE = "use"; + + static final String RSA_PUBLIC_KEY_MODULUS = "n"; + + static final String RSA_PUBLIC_KEY_EXPONENT = "e"; + + static final String KEYS = "keys"; +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java new file mode 100644 index 000000000..93caa5a5c --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java @@ -0,0 +1,168 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +/** + * @author Joe Grandja + */ +abstract class JwkDefinition { + private final String keyId; + private final KeyType keyType; + private final PublicKeyUse publicKeyUse; + private final CryptoAlgorithm algorithm; + + protected JwkDefinition(String keyId, + KeyType keyType, + PublicKeyUse publicKeyUse, + CryptoAlgorithm algorithm) { + this.keyId = keyId; + this.keyType = keyType; + this.publicKeyUse = publicKeyUse; + this.algorithm = algorithm; + } + + String getKeyId() { + return this.keyId; + } + + KeyType getKeyType() { + return this.keyType; + } + + PublicKeyUse getPublicKeyUse() { + return this.publicKeyUse; + } + + CryptoAlgorithm getAlgorithm() { + return this.algorithm; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + + JwkDefinition that = (JwkDefinition) obj; + if (!this.getKeyId().equals(that.getKeyId())) { + return false; + } + + return this.getKeyType().equals(that.getKeyType()); + } + + @Override + public int hashCode() { + int result = this.getKeyId().hashCode(); + result = 31 * result + this.getKeyType().hashCode(); + return result; + } + + enum KeyType { + RSA("RSA"), + EC("EC"), + OCT("oct"); + + private final String value; + + KeyType(String value) { + this.value = value; + } + + String value() { + return this.value; + } + + static KeyType fromValue(String value) { + KeyType result = null; + for (KeyType keyType : values()) { + if (keyType.value().equals(value)) { + result = keyType; + break; + } + } + return result; + } + } + + enum PublicKeyUse { + SIG("sig"), + ENC("enc"); + + private final String value; + + PublicKeyUse(String value) { + this.value = value; + } + + String value() { + return this.value; + } + + static PublicKeyUse fromValue(String value) { + PublicKeyUse result = null; + for (PublicKeyUse publicKeyUse : values()) { + if (publicKeyUse.value().equals(value)) { + result = publicKeyUse; + break; + } + } + return result; + } + } + + enum CryptoAlgorithm { + RS256("SHA256withRSA", "RS256", "RSASSA-PKCS1-v1_5 using SHA-256"), + RS384("SHA384withRSA", "RS384", "RSASSA-PKCS1-v1_5 using SHA-384"), + RS512("SHA512withRSA", "RS512", "RSASSA-PKCS1-v1_5 using SHA-512"); + + private final String standardName; // JCA Standard Name + private final String headerParamValue; + private final String description; + + CryptoAlgorithm(String standardName, String headerParamValue, String description) { + this.standardName = standardName; + this.headerParamValue = headerParamValue; + this.description = description; + } + + String standardName() { + return this.standardName; + } + + String headerParamValue() { + return this.headerParamValue; + } + + String description() { + return this.description; + } + + static CryptoAlgorithm fromStandardName(String standardName) { + CryptoAlgorithm result = null; + for (CryptoAlgorithm algorithm : values()) { + if (algorithm.standardName().equals(standardName)) { + result = algorithm; + break; + } + } + return result; + } + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java new file mode 100644 index 000000000..a9f241f92 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java @@ -0,0 +1,117 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.springframework.security.jwt.codec.Codecs; +import org.springframework.security.jwt.crypto.sign.RsaVerifier; +import org.springframework.security.jwt.crypto.sign.SignatureVerifier; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.KeyFactory; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAPublicKeySpec; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author Joe Grandja + */ +class JwkDefinitionSource { + private final URL jwkSetUrl; + private final JwkSetConverter jwkSetConverter = new JwkSetConverter(); + private final AtomicReference> jwkDefinitions = + new AtomicReference>(new HashMap()); + + JwkDefinitionSource(String jwkSetUrl) { + try { + this.jwkSetUrl = new URL(jwkSetUrl); + } catch (MalformedURLException ex) { + throw new IllegalArgumentException("Invalid JWK Set URL: " + ex.getMessage(), ex); + } + } + + JwkDefinition getDefinition(String keyId) { + JwkDefinition result = null; + for (JwkDefinition jwkDefinition : this.jwkDefinitions.get().keySet()) { + if (jwkDefinition.getKeyId().equals(keyId)) { + result = jwkDefinition; + break; + } + } + return result; + } + + JwkDefinition getDefinitionRefreshIfNecessary(String keyId) { + JwkDefinition result = this.getDefinition(keyId); + if (result != null) { + return result; + } + this.refreshJwkDefinitions(); + return this.getDefinition(keyId); + } + + SignatureVerifier getVerifier(String keyId) { + SignatureVerifier result = null; + JwkDefinition jwkDefinition = this.getDefinitionRefreshIfNecessary(keyId); + if (jwkDefinition != null) { + result = this.jwkDefinitions.get().get(jwkDefinition); + } + return result; + } + + private void refreshJwkDefinitions() { + Set jwkDefinitionSet; + try { + jwkDefinitionSet = this.jwkSetConverter.convert(this.jwkSetUrl.openStream()); + } catch (IOException ex) { + throw new JwkException("An I/O error occurred while refreshing the JWK Set: " + ex.getMessage(), ex); + } + + Map refreshedJwkDefinitions = new LinkedHashMap(); + + for (JwkDefinition jwkDefinition : jwkDefinitionSet) { + if (JwkDefinition.KeyType.RSA.equals(jwkDefinition.getKeyType())) { + refreshedJwkDefinitions.put(jwkDefinition, this.createRSAVerifier((RSAJwkDefinition)jwkDefinition)); + } + } + + this.jwkDefinitions.set(refreshedJwkDefinitions); + } + + private RsaVerifier createRSAVerifier(RSAJwkDefinition rsaDefinition) { + RsaVerifier result; + try { + BigInteger modulus = new BigInteger(Codecs.b64UrlDecode(rsaDefinition.getModulus())); + BigInteger exponent = new BigInteger(Codecs.b64UrlDecode(rsaDefinition.getExponent())); + + RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") + .generatePublic(new RSAPublicKeySpec(modulus, exponent)); + + result = new RsaVerifier(rsaPublicKey, rsaDefinition.getAlgorithm().standardName()); + + } catch (Exception ex) { + throw new JwkException("An error occurred while creating a RSA Public Key Verifier for \"" + + rsaDefinition.getKeyId() + "\" : " + ex.getMessage(), ex); + } + return result; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java new file mode 100644 index 000000000..f9a5e0032 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; + +/** + * @author Joe Grandja + */ +public class JwkException extends OAuth2Exception { + + public JwkException(String message) { + super(message); + } + + public JwkException(String message, Throwable cause) { + super(message, cause); + } + + @Override + public String getOAuth2ErrorCode() { + return "server_error"; + } + + @Override + public int getHttpErrorCode() { + return 500; + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java new file mode 100644 index 000000000..1b6964adb --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java @@ -0,0 +1,144 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import org.springframework.core.convert.converter.Converter; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.*; + + +/** + * @author Joe Grandja + */ +class JwkSetConverter implements Converter> { + private final JsonFactory factory = new JsonFactory(); + + @Override + public Set convert(InputStream jwkSetSource) { + Set jwkDefinitions; + JsonParser parser = null; + + try { + parser = this.factory.createParser(jwkSetSource); + + if (parser.nextToken() != JsonToken.START_OBJECT) { + throw new JwkException("Invalid JWK Set Object."); + } + if (parser.nextToken() != JsonToken.FIELD_NAME) { + throw new JwkException("Invalid JWK Set Object."); + } + if (!parser.getCurrentName().equals(KEYS)) { + throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have a \"" + KEYS + "\" attribute."); + } + if (parser.nextToken() != JsonToken.START_ARRAY) { + throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have an array of JWK(s)."); + } + + jwkDefinitions = new LinkedHashSet(); + Map attributes = new HashMap(); + + while (parser.nextToken() == JsonToken.START_OBJECT) { + while (parser.nextToken() == JsonToken.FIELD_NAME) { + String attributeName = parser.getCurrentName(); + parser.nextToken(); + String attributeValue = parser.getValueAsString(); + attributes.put(attributeName, attributeValue); + } + JwkDefinition jwkDefinition = this.createJwkDefinition(attributes); + if (!jwkDefinitions.add(jwkDefinition)) { + throw new JwkException("Duplicate JWK found in Set: " + + jwkDefinition.getKeyId() + " (" + KEY_ID + ")"); + } + attributes.clear(); + } + + } catch (IOException ex) { + throw new JwkException("An I/O error occurred while reading the JWK Set: " + ex.getMessage(), ex); + } finally { + try { + if (parser != null) parser.close(); + } catch (IOException ex) { } + } + + return jwkDefinitions; + } + + private JwkDefinition createJwkDefinition(Map attributes) { + JwkDefinition.KeyType keyType = + JwkDefinition.KeyType.fromValue(attributes.get(KEY_TYPE)); + + if (!JwkDefinition.KeyType.RSA.equals(keyType)) { + throw new JwkException((keyType != null ? keyType.value() : "unknown") + + " (" + KEY_TYPE + ") is currently not supported."); + } + + return this.createRSAJwkDefinition(attributes); + } + + private JwkDefinition createRSAJwkDefinition(Map attributes) { + // kid + String keyId = attributes.get(KEY_ID); + if (!StringUtils.hasText(keyId)) { + throw new JwkException("\"" + KEY_ID + "\" is a required attribute for a JWK."); + } + + // use + JwkDefinition.PublicKeyUse publicKeyUse = + JwkDefinition.PublicKeyUse.fromValue(attributes.get(PUBLIC_KEY_USE)); + if (!JwkDefinition.PublicKeyUse.SIG.equals(publicKeyUse)) { + throw new JwkException((publicKeyUse != null ? publicKeyUse.value() : "unknown") + + " (" + PUBLIC_KEY_USE + ") is currently not supported."); + } + + // alg + JwkDefinition.CryptoAlgorithm algorithm = + JwkDefinition.CryptoAlgorithm.fromStandardName(attributes.get(ALGORITHM)); + if (!JwkDefinition.CryptoAlgorithm.RS256.equals(algorithm) && + !JwkDefinition.CryptoAlgorithm.RS384.equals(algorithm) && + !JwkDefinition.CryptoAlgorithm.RS512.equals(algorithm)) { + throw new JwkException((algorithm != null ? algorithm.standardName() : "unknown") + + " (" + ALGORITHM + ") is currently not supported."); + } + + // n + String modulus = attributes.get(RSA_PUBLIC_KEY_MODULUS); + if (!StringUtils.hasText(modulus)) { + throw new JwkException("\"" + RSA_PUBLIC_KEY_MODULUS + "\" is a required attribute for a RSA JWK."); + } + + // e + String exponent = attributes.get(RSA_PUBLIC_KEY_EXPONENT); + if (!StringUtils.hasText(exponent)) { + throw new JwkException("\"" + RSA_PUBLIC_KEY_EXPONENT + "\" is a required attribute for a RSA JWK."); + } + + RSAJwkDefinition jwkDefinition = new RSAJwkDefinition( + keyId, publicKeyUse, algorithm, modulus, exponent); + + return jwkDefinition; + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java new file mode 100644 index 000000000..cbfd1696f --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; +import org.springframework.util.Assert; + +import java.util.Collection; + +/** + * @author Joe Grandja + */ +public class JwkTokenStore implements TokenStore { + private final JwtTokenStore delegate; + + public JwkTokenStore(String jwkSetUrl) { + Assert.hasText(jwkSetUrl, "jwkSetUrl cannot be empty"); + JwkDefinitionSource jwkDefinitionSource = new JwkDefinitionSource(jwkSetUrl); + JwkVerifyingJwtAccessTokenConverter accessTokenConverter = + new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource); + this.delegate = new JwtTokenStore(accessTokenConverter); + } + + @Override + public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { + return this.delegate.readAuthentication(token); + } + + @Override + public OAuth2Authentication readAuthentication(String token) { + return this.delegate.readAuthentication(token); + } + + @Override + public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { + throw this.operationNotSupported(); + } + + @Override + public OAuth2AccessToken readAccessToken(String tokenValue) { + return this.delegate.readAccessToken(tokenValue); + } + + @Override + public void removeAccessToken(OAuth2AccessToken token) { + throw this.operationNotSupported(); + } + + @Override + public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { + throw this.operationNotSupported(); + } + + @Override + public OAuth2RefreshToken readRefreshToken(String tokenValue) { + throw this.operationNotSupported(); + } + + @Override + public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { + throw this.operationNotSupported(); + } + + @Override + public void removeRefreshToken(OAuth2RefreshToken token) { + throw this.operationNotSupported(); + } + + @Override + public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { + throw this.operationNotSupported(); + } + + @Override + public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { + throw this.operationNotSupported(); + } + + @Override + public Collection findTokensByClientIdAndUserName(String clientId, String userName) { + throw this.operationNotSupported(); + } + + @Override + public Collection findTokensByClientId(String clientId) { + throw this.operationNotSupported(); + } + + private JwkException operationNotSupported() { + return new JwkException("This operation is currently not supported."); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java new file mode 100644 index 000000000..dc7ea2606 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.springframework.security.jwt.Jwt; +import org.springframework.security.jwt.JwtHelper; +import org.springframework.security.jwt.crypto.sign.SignatureVerifier; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.util.JsonParser; +import org.springframework.security.oauth2.common.util.JsonParserFactory; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; + +import java.util.Map; + +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.ALGORITHM; +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.KEY_ID; + +/** + * @author Joe Grandja + */ +class JwkVerifyingJwtAccessTokenConverter extends JwtAccessTokenConverter { + private final JwkDefinitionSource jwkDefinitionSource; + private final JwtHeaderConverter jwtHeaderConverter = new JwtHeaderConverter(); + private final JsonParser jsonParser = JsonParserFactory.create(); + + JwkVerifyingJwtAccessTokenConverter(JwkDefinitionSource jwkDefinitionSource) { + this.jwkDefinitionSource = jwkDefinitionSource; + } + + @Override + protected Map decode(String token) { + try { + Map headers = this.jwtHeaderConverter.convert(token); + + // Validate "kid" header + String keyIdHeader = headers.get(KEY_ID); + if (keyIdHeader == null) { + throw new JwkException("Invalid JWT/JWS: \"" + KEY_ID + "\" is a required JOSE Header."); + } + JwkDefinition jwkDefinition = this.jwkDefinitionSource.getDefinitionRefreshIfNecessary(keyIdHeader); + if (jwkDefinition == null) { + throw new JwkException("Invalid JOSE Header \"" + KEY_ID + "\" (" + keyIdHeader + ")"); + } + + // Validate "alg" header + String algorithmHeader = headers.get(ALGORITHM); + if (algorithmHeader == null) { + throw new JwkException("Invalid JWT/JWS: \"" + ALGORITHM + "\" is a required JOSE Header."); + } + if (!algorithmHeader.equals(jwkDefinition.getAlgorithm().headerParamValue())) { + throw new JwkException("Invalid JOSE Header \"" + ALGORITHM + "\" (" + algorithmHeader + ")" + + " does not match algorithm associated with \"" + KEY_ID + "\" (" + keyIdHeader + ")"); + } + + // Verify signature + SignatureVerifier verifier = this.jwkDefinitionSource.getVerifier(keyIdHeader); + Jwt jwt = JwtHelper.decode(token); + jwt.verifySignature(verifier); + + Map claims = this.jsonParser.parseMap(jwt.getClaims()); + if (claims.containsKey(EXP) && claims.get(EXP) instanceof Integer) { + Integer expiryInt = (Integer) claims.get(EXP); + claims.put(EXP, new Long(expiryInt)); + } + + return claims; + + } catch (Exception ex) { + throw new JwkException("Failed to decode/verify the JWT/JWS: " + ex.getMessage(), ex); + } + } + + @Override + protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { + throw new JwkException("JWT/JWS (signing) is currently not supported."); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java new file mode 100644 index 000000000..2afa65a8d --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.jwt.codec.Codecs; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Joe Grandja + */ +class JwtHeaderConverter implements Converter> { + private final JsonFactory factory = new JsonFactory(); + + @Override + public Map convert(String token) { + Map headers; + + int headerEndIndex = token.indexOf('.'); + if (headerEndIndex == -1) { + throw new JwkException("Invalid JWT. Missing JOSE Header."); + } + byte[] decodedHeader = Codecs.b64UrlDecode(token.substring(0, headerEndIndex)); + + JsonParser parser = null; + + try { + parser = this.factory.createParser(decodedHeader); + headers = new HashMap(); + if (parser.nextToken() == JsonToken.START_OBJECT) { + while (parser.nextToken() == JsonToken.FIELD_NAME) { + String headerName = parser.getCurrentName(); + parser.nextToken(); + String headerValue = parser.getValueAsString(); + headers.put(headerName, headerValue); + } + } + + } catch (IOException ex) { + throw new JwkException("An I/O error occurred while reading the JWT: " + ex.getMessage(), ex); + } finally { + try { + if (parser != null) parser.close(); + } catch (IOException ex) { } + } + + return headers; + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java new file mode 100644 index 000000000..ef8d1d06e --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +/** + * @author Joe Grandja + */ +final class RSAJwkDefinition extends JwkDefinition { + private final String modulus; + private final String exponent; + + RSAJwkDefinition(String keyId, + JwkDefinition.PublicKeyUse publicKeyUse, + JwkDefinition.CryptoAlgorithm algorithm, + String modulus, + String exponent) { + + super(keyId, JwkDefinition.KeyType.RSA, publicKeyUse, algorithm); + this.modulus = modulus; + this.exponent = exponent; + } + + String getModulus() { + return this.modulus; + } + + String getExponent() { + return this.exponent; + } +} \ No newline at end of file From 6a46b8c49976a11c65b9a8699def0acd4b426519 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 13 Feb 2017 16:08:22 -0500 Subject: [PATCH 07/15] Revert "Add TokenStore supporting Jwk verification" This reverts commit f66a16581f860354cb5323cc4af4eaf781edf3ba. --- .../token/store/jwk/JwkAttributes.java | 35 ---- .../token/store/jwk/JwkDefinition.java | 168 ------------------ .../token/store/jwk/JwkDefinitionSource.java | 117 ------------ .../token/store/jwk/JwkException.java | 42 ----- .../token/store/jwk/JwkSetConverter.java | 144 --------------- .../token/store/jwk/JwkTokenStore.java | 109 ------------ .../JwkVerifyingJwtAccessTokenConverter.java | 91 ---------- .../token/store/jwk/JwtHeaderConverter.java | 68 ------- .../token/store/jwk/RSAJwkDefinition.java | 43 ----- 9 files changed, 817 deletions(-) delete mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java delete mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java delete mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java delete mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java delete mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java delete mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java delete mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java delete mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java delete mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java deleted file mode 100644 index 72769bbc0..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.provider.token.store.jwk; - -/** - * @author Joe Grandja - */ -final class JwkAttributes { - static final String KEY_ID = "kid"; - - static final String KEY_TYPE = "kty"; - - static final String ALGORITHM = "alg"; - - static final String PUBLIC_KEY_USE = "use"; - - static final String RSA_PUBLIC_KEY_MODULUS = "n"; - - static final String RSA_PUBLIC_KEY_EXPONENT = "e"; - - static final String KEYS = "keys"; -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java deleted file mode 100644 index 93caa5a5c..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.provider.token.store.jwk; - -/** - * @author Joe Grandja - */ -abstract class JwkDefinition { - private final String keyId; - private final KeyType keyType; - private final PublicKeyUse publicKeyUse; - private final CryptoAlgorithm algorithm; - - protected JwkDefinition(String keyId, - KeyType keyType, - PublicKeyUse publicKeyUse, - CryptoAlgorithm algorithm) { - this.keyId = keyId; - this.keyType = keyType; - this.publicKeyUse = publicKeyUse; - this.algorithm = algorithm; - } - - String getKeyId() { - return this.keyId; - } - - KeyType getKeyType() { - return this.keyType; - } - - PublicKeyUse getPublicKeyUse() { - return this.publicKeyUse; - } - - CryptoAlgorithm getAlgorithm() { - return this.algorithm; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || this.getClass() != obj.getClass()) { - return false; - } - - JwkDefinition that = (JwkDefinition) obj; - if (!this.getKeyId().equals(that.getKeyId())) { - return false; - } - - return this.getKeyType().equals(that.getKeyType()); - } - - @Override - public int hashCode() { - int result = this.getKeyId().hashCode(); - result = 31 * result + this.getKeyType().hashCode(); - return result; - } - - enum KeyType { - RSA("RSA"), - EC("EC"), - OCT("oct"); - - private final String value; - - KeyType(String value) { - this.value = value; - } - - String value() { - return this.value; - } - - static KeyType fromValue(String value) { - KeyType result = null; - for (KeyType keyType : values()) { - if (keyType.value().equals(value)) { - result = keyType; - break; - } - } - return result; - } - } - - enum PublicKeyUse { - SIG("sig"), - ENC("enc"); - - private final String value; - - PublicKeyUse(String value) { - this.value = value; - } - - String value() { - return this.value; - } - - static PublicKeyUse fromValue(String value) { - PublicKeyUse result = null; - for (PublicKeyUse publicKeyUse : values()) { - if (publicKeyUse.value().equals(value)) { - result = publicKeyUse; - break; - } - } - return result; - } - } - - enum CryptoAlgorithm { - RS256("SHA256withRSA", "RS256", "RSASSA-PKCS1-v1_5 using SHA-256"), - RS384("SHA384withRSA", "RS384", "RSASSA-PKCS1-v1_5 using SHA-384"), - RS512("SHA512withRSA", "RS512", "RSASSA-PKCS1-v1_5 using SHA-512"); - - private final String standardName; // JCA Standard Name - private final String headerParamValue; - private final String description; - - CryptoAlgorithm(String standardName, String headerParamValue, String description) { - this.standardName = standardName; - this.headerParamValue = headerParamValue; - this.description = description; - } - - String standardName() { - return this.standardName; - } - - String headerParamValue() { - return this.headerParamValue; - } - - String description() { - return this.description; - } - - static CryptoAlgorithm fromStandardName(String standardName) { - CryptoAlgorithm result = null; - for (CryptoAlgorithm algorithm : values()) { - if (algorithm.standardName().equals(standardName)) { - result = algorithm; - break; - } - } - return result; - } - } -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java deleted file mode 100644 index a9f241f92..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.provider.token.store.jwk; - -import org.springframework.security.jwt.codec.Codecs; -import org.springframework.security.jwt.crypto.sign.RsaVerifier; -import org.springframework.security.jwt.crypto.sign.SignatureVerifier; - -import java.io.IOException; -import java.math.BigInteger; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.KeyFactory; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.RSAPublicKeySpec; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -/** - * @author Joe Grandja - */ -class JwkDefinitionSource { - private final URL jwkSetUrl; - private final JwkSetConverter jwkSetConverter = new JwkSetConverter(); - private final AtomicReference> jwkDefinitions = - new AtomicReference>(new HashMap()); - - JwkDefinitionSource(String jwkSetUrl) { - try { - this.jwkSetUrl = new URL(jwkSetUrl); - } catch (MalformedURLException ex) { - throw new IllegalArgumentException("Invalid JWK Set URL: " + ex.getMessage(), ex); - } - } - - JwkDefinition getDefinition(String keyId) { - JwkDefinition result = null; - for (JwkDefinition jwkDefinition : this.jwkDefinitions.get().keySet()) { - if (jwkDefinition.getKeyId().equals(keyId)) { - result = jwkDefinition; - break; - } - } - return result; - } - - JwkDefinition getDefinitionRefreshIfNecessary(String keyId) { - JwkDefinition result = this.getDefinition(keyId); - if (result != null) { - return result; - } - this.refreshJwkDefinitions(); - return this.getDefinition(keyId); - } - - SignatureVerifier getVerifier(String keyId) { - SignatureVerifier result = null; - JwkDefinition jwkDefinition = this.getDefinitionRefreshIfNecessary(keyId); - if (jwkDefinition != null) { - result = this.jwkDefinitions.get().get(jwkDefinition); - } - return result; - } - - private void refreshJwkDefinitions() { - Set jwkDefinitionSet; - try { - jwkDefinitionSet = this.jwkSetConverter.convert(this.jwkSetUrl.openStream()); - } catch (IOException ex) { - throw new JwkException("An I/O error occurred while refreshing the JWK Set: " + ex.getMessage(), ex); - } - - Map refreshedJwkDefinitions = new LinkedHashMap(); - - for (JwkDefinition jwkDefinition : jwkDefinitionSet) { - if (JwkDefinition.KeyType.RSA.equals(jwkDefinition.getKeyType())) { - refreshedJwkDefinitions.put(jwkDefinition, this.createRSAVerifier((RSAJwkDefinition)jwkDefinition)); - } - } - - this.jwkDefinitions.set(refreshedJwkDefinitions); - } - - private RsaVerifier createRSAVerifier(RSAJwkDefinition rsaDefinition) { - RsaVerifier result; - try { - BigInteger modulus = new BigInteger(Codecs.b64UrlDecode(rsaDefinition.getModulus())); - BigInteger exponent = new BigInteger(Codecs.b64UrlDecode(rsaDefinition.getExponent())); - - RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") - .generatePublic(new RSAPublicKeySpec(modulus, exponent)); - - result = new RsaVerifier(rsaPublicKey, rsaDefinition.getAlgorithm().standardName()); - - } catch (Exception ex) { - throw new JwkException("An error occurred while creating a RSA Public Key Verifier for \"" + - rsaDefinition.getKeyId() + "\" : " + ex.getMessage(), ex); - } - return result; - } -} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java deleted file mode 100644 index f9a5e0032..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.provider.token.store.jwk; - -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; - -/** - * @author Joe Grandja - */ -public class JwkException extends OAuth2Exception { - - public JwkException(String message) { - super(message); - } - - public JwkException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public String getOAuth2ErrorCode() { - return "server_error"; - } - - @Override - public int getHttpErrorCode() { - return 500; - } -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java deleted file mode 100644 index 1b6964adb..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.provider.token.store.jwk; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import org.springframework.core.convert.converter.Converter; -import org.springframework.util.StringUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.*; - - -/** - * @author Joe Grandja - */ -class JwkSetConverter implements Converter> { - private final JsonFactory factory = new JsonFactory(); - - @Override - public Set convert(InputStream jwkSetSource) { - Set jwkDefinitions; - JsonParser parser = null; - - try { - parser = this.factory.createParser(jwkSetSource); - - if (parser.nextToken() != JsonToken.START_OBJECT) { - throw new JwkException("Invalid JWK Set Object."); - } - if (parser.nextToken() != JsonToken.FIELD_NAME) { - throw new JwkException("Invalid JWK Set Object."); - } - if (!parser.getCurrentName().equals(KEYS)) { - throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have a \"" + KEYS + "\" attribute."); - } - if (parser.nextToken() != JsonToken.START_ARRAY) { - throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have an array of JWK(s)."); - } - - jwkDefinitions = new LinkedHashSet(); - Map attributes = new HashMap(); - - while (parser.nextToken() == JsonToken.START_OBJECT) { - while (parser.nextToken() == JsonToken.FIELD_NAME) { - String attributeName = parser.getCurrentName(); - parser.nextToken(); - String attributeValue = parser.getValueAsString(); - attributes.put(attributeName, attributeValue); - } - JwkDefinition jwkDefinition = this.createJwkDefinition(attributes); - if (!jwkDefinitions.add(jwkDefinition)) { - throw new JwkException("Duplicate JWK found in Set: " + - jwkDefinition.getKeyId() + " (" + KEY_ID + ")"); - } - attributes.clear(); - } - - } catch (IOException ex) { - throw new JwkException("An I/O error occurred while reading the JWK Set: " + ex.getMessage(), ex); - } finally { - try { - if (parser != null) parser.close(); - } catch (IOException ex) { } - } - - return jwkDefinitions; - } - - private JwkDefinition createJwkDefinition(Map attributes) { - JwkDefinition.KeyType keyType = - JwkDefinition.KeyType.fromValue(attributes.get(KEY_TYPE)); - - if (!JwkDefinition.KeyType.RSA.equals(keyType)) { - throw new JwkException((keyType != null ? keyType.value() : "unknown") + - " (" + KEY_TYPE + ") is currently not supported."); - } - - return this.createRSAJwkDefinition(attributes); - } - - private JwkDefinition createRSAJwkDefinition(Map attributes) { - // kid - String keyId = attributes.get(KEY_ID); - if (!StringUtils.hasText(keyId)) { - throw new JwkException("\"" + KEY_ID + "\" is a required attribute for a JWK."); - } - - // use - JwkDefinition.PublicKeyUse publicKeyUse = - JwkDefinition.PublicKeyUse.fromValue(attributes.get(PUBLIC_KEY_USE)); - if (!JwkDefinition.PublicKeyUse.SIG.equals(publicKeyUse)) { - throw new JwkException((publicKeyUse != null ? publicKeyUse.value() : "unknown") + - " (" + PUBLIC_KEY_USE + ") is currently not supported."); - } - - // alg - JwkDefinition.CryptoAlgorithm algorithm = - JwkDefinition.CryptoAlgorithm.fromStandardName(attributes.get(ALGORITHM)); - if (!JwkDefinition.CryptoAlgorithm.RS256.equals(algorithm) && - !JwkDefinition.CryptoAlgorithm.RS384.equals(algorithm) && - !JwkDefinition.CryptoAlgorithm.RS512.equals(algorithm)) { - throw new JwkException((algorithm != null ? algorithm.standardName() : "unknown") + - " (" + ALGORITHM + ") is currently not supported."); - } - - // n - String modulus = attributes.get(RSA_PUBLIC_KEY_MODULUS); - if (!StringUtils.hasText(modulus)) { - throw new JwkException("\"" + RSA_PUBLIC_KEY_MODULUS + "\" is a required attribute for a RSA JWK."); - } - - // e - String exponent = attributes.get(RSA_PUBLIC_KEY_EXPONENT); - if (!StringUtils.hasText(exponent)) { - throw new JwkException("\"" + RSA_PUBLIC_KEY_EXPONENT + "\" is a required attribute for a RSA JWK."); - } - - RSAJwkDefinition jwkDefinition = new RSAJwkDefinition( - keyId, publicKeyUse, algorithm, modulus, exponent); - - return jwkDefinition; - } -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java deleted file mode 100644 index cbfd1696f..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.provider.token.store.jwk; - -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2RefreshToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; -import org.springframework.util.Assert; - -import java.util.Collection; - -/** - * @author Joe Grandja - */ -public class JwkTokenStore implements TokenStore { - private final JwtTokenStore delegate; - - public JwkTokenStore(String jwkSetUrl) { - Assert.hasText(jwkSetUrl, "jwkSetUrl cannot be empty"); - JwkDefinitionSource jwkDefinitionSource = new JwkDefinitionSource(jwkSetUrl); - JwkVerifyingJwtAccessTokenConverter accessTokenConverter = - new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource); - this.delegate = new JwtTokenStore(accessTokenConverter); - } - - @Override - public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { - return this.delegate.readAuthentication(token); - } - - @Override - public OAuth2Authentication readAuthentication(String token) { - return this.delegate.readAuthentication(token); - } - - @Override - public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { - throw this.operationNotSupported(); - } - - @Override - public OAuth2AccessToken readAccessToken(String tokenValue) { - return this.delegate.readAccessToken(tokenValue); - } - - @Override - public void removeAccessToken(OAuth2AccessToken token) { - throw this.operationNotSupported(); - } - - @Override - public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { - throw this.operationNotSupported(); - } - - @Override - public OAuth2RefreshToken readRefreshToken(String tokenValue) { - throw this.operationNotSupported(); - } - - @Override - public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { - throw this.operationNotSupported(); - } - - @Override - public void removeRefreshToken(OAuth2RefreshToken token) { - throw this.operationNotSupported(); - } - - @Override - public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { - throw this.operationNotSupported(); - } - - @Override - public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { - throw this.operationNotSupported(); - } - - @Override - public Collection findTokensByClientIdAndUserName(String clientId, String userName) { - throw this.operationNotSupported(); - } - - @Override - public Collection findTokensByClientId(String clientId) { - throw this.operationNotSupported(); - } - - private JwkException operationNotSupported() { - return new JwkException("This operation is currently not supported."); - } -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java deleted file mode 100644 index dc7ea2606..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.provider.token.store.jwk; - -import org.springframework.security.jwt.Jwt; -import org.springframework.security.jwt.JwtHelper; -import org.springframework.security.jwt.crypto.sign.SignatureVerifier; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.util.JsonParser; -import org.springframework.security.oauth2.common.util.JsonParserFactory; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; - -import java.util.Map; - -import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.ALGORITHM; -import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.KEY_ID; - -/** - * @author Joe Grandja - */ -class JwkVerifyingJwtAccessTokenConverter extends JwtAccessTokenConverter { - private final JwkDefinitionSource jwkDefinitionSource; - private final JwtHeaderConverter jwtHeaderConverter = new JwtHeaderConverter(); - private final JsonParser jsonParser = JsonParserFactory.create(); - - JwkVerifyingJwtAccessTokenConverter(JwkDefinitionSource jwkDefinitionSource) { - this.jwkDefinitionSource = jwkDefinitionSource; - } - - @Override - protected Map decode(String token) { - try { - Map headers = this.jwtHeaderConverter.convert(token); - - // Validate "kid" header - String keyIdHeader = headers.get(KEY_ID); - if (keyIdHeader == null) { - throw new JwkException("Invalid JWT/JWS: \"" + KEY_ID + "\" is a required JOSE Header."); - } - JwkDefinition jwkDefinition = this.jwkDefinitionSource.getDefinitionRefreshIfNecessary(keyIdHeader); - if (jwkDefinition == null) { - throw new JwkException("Invalid JOSE Header \"" + KEY_ID + "\" (" + keyIdHeader + ")"); - } - - // Validate "alg" header - String algorithmHeader = headers.get(ALGORITHM); - if (algorithmHeader == null) { - throw new JwkException("Invalid JWT/JWS: \"" + ALGORITHM + "\" is a required JOSE Header."); - } - if (!algorithmHeader.equals(jwkDefinition.getAlgorithm().headerParamValue())) { - throw new JwkException("Invalid JOSE Header \"" + ALGORITHM + "\" (" + algorithmHeader + ")" + - " does not match algorithm associated with \"" + KEY_ID + "\" (" + keyIdHeader + ")"); - } - - // Verify signature - SignatureVerifier verifier = this.jwkDefinitionSource.getVerifier(keyIdHeader); - Jwt jwt = JwtHelper.decode(token); - jwt.verifySignature(verifier); - - Map claims = this.jsonParser.parseMap(jwt.getClaims()); - if (claims.containsKey(EXP) && claims.get(EXP) instanceof Integer) { - Integer expiryInt = (Integer) claims.get(EXP); - claims.put(EXP, new Long(expiryInt)); - } - - return claims; - - } catch (Exception ex) { - throw new JwkException("Failed to decode/verify the JWT/JWS: " + ex.getMessage(), ex); - } - } - - @Override - protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { - throw new JwkException("JWT/JWS (signing) is currently not supported."); - } -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java deleted file mode 100644 index 2afa65a8d..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.provider.token.store.jwk; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.jwt.codec.Codecs; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -/** - * @author Joe Grandja - */ -class JwtHeaderConverter implements Converter> { - private final JsonFactory factory = new JsonFactory(); - - @Override - public Map convert(String token) { - Map headers; - - int headerEndIndex = token.indexOf('.'); - if (headerEndIndex == -1) { - throw new JwkException("Invalid JWT. Missing JOSE Header."); - } - byte[] decodedHeader = Codecs.b64UrlDecode(token.substring(0, headerEndIndex)); - - JsonParser parser = null; - - try { - parser = this.factory.createParser(decodedHeader); - headers = new HashMap(); - if (parser.nextToken() == JsonToken.START_OBJECT) { - while (parser.nextToken() == JsonToken.FIELD_NAME) { - String headerName = parser.getCurrentName(); - parser.nextToken(); - String headerValue = parser.getValueAsString(); - headers.put(headerName, headerValue); - } - } - - } catch (IOException ex) { - throw new JwkException("An I/O error occurred while reading the JWT: " + ex.getMessage(), ex); - } finally { - try { - if (parser != null) parser.close(); - } catch (IOException ex) { } - } - - return headers; - } -} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java deleted file mode 100644 index ef8d1d06e..000000000 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2012-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.oauth2.provider.token.store.jwk; - -/** - * @author Joe Grandja - */ -final class RSAJwkDefinition extends JwkDefinition { - private final String modulus; - private final String exponent; - - RSAJwkDefinition(String keyId, - JwkDefinition.PublicKeyUse publicKeyUse, - JwkDefinition.CryptoAlgorithm algorithm, - String modulus, - String exponent) { - - super(keyId, JwkDefinition.KeyType.RSA, publicKeyUse, algorithm); - this.modulus = modulus; - this.exponent = exponent; - } - - String getModulus() { - return this.modulus; - } - - String getExponent() { - return this.exponent; - } -} \ No newline at end of file From 565230fe7d1a04213ee828fb4c025ea7f5cd9216 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 13 Feb 2017 16:09:22 -0500 Subject: [PATCH 08/15] Add TokenStore supporting Jwk verification Fixes gh-977 --- .../token/store/jwk/JwkAttributes.java | 35 ++++ .../token/store/jwk/JwkDefinition.java | 168 ++++++++++++++++++ .../token/store/jwk/JwkDefinitionSource.java | 117 ++++++++++++ .../token/store/jwk/JwkException.java | 42 +++++ .../token/store/jwk/JwkSetConverter.java | 144 +++++++++++++++ .../token/store/jwk/JwkTokenStore.java | 109 ++++++++++++ .../JwkVerifyingJwtAccessTokenConverter.java | 91 ++++++++++ .../token/store/jwk/JwtHeaderConverter.java | 68 +++++++ .../token/store/jwk/RSAJwkDefinition.java | 43 +++++ 9 files changed, 817 insertions(+) create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java create mode 100644 spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java new file mode 100644 index 000000000..72769bbc0 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +/** + * @author Joe Grandja + */ +final class JwkAttributes { + static final String KEY_ID = "kid"; + + static final String KEY_TYPE = "kty"; + + static final String ALGORITHM = "alg"; + + static final String PUBLIC_KEY_USE = "use"; + + static final String RSA_PUBLIC_KEY_MODULUS = "n"; + + static final String RSA_PUBLIC_KEY_EXPONENT = "e"; + + static final String KEYS = "keys"; +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java new file mode 100644 index 000000000..93caa5a5c --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java @@ -0,0 +1,168 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +/** + * @author Joe Grandja + */ +abstract class JwkDefinition { + private final String keyId; + private final KeyType keyType; + private final PublicKeyUse publicKeyUse; + private final CryptoAlgorithm algorithm; + + protected JwkDefinition(String keyId, + KeyType keyType, + PublicKeyUse publicKeyUse, + CryptoAlgorithm algorithm) { + this.keyId = keyId; + this.keyType = keyType; + this.publicKeyUse = publicKeyUse; + this.algorithm = algorithm; + } + + String getKeyId() { + return this.keyId; + } + + KeyType getKeyType() { + return this.keyType; + } + + PublicKeyUse getPublicKeyUse() { + return this.publicKeyUse; + } + + CryptoAlgorithm getAlgorithm() { + return this.algorithm; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + + JwkDefinition that = (JwkDefinition) obj; + if (!this.getKeyId().equals(that.getKeyId())) { + return false; + } + + return this.getKeyType().equals(that.getKeyType()); + } + + @Override + public int hashCode() { + int result = this.getKeyId().hashCode(); + result = 31 * result + this.getKeyType().hashCode(); + return result; + } + + enum KeyType { + RSA("RSA"), + EC("EC"), + OCT("oct"); + + private final String value; + + KeyType(String value) { + this.value = value; + } + + String value() { + return this.value; + } + + static KeyType fromValue(String value) { + KeyType result = null; + for (KeyType keyType : values()) { + if (keyType.value().equals(value)) { + result = keyType; + break; + } + } + return result; + } + } + + enum PublicKeyUse { + SIG("sig"), + ENC("enc"); + + private final String value; + + PublicKeyUse(String value) { + this.value = value; + } + + String value() { + return this.value; + } + + static PublicKeyUse fromValue(String value) { + PublicKeyUse result = null; + for (PublicKeyUse publicKeyUse : values()) { + if (publicKeyUse.value().equals(value)) { + result = publicKeyUse; + break; + } + } + return result; + } + } + + enum CryptoAlgorithm { + RS256("SHA256withRSA", "RS256", "RSASSA-PKCS1-v1_5 using SHA-256"), + RS384("SHA384withRSA", "RS384", "RSASSA-PKCS1-v1_5 using SHA-384"), + RS512("SHA512withRSA", "RS512", "RSASSA-PKCS1-v1_5 using SHA-512"); + + private final String standardName; // JCA Standard Name + private final String headerParamValue; + private final String description; + + CryptoAlgorithm(String standardName, String headerParamValue, String description) { + this.standardName = standardName; + this.headerParamValue = headerParamValue; + this.description = description; + } + + String standardName() { + return this.standardName; + } + + String headerParamValue() { + return this.headerParamValue; + } + + String description() { + return this.description; + } + + static CryptoAlgorithm fromStandardName(String standardName) { + CryptoAlgorithm result = null; + for (CryptoAlgorithm algorithm : values()) { + if (algorithm.standardName().equals(standardName)) { + result = algorithm; + break; + } + } + return result; + } + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java new file mode 100644 index 000000000..a9f241f92 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java @@ -0,0 +1,117 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.springframework.security.jwt.codec.Codecs; +import org.springframework.security.jwt.crypto.sign.RsaVerifier; +import org.springframework.security.jwt.crypto.sign.SignatureVerifier; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.KeyFactory; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAPublicKeySpec; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author Joe Grandja + */ +class JwkDefinitionSource { + private final URL jwkSetUrl; + private final JwkSetConverter jwkSetConverter = new JwkSetConverter(); + private final AtomicReference> jwkDefinitions = + new AtomicReference>(new HashMap()); + + JwkDefinitionSource(String jwkSetUrl) { + try { + this.jwkSetUrl = new URL(jwkSetUrl); + } catch (MalformedURLException ex) { + throw new IllegalArgumentException("Invalid JWK Set URL: " + ex.getMessage(), ex); + } + } + + JwkDefinition getDefinition(String keyId) { + JwkDefinition result = null; + for (JwkDefinition jwkDefinition : this.jwkDefinitions.get().keySet()) { + if (jwkDefinition.getKeyId().equals(keyId)) { + result = jwkDefinition; + break; + } + } + return result; + } + + JwkDefinition getDefinitionRefreshIfNecessary(String keyId) { + JwkDefinition result = this.getDefinition(keyId); + if (result != null) { + return result; + } + this.refreshJwkDefinitions(); + return this.getDefinition(keyId); + } + + SignatureVerifier getVerifier(String keyId) { + SignatureVerifier result = null; + JwkDefinition jwkDefinition = this.getDefinitionRefreshIfNecessary(keyId); + if (jwkDefinition != null) { + result = this.jwkDefinitions.get().get(jwkDefinition); + } + return result; + } + + private void refreshJwkDefinitions() { + Set jwkDefinitionSet; + try { + jwkDefinitionSet = this.jwkSetConverter.convert(this.jwkSetUrl.openStream()); + } catch (IOException ex) { + throw new JwkException("An I/O error occurred while refreshing the JWK Set: " + ex.getMessage(), ex); + } + + Map refreshedJwkDefinitions = new LinkedHashMap(); + + for (JwkDefinition jwkDefinition : jwkDefinitionSet) { + if (JwkDefinition.KeyType.RSA.equals(jwkDefinition.getKeyType())) { + refreshedJwkDefinitions.put(jwkDefinition, this.createRSAVerifier((RSAJwkDefinition)jwkDefinition)); + } + } + + this.jwkDefinitions.set(refreshedJwkDefinitions); + } + + private RsaVerifier createRSAVerifier(RSAJwkDefinition rsaDefinition) { + RsaVerifier result; + try { + BigInteger modulus = new BigInteger(Codecs.b64UrlDecode(rsaDefinition.getModulus())); + BigInteger exponent = new BigInteger(Codecs.b64UrlDecode(rsaDefinition.getExponent())); + + RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") + .generatePublic(new RSAPublicKeySpec(modulus, exponent)); + + result = new RsaVerifier(rsaPublicKey, rsaDefinition.getAlgorithm().standardName()); + + } catch (Exception ex) { + throw new JwkException("An error occurred while creating a RSA Public Key Verifier for \"" + + rsaDefinition.getKeyId() + "\" : " + ex.getMessage(), ex); + } + return result; + } +} diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java new file mode 100644 index 000000000..f9a5e0032 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; + +/** + * @author Joe Grandja + */ +public class JwkException extends OAuth2Exception { + + public JwkException(String message) { + super(message); + } + + public JwkException(String message, Throwable cause) { + super(message, cause); + } + + @Override + public String getOAuth2ErrorCode() { + return "server_error"; + } + + @Override + public int getHttpErrorCode() { + return 500; + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java new file mode 100644 index 000000000..1b6964adb --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java @@ -0,0 +1,144 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import org.springframework.core.convert.converter.Converter; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.*; + + +/** + * @author Joe Grandja + */ +class JwkSetConverter implements Converter> { + private final JsonFactory factory = new JsonFactory(); + + @Override + public Set convert(InputStream jwkSetSource) { + Set jwkDefinitions; + JsonParser parser = null; + + try { + parser = this.factory.createParser(jwkSetSource); + + if (parser.nextToken() != JsonToken.START_OBJECT) { + throw new JwkException("Invalid JWK Set Object."); + } + if (parser.nextToken() != JsonToken.FIELD_NAME) { + throw new JwkException("Invalid JWK Set Object."); + } + if (!parser.getCurrentName().equals(KEYS)) { + throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have a \"" + KEYS + "\" attribute."); + } + if (parser.nextToken() != JsonToken.START_ARRAY) { + throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have an array of JWK(s)."); + } + + jwkDefinitions = new LinkedHashSet(); + Map attributes = new HashMap(); + + while (parser.nextToken() == JsonToken.START_OBJECT) { + while (parser.nextToken() == JsonToken.FIELD_NAME) { + String attributeName = parser.getCurrentName(); + parser.nextToken(); + String attributeValue = parser.getValueAsString(); + attributes.put(attributeName, attributeValue); + } + JwkDefinition jwkDefinition = this.createJwkDefinition(attributes); + if (!jwkDefinitions.add(jwkDefinition)) { + throw new JwkException("Duplicate JWK found in Set: " + + jwkDefinition.getKeyId() + " (" + KEY_ID + ")"); + } + attributes.clear(); + } + + } catch (IOException ex) { + throw new JwkException("An I/O error occurred while reading the JWK Set: " + ex.getMessage(), ex); + } finally { + try { + if (parser != null) parser.close(); + } catch (IOException ex) { } + } + + return jwkDefinitions; + } + + private JwkDefinition createJwkDefinition(Map attributes) { + JwkDefinition.KeyType keyType = + JwkDefinition.KeyType.fromValue(attributes.get(KEY_TYPE)); + + if (!JwkDefinition.KeyType.RSA.equals(keyType)) { + throw new JwkException((keyType != null ? keyType.value() : "unknown") + + " (" + KEY_TYPE + ") is currently not supported."); + } + + return this.createRSAJwkDefinition(attributes); + } + + private JwkDefinition createRSAJwkDefinition(Map attributes) { + // kid + String keyId = attributes.get(KEY_ID); + if (!StringUtils.hasText(keyId)) { + throw new JwkException("\"" + KEY_ID + "\" is a required attribute for a JWK."); + } + + // use + JwkDefinition.PublicKeyUse publicKeyUse = + JwkDefinition.PublicKeyUse.fromValue(attributes.get(PUBLIC_KEY_USE)); + if (!JwkDefinition.PublicKeyUse.SIG.equals(publicKeyUse)) { + throw new JwkException((publicKeyUse != null ? publicKeyUse.value() : "unknown") + + " (" + PUBLIC_KEY_USE + ") is currently not supported."); + } + + // alg + JwkDefinition.CryptoAlgorithm algorithm = + JwkDefinition.CryptoAlgorithm.fromStandardName(attributes.get(ALGORITHM)); + if (!JwkDefinition.CryptoAlgorithm.RS256.equals(algorithm) && + !JwkDefinition.CryptoAlgorithm.RS384.equals(algorithm) && + !JwkDefinition.CryptoAlgorithm.RS512.equals(algorithm)) { + throw new JwkException((algorithm != null ? algorithm.standardName() : "unknown") + + " (" + ALGORITHM + ") is currently not supported."); + } + + // n + String modulus = attributes.get(RSA_PUBLIC_KEY_MODULUS); + if (!StringUtils.hasText(modulus)) { + throw new JwkException("\"" + RSA_PUBLIC_KEY_MODULUS + "\" is a required attribute for a RSA JWK."); + } + + // e + String exponent = attributes.get(RSA_PUBLIC_KEY_EXPONENT); + if (!StringUtils.hasText(exponent)) { + throw new JwkException("\"" + RSA_PUBLIC_KEY_EXPONENT + "\" is a required attribute for a RSA JWK."); + } + + RSAJwkDefinition jwkDefinition = new RSAJwkDefinition( + keyId, publicKeyUse, algorithm, modulus, exponent); + + return jwkDefinition; + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java new file mode 100644 index 000000000..cbfd1696f --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; +import org.springframework.util.Assert; + +import java.util.Collection; + +/** + * @author Joe Grandja + */ +public class JwkTokenStore implements TokenStore { + private final JwtTokenStore delegate; + + public JwkTokenStore(String jwkSetUrl) { + Assert.hasText(jwkSetUrl, "jwkSetUrl cannot be empty"); + JwkDefinitionSource jwkDefinitionSource = new JwkDefinitionSource(jwkSetUrl); + JwkVerifyingJwtAccessTokenConverter accessTokenConverter = + new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource); + this.delegate = new JwtTokenStore(accessTokenConverter); + } + + @Override + public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { + return this.delegate.readAuthentication(token); + } + + @Override + public OAuth2Authentication readAuthentication(String token) { + return this.delegate.readAuthentication(token); + } + + @Override + public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { + throw this.operationNotSupported(); + } + + @Override + public OAuth2AccessToken readAccessToken(String tokenValue) { + return this.delegate.readAccessToken(tokenValue); + } + + @Override + public void removeAccessToken(OAuth2AccessToken token) { + throw this.operationNotSupported(); + } + + @Override + public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { + throw this.operationNotSupported(); + } + + @Override + public OAuth2RefreshToken readRefreshToken(String tokenValue) { + throw this.operationNotSupported(); + } + + @Override + public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { + throw this.operationNotSupported(); + } + + @Override + public void removeRefreshToken(OAuth2RefreshToken token) { + throw this.operationNotSupported(); + } + + @Override + public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { + throw this.operationNotSupported(); + } + + @Override + public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { + throw this.operationNotSupported(); + } + + @Override + public Collection findTokensByClientIdAndUserName(String clientId, String userName) { + throw this.operationNotSupported(); + } + + @Override + public Collection findTokensByClientId(String clientId) { + throw this.operationNotSupported(); + } + + private JwkException operationNotSupported() { + return new JwkException("This operation is currently not supported."); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java new file mode 100644 index 000000000..dc7ea2606 --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.springframework.security.jwt.Jwt; +import org.springframework.security.jwt.JwtHelper; +import org.springframework.security.jwt.crypto.sign.SignatureVerifier; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.util.JsonParser; +import org.springframework.security.oauth2.common.util.JsonParserFactory; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; + +import java.util.Map; + +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.ALGORITHM; +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.KEY_ID; + +/** + * @author Joe Grandja + */ +class JwkVerifyingJwtAccessTokenConverter extends JwtAccessTokenConverter { + private final JwkDefinitionSource jwkDefinitionSource; + private final JwtHeaderConverter jwtHeaderConverter = new JwtHeaderConverter(); + private final JsonParser jsonParser = JsonParserFactory.create(); + + JwkVerifyingJwtAccessTokenConverter(JwkDefinitionSource jwkDefinitionSource) { + this.jwkDefinitionSource = jwkDefinitionSource; + } + + @Override + protected Map decode(String token) { + try { + Map headers = this.jwtHeaderConverter.convert(token); + + // Validate "kid" header + String keyIdHeader = headers.get(KEY_ID); + if (keyIdHeader == null) { + throw new JwkException("Invalid JWT/JWS: \"" + KEY_ID + "\" is a required JOSE Header."); + } + JwkDefinition jwkDefinition = this.jwkDefinitionSource.getDefinitionRefreshIfNecessary(keyIdHeader); + if (jwkDefinition == null) { + throw new JwkException("Invalid JOSE Header \"" + KEY_ID + "\" (" + keyIdHeader + ")"); + } + + // Validate "alg" header + String algorithmHeader = headers.get(ALGORITHM); + if (algorithmHeader == null) { + throw new JwkException("Invalid JWT/JWS: \"" + ALGORITHM + "\" is a required JOSE Header."); + } + if (!algorithmHeader.equals(jwkDefinition.getAlgorithm().headerParamValue())) { + throw new JwkException("Invalid JOSE Header \"" + ALGORITHM + "\" (" + algorithmHeader + ")" + + " does not match algorithm associated with \"" + KEY_ID + "\" (" + keyIdHeader + ")"); + } + + // Verify signature + SignatureVerifier verifier = this.jwkDefinitionSource.getVerifier(keyIdHeader); + Jwt jwt = JwtHelper.decode(token); + jwt.verifySignature(verifier); + + Map claims = this.jsonParser.parseMap(jwt.getClaims()); + if (claims.containsKey(EXP) && claims.get(EXP) instanceof Integer) { + Integer expiryInt = (Integer) claims.get(EXP); + claims.put(EXP, new Long(expiryInt)); + } + + return claims; + + } catch (Exception ex) { + throw new JwkException("Failed to decode/verify the JWT/JWS: " + ex.getMessage(), ex); + } + } + + @Override + protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { + throw new JwkException("JWT/JWS (signing) is currently not supported."); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java new file mode 100644 index 000000000..2afa65a8d --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.jwt.codec.Codecs; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Joe Grandja + */ +class JwtHeaderConverter implements Converter> { + private final JsonFactory factory = new JsonFactory(); + + @Override + public Map convert(String token) { + Map headers; + + int headerEndIndex = token.indexOf('.'); + if (headerEndIndex == -1) { + throw new JwkException("Invalid JWT. Missing JOSE Header."); + } + byte[] decodedHeader = Codecs.b64UrlDecode(token.substring(0, headerEndIndex)); + + JsonParser parser = null; + + try { + parser = this.factory.createParser(decodedHeader); + headers = new HashMap(); + if (parser.nextToken() == JsonToken.START_OBJECT) { + while (parser.nextToken() == JsonToken.FIELD_NAME) { + String headerName = parser.getCurrentName(); + parser.nextToken(); + String headerValue = parser.getValueAsString(); + headers.put(headerName, headerValue); + } + } + + } catch (IOException ex) { + throw new JwkException("An I/O error occurred while reading the JWT: " + ex.getMessage(), ex); + } finally { + try { + if (parser != null) parser.close(); + } catch (IOException ex) { } + } + + return headers; + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java new file mode 100644 index 000000000..6361784af --- /dev/null +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +/** + * @author Joe Grandja + */ +final class RSAJwkDefinition extends JwkDefinition { + private final String modulus; + private final String exponent; + + RSAJwkDefinition(String keyId, + PublicKeyUse publicKeyUse, + CryptoAlgorithm algorithm, + String modulus, + String exponent) { + + super(keyId, KeyType.RSA, publicKeyUse, algorithm); + this.modulus = modulus; + this.exponent = exponent; + } + + String getModulus() { + return this.modulus; + } + + String getExponent() { + return this.exponent; + } +} \ No newline at end of file From aea7e16cadc607f6b4c4f95fc3b25ae4f2a4f5cc Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 20 Feb 2017 16:30:22 +0000 Subject: [PATCH 09/15] Add test for ambiguous token services --- .../TokenServicesMultipleBeansTests.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java new file mode 100644 index 000000000..a993afbf9 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.config.annotation; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.oauth2.config.annotation.TokenServicesMultipleBeansTests.BrokenOAuthApplication; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +/** + * @author Dave Syer + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes=BrokenOAuthApplication.class) +@WebAppConfiguration +public class TokenServicesMultipleBeansTests { + + @Autowired + private ResourceServerTokenServices tokenServices; + + @Test + public void test() { + assertNotNull(tokenServices); + } + + @Configuration + @EnableAuthorizationServer + @EnableWebSecurity + protected static class BrokenOAuthApplication extends AuthorizationServerConfigurerAdapter { + } +} From cba2fc02104395bf0266aa82c95ae5ce43523e91 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 21 Feb 2017 15:17:31 +0000 Subject: [PATCH 10/15] Use FactoryBean to expose @Beans of different TokenServices flavours There is one for ConsumerTokenServices and one for AuthorizationServerTokenServices. Fixes gh-984 --- ...orizationServerEndpointsConfiguration.java | 53 +++++++++++++++++-- .../TokenServicesMultipleBeansTests.java | 17 +++++- .../src/test/java/demo/ApplicationTests.java | 6 +-- .../java/demo/RefreshTokenSupportTests.java | 6 +-- 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerEndpointsConfiguration.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerEndpointsConfiguration.java index 2fc3e63c2..160f7eb07 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerEndpointsConfiguration.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/annotation/web/configuration/AuthorizationServerEndpointsConfiguration.java @@ -20,8 +20,11 @@ import javax.annotation.PostConstruct; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.AbstractFactoryBean; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -131,8 +134,19 @@ public FrameworkEndpointHandlerMapping oauth2EndpointHandlerMapping() throws Exc } @Bean - public ConsumerTokenServices consumerTokenServices() throws Exception { - return getEndpointsConfigurer().getConsumerTokenServices(); + public FactoryBean consumerTokenServices() throws Exception { + return new AbstractFactoryBean() { + + @Override + public Class getObjectType() { + return ConsumerTokenServices.class; + } + + @Override + protected ConsumerTokenServices createInstance() throws Exception { + return getEndpointsConfigurer().getConsumerTokenServices(); + } + }; } /** @@ -146,13 +160,18 @@ public ConsumerTokenServices consumerTokenServices() throws Exception { * @return an AuthorizationServerTokenServices */ @Bean - public AuthorizationServerTokenServices defaultAuthorizationServerTokenServices() { - return endpoints.getDefaultAuthorizationServerTokenServices(); + public FactoryBean defaultAuthorizationServerTokenServices() { + return new AuthorizationServerTokenServicesFactoryBean(endpoints); } public AuthorizationServerEndpointsConfigurer getEndpointsConfigurer() { if (!endpoints.isTokenServicesOverride()) { - endpoints.tokenServices(defaultAuthorizationServerTokenServices()); + try { + endpoints.tokenServices(endpoints.getDefaultAuthorizationServerTokenServices()); + } + catch (Exception e) { + throw new BeanCreationException("Cannot create token services", e); + } } return endpoints; } @@ -193,6 +212,30 @@ private String extractPath(FrameworkEndpointHandlerMapping mapping, String page) return "forward:" + path; } + protected static class AuthorizationServerTokenServicesFactoryBean + extends AbstractFactoryBean { + + private AuthorizationServerEndpointsConfigurer endpoints; + + protected AuthorizationServerTokenServicesFactoryBean() { + } + + public AuthorizationServerTokenServicesFactoryBean( + AuthorizationServerEndpointsConfigurer endpoints) { + this.endpoints = endpoints; + } + + @Override + public Class getObjectType() { + return AuthorizationServerTokenServices.class; + } + + @Override + protected AuthorizationServerTokenServices createInstance() throws Exception { + return endpoints.getDefaultAuthorizationServerTokenServices(); + } + } + @Component protected static class TokenKeyEndpointRegistrar implements BeanDefinitionRegistryPostProcessor { diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java index a993afbf9..d38593d54 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/config/annotation/TokenServicesMultipleBeansTests.java @@ -17,6 +17,7 @@ package org.springframework.security.oauth2.config.annotation; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,6 +27,9 @@ import org.springframework.security.oauth2.config.annotation.TokenServicesMultipleBeansTests.BrokenOAuthApplication; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.security.oauth2.provider.token.ConsumerTokenServices; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -40,16 +44,25 @@ @WebAppConfiguration public class TokenServicesMultipleBeansTests { - @Autowired + @Autowired(required=false) private ResourceServerTokenServices tokenServices; + @Autowired + private AuthorizationServerTokenServices authServerTokenServices; + + @Autowired + private ConsumerTokenServices consumerTokenServices; + @Test public void test() { - assertNotNull(tokenServices); + assertNull(tokenServices); + assertNotNull(authServerTokenServices); + assertNotNull(consumerTokenServices); } @Configuration @EnableAuthorizationServer + @EnableResourceServer @EnableWebSecurity protected static class BrokenOAuthApplication extends AuthorizationServerConfigurerAdapter { } diff --git a/tests/annotation/jwt/src/test/java/demo/ApplicationTests.java b/tests/annotation/jwt/src/test/java/demo/ApplicationTests.java index 82a5ffee6..13b37ed7a 100644 --- a/tests/annotation/jwt/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/jwt/src/test/java/demo/ApplicationTests.java @@ -6,13 +6,12 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.TestRestTemplate; import org.springframework.http.HttpStatus; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; @@ -28,8 +27,7 @@ public class ApplicationTests { private int port; @Autowired - @Qualifier("defaultAuthorizationServerTokenServices") - private DefaultTokenServices tokenServices; + private AuthorizationServerTokenServices tokenServices; @Test public void tokenStoreIsJwt() { diff --git a/tests/annotation/jwt/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/jwt/src/test/java/demo/RefreshTokenSupportTests.java index 295fbdabf..141d73969 100644 --- a/tests/annotation/jwt/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/jwt/src/test/java/demo/RefreshTokenSupportTests.java @@ -3,11 +3,10 @@ import static org.junit.Assert.assertEquals; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpStatus; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.test.util.ReflectionTestUtils; @@ -21,8 +20,7 @@ public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { @Autowired - @Qualifier("defaultAuthorizationServerTokenServices") - private DefaultTokenServices services; + private AuthorizationServerTokenServices services; protected void verifyAccessTokens(OAuth2AccessToken oldAccessToken, OAuth2AccessToken newAccessToken) { // make sure the new access token can be used. From a94a67baad3685fc97b62824a910e1f5e5908038 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 21 Feb 2017 17:28:37 +0000 Subject: [PATCH 11/15] Update to Spring Boot 1.5.1 --- .../src/main/java/demo/Application.java | 7 ++----- .../src/main/resources/application.yml | 3 +++ .../src/test/java/demo/ApplicationTests.java | 13 +++++-------- .../demo/AuthorizationCodeProviderTests.java | 3 --- .../demo/ClientCredentialsProviderTests.java | 3 --- .../test/java/demo/ImplicitProviderTests.java | 3 --- .../java/demo/ProtectedResourceTests.java | 3 --- .../java/demo/RefreshTokenSupportTests.java | 3 --- .../ResourceOwnerPasswordProviderTests.java | 3 --- .../client/src/main/resources/application.yml | 3 +++ .../test/java/client/ApplicationTests.java | 13 +++++-------- .../client/ClientServerInteractionTests.java | 8 +++++--- tests/annotation/common/pom.xml | 9 --------- .../common/AbstractIntegrationTests.java | 14 +++++++------- .../java/sparklr/common/HttpTestUtils.java | 4 ++-- .../src/main/java/demo/Application.java | 5 ++--- .../src/main/resources/application.yml | 3 +++ .../src/test/java/demo/ApplicationTests.java | 12 ++++-------- .../demo/ClientCredentialsProviderTests.java | 2 -- .../src/main/java/demo/Application.java | 5 ++--- .../src/main/resources/application.yml | 3 +++ .../src/test/java/demo/ApplicationTests.java | 12 ++++-------- .../demo/AuthorizationCodeProviderTests.java | 2 -- .../demo/ClientCredentialsProviderTests.java | 3 --- .../test/java/demo/CustomProviderTests.java | 4 +--- .../test/java/demo/ImplicitProviderTests.java | 4 +--- .../java/demo/ProtectedResourceTests.java | 3 --- .../java/demo/RefreshTokenSupportTests.java | 3 --- .../ResourceOwnerPasswordProviderTests.java | 3 --- .../form/src/main/java/demo/Application.java | 7 ++----- .../form/src/main/resources/application.yml | 3 +++ .../src/test/java/demo/ApplicationTests.java | 12 ++++-------- .../demo/AuthorizationCodeProviderTests.java | 3 --- .../demo/ClientCredentialsProviderTests.java | 2 -- .../test/java/demo/ImplicitProviderTests.java | 3 --- .../java/demo/ProtectedResourceTests.java | 3 --- .../java/demo/RefreshTokenSupportTests.java | 3 --- .../ResourceOwnerPasswordProviderTests.java | 2 -- .../jaxb/src/main/java/demo/Application.java | 7 ++----- .../jaxb/src/main/resources/application.yml | 3 +++ .../src/test/java/demo/ApplicationTests.java | 12 ++++-------- .../demo/AuthorizationCodeProviderTests.java | 2 -- .../demo/ClientCredentialsProviderTests.java | 2 -- .../test/java/demo/ImplicitProviderTests.java | 4 +--- .../java/demo/ProtectedResourceTests.java | 2 -- .../java/demo/RefreshTokenSupportTests.java | 2 -- .../ResourceOwnerPasswordProviderTests.java | 2 -- .../jdbc/src/main/java/demo/Application.java | 2 +- .../jdbc/src/main/resources/application.yml | 4 ++++ .../src/test/java/demo/ApplicationTests.java | 15 +++++---------- .../demo/AuthorizationCodeProviderTests.java | 4 ++-- .../demo/ClientCredentialsProviderTests.java | 4 ++-- .../test/java/demo/ImplicitProviderTests.java | 4 ++-- .../java/demo/ProtectedResourceTests.java | 4 ++-- .../java/demo/RefreshTokenSupportTests.java | 4 ++-- .../ResourceOwnerPasswordProviderTests.java | 4 ++-- .../jpa/src/main/java/demo/Application.java | 2 +- .../jpa/src/main/resources/application.yml | 3 +++ .../src/test/java/demo/ApplicationTests.java | 12 ++++-------- .../AuthorizationCodeProviderCookieTests.java | 2 -- .../demo/AuthorizationCodeProviderTests.java | 2 -- .../demo/ClientCredentialsProviderTests.java | 3 --- .../test/java/demo/ImplicitProviderTests.java | 4 +--- .../java/demo/ProtectedResourceTests.java | 3 --- .../java/demo/RefreshTokenSupportTests.java | 3 --- .../ResourceOwnerPasswordProviderTests.java | 4 +--- .../jwt/src/main/java/demo/Application.java | 7 ++----- .../jwt/src/main/resources/application.yml | 3 +++ .../src/test/java/demo/ApplicationTests.java | 19 ++++++++----------- .../demo/AuthorizationCodeProviderTests.java | 3 --- .../demo/ClientCredentialsProviderTests.java | 4 +--- .../test/java/demo/ImplicitProviderTests.java | 3 --- .../java/demo/ProtectedResourceTests.java | 3 --- .../java/demo/RefreshTokenSupportTests.java | 2 -- .../ResourceOwnerPasswordProviderTests.java | 4 +--- .../src/main/resources/application.yml | 3 +++ .../src/test/java/demo/ApplicationTests.java | 12 ++++-------- .../demo/AuthorizationCodeProviderTests.java | 2 -- .../demo/ClientCredentialsProviderTests.java | 4 +--- .../test/java/demo/ImplicitProviderTests.java | 3 --- .../java/demo/ProtectedResourceTests.java | 2 -- .../java/demo/RefreshTokenSupportTests.java | 3 --- .../ResourceOwnerPasswordProviderTests.java | 3 --- ...letPathClientCredentialsProviderTests.java | 11 ++++------- .../multi/src/main/java/demo/Application.java | 4 +++- .../src/test/java/demo/ApplicationTests.java | 12 ++++-------- .../demo/AuthorizationCodeProviderTests.java | 3 --- .../demo/ClientCredentialsProviderTests.java | 3 --- .../test/java/demo/ImplicitProviderTests.java | 3 --- .../java/demo/ProtectedResourceTests.java | 2 -- .../java/demo/RefreshTokenSupportTests.java | 3 --- .../ResourceOwnerPasswordProviderTests.java | 2 -- tests/annotation/pom.xml | 4 ++-- .../src/main/java/demo/Application.java | 8 ++------ .../src/main/resources/application.yml | 3 +++ .../src/test/java/demo/ApplicationTests.java | 12 ++++-------- .../java/demo/ProtectedResourceTests.java | 3 --- .../src/main/resources/application.yml | 3 +++ .../src/test/java/demo/ApplicationTests.java | 9 +++------ .../AuthorizationCodeProviderCookieTests.java | 2 -- .../demo/AuthorizationCodeProviderTests.java | 2 -- .../demo/ClientCredentialsProviderTests.java | 3 --- .../java/demo/GlobalMethodSecurityTests.java | 12 ++++++------ .../test/java/demo/ImplicitProviderTests.java | 4 +--- .../java/demo/ProtectedResourceTests.java | 3 --- .../java/demo/RefreshTokenSupportTests.java | 3 --- .../ResourceOwnerPasswordProviderTests.java | 4 +--- 107 files changed, 162 insertions(+), 349 deletions(-) diff --git a/tests/annotation/approval/src/main/java/demo/Application.java b/tests/annotation/approval/src/main/java/demo/Application.java index 101b9bfb8..82281f84a 100644 --- a/tests/annotation/approval/src/main/java/demo/Application.java +++ b/tests/annotation/approval/src/main/java/demo/Application.java @@ -2,9 +2,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; @@ -19,9 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Configuration -@ComponentScan -@EnableAutoConfiguration +@SpringBootApplication @EnableResourceServer @RestController public class Application { diff --git a/tests/annotation/approval/src/main/resources/application.yml b/tests/annotation/approval/src/main/resources/application.yml index e52b05d1f..99c539833 100644 --- a/tests/annotation/approval/src/main/resources/application.yml +++ b/tests/annotation/approval/src/main/resources/application.yml @@ -6,3 +6,6 @@ management: security: user: password: password + oauth2: + resource: + filter-order: 3 diff --git a/tests/annotation/approval/src/test/java/demo/ApplicationTests.java b/tests/annotation/approval/src/test/java/demo/ApplicationTests.java index 15eca8da6..39d23f056 100644 --- a/tests/annotation/approval/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/approval/src/test/java/demo/ApplicationTests.java @@ -2,15 +2,12 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) public class ApplicationTests { @Test diff --git a/tests/annotation/approval/src/test/java/demo/AuthorizationCodeProviderTests.java b/tests/annotation/approval/src/test/java/demo/AuthorizationCodeProviderTests.java index 3bc5e7dac..7b60325b0 100755 --- a/tests/annotation/approval/src/test/java/demo/AuthorizationCodeProviderTests.java +++ b/tests/annotation/approval/src/test/java/demo/AuthorizationCodeProviderTests.java @@ -14,14 +14,11 @@ import static org.junit.Assert.assertTrue; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractAuthorizationCodeProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderTests extends AbstractAuthorizationCodeProviderTests { protected void verifyAuthorizationPage(String page) { diff --git a/tests/annotation/approval/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/approval/src/test/java/demo/ClientCredentialsProviderTests.java index af8190074..857ff7fa0 100644 --- a/tests/annotation/approval/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/approval/src/test/java/demo/ClientCredentialsProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractClientCredentialsProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { diff --git a/tests/annotation/approval/src/test/java/demo/ImplicitProviderTests.java b/tests/annotation/approval/src/test/java/demo/ImplicitProviderTests.java index 7a8958187..0e34ccc7d 100644 --- a/tests/annotation/approval/src/test/java/demo/ImplicitProviderTests.java +++ b/tests/annotation/approval/src/test/java/demo/ImplicitProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractImplicitProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ImplicitProviderTests extends AbstractImplicitProviderTests { } diff --git a/tests/annotation/approval/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/approval/src/test/java/demo/ProtectedResourceTests.java index c752cbe12..302b30f96 100644 --- a/tests/annotation/approval/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/approval/src/test/java/demo/ProtectedResourceTests.java @@ -13,15 +13,12 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractProtectedResourceTests; /** * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { } diff --git a/tests/annotation/approval/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/approval/src/test/java/demo/RefreshTokenSupportTests.java index 4ed370eea..417bac867 100644 --- a/tests/annotation/approval/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/approval/src/test/java/demo/RefreshTokenSupportTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractRefreshTokenSupportTests; /** * @author Ryan Heaton * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { } diff --git a/tests/annotation/approval/src/test/java/demo/ResourceOwnerPasswordProviderTests.java b/tests/annotation/approval/src/test/java/demo/ResourceOwnerPasswordProviderTests.java index aa5786098..5682e05a6 100644 --- a/tests/annotation/approval/src/test/java/demo/ResourceOwnerPasswordProviderTests.java +++ b/tests/annotation/approval/src/test/java/demo/ResourceOwnerPasswordProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractResourceOwnerPasswordProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ResourceOwnerPasswordProviderTests extends AbstractResourceOwnerPasswordProviderTests { } diff --git a/tests/annotation/client/src/main/resources/application.yml b/tests/annotation/client/src/main/resources/application.yml index 72f3eb459..e223ea3ad 100644 --- a/tests/annotation/client/src/main/resources/application.yml +++ b/tests/annotation/client/src/main/resources/application.yml @@ -8,3 +8,6 @@ server: security: basic: enabled: false + oauth2: + resource: + filter-order: 3 diff --git a/tests/annotation/client/src/test/java/client/ApplicationTests.java b/tests/annotation/client/src/test/java/client/ApplicationTests.java index 8c5aef34a..971e2b4a0 100644 --- a/tests/annotation/client/src/test/java/client/ApplicationTests.java +++ b/tests/annotation/client/src/test/java/client/ApplicationTests.java @@ -2,15 +2,12 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = ClientApplication.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT, classes=ClientApplication.class) public class ApplicationTests { @Test diff --git a/tests/annotation/client/src/test/java/client/ClientServerInteractionTests.java b/tests/annotation/client/src/test/java/client/ClientServerInteractionTests.java index 64db5054c..f078ff6e3 100644 --- a/tests/annotation/client/src/test/java/client/ClientServerInteractionTests.java +++ b/tests/annotation/client/src/test/java/client/ClientServerInteractionTests.java @@ -6,7 +6,8 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; import org.springframework.security.oauth2.client.OAuth2RestOperations; import org.springframework.security.oauth2.client.OAuth2RestTemplate; @@ -19,7 +20,8 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = { ClientApplication.class, CombinedApplication.class }) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = { + ClientApplication.class, CombinedApplication.class }) @ActiveProfiles("combined") public class ClientServerInteractionTests extends AbstractIntegrationTests { @@ -27,7 +29,7 @@ public class ClientServerInteractionTests extends AbstractIntegrationTests { private AuthorizationCodeResourceDetails resource; private OAuth2RestOperations template; - + @Before public void init() { template = new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext()); diff --git a/tests/annotation/common/pom.xml b/tests/annotation/common/pom.xml index 6491f1938..ec1d24e93 100644 --- a/tests/annotation/common/pom.xml +++ b/tests/annotation/common/pom.xml @@ -53,15 +53,6 @@ demo.Application - - - - org.springframework.boot - spring-boot-maven-plugin - - - - spring-snapshots diff --git a/tests/annotation/common/src/main/java/sparklr/common/AbstractIntegrationTests.java b/tests/annotation/common/src/main/java/sparklr/common/AbstractIntegrationTests.java index b35341615..023566427 100644 --- a/tests/annotation/common/src/main/java/sparklr/common/AbstractIntegrationTests.java +++ b/tests/annotation/common/src/main/java/sparklr/common/AbstractIntegrationTests.java @@ -31,7 +31,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.core.io.ClassPathResource; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.converter.HttpMessageConverter; @@ -53,12 +55,10 @@ import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) public abstract class AbstractIntegrationTests { public static final String JAVAX_NET_SSL_TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword"; @@ -84,7 +84,7 @@ public abstract class AbstractIntegrationTests { } } - @Value("${local.server.port}") + @LocalServerPort private int port; @Rule diff --git a/tests/annotation/common/src/main/java/sparklr/common/HttpTestUtils.java b/tests/annotation/common/src/main/java/sparklr/common/HttpTestUtils.java index 8e765c04c..1a0456552 100644 --- a/tests/annotation/common/src/main/java/sparklr/common/HttpTestUtils.java +++ b/tests/annotation/common/src/main/java/sparklr/common/HttpTestUtils.java @@ -9,7 +9,7 @@ import org.junit.rules.MethodRule; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -234,7 +234,7 @@ public RestOperations getRestTemplate() { } public RestOperations createRestTemplate() { - RestTemplate client = new TestRestTemplate(); + RestTemplate client = new TestRestTemplate().getRestTemplate(); return client; } diff --git a/tests/annotation/custom-authentication/src/main/java/demo/Application.java b/tests/annotation/custom-authentication/src/main/java/demo/Application.java index 8566b7eb1..b45985dc4 100644 --- a/tests/annotation/custom-authentication/src/main/java/demo/Application.java +++ b/tests/annotation/custom-authentication/src/main/java/demo/Application.java @@ -2,7 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; @@ -19,8 +19,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -@Configuration -@EnableAutoConfiguration +@SpringBootApplication @EnableResourceServer @RestController public class Application { diff --git a/tests/annotation/custom-authentication/src/main/resources/application.yml b/tests/annotation/custom-authentication/src/main/resources/application.yml index ccf06f8ba..8f7a77341 100644 --- a/tests/annotation/custom-authentication/src/main/resources/application.yml +++ b/tests/annotation/custom-authentication/src/main/resources/application.yml @@ -6,6 +6,9 @@ management: security: user: password: password + oauth2: + resource: + filter-order: 3 logging: level: # org.springframework.security: DEBUG \ No newline at end of file diff --git a/tests/annotation/custom-authentication/src/test/java/demo/ApplicationTests.java b/tests/annotation/custom-authentication/src/test/java/demo/ApplicationTests.java index 15eca8da6..1f2353d99 100644 --- a/tests/annotation/custom-authentication/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/custom-authentication/src/test/java/demo/ApplicationTests.java @@ -2,15 +2,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest public class ApplicationTests { @Test diff --git a/tests/annotation/custom-authentication/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/custom-authentication/src/test/java/demo/ClientCredentialsProviderTests.java index f4dd6da46..f0734bc81 100644 --- a/tests/annotation/custom-authentication/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/custom-authentication/src/test/java/demo/ClientCredentialsProviderTests.java @@ -9,7 +9,6 @@ import org.junit.Before; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -30,7 +29,6 @@ * * @author michaeltecourt */ -@SpringApplicationConfiguration(classes = Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { protected URI tokenUri; diff --git a/tests/annotation/custom-grant/src/main/java/demo/Application.java b/tests/annotation/custom-grant/src/main/java/demo/Application.java index 7dd777944..df08bbfe9 100644 --- a/tests/annotation/custom-grant/src/main/java/demo/Application.java +++ b/tests/annotation/custom-grant/src/main/java/demo/Application.java @@ -6,7 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; @@ -24,8 +24,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -@Configuration -@EnableAutoConfiguration +@SpringBootApplication @EnableResourceServer @RestController public class Application { diff --git a/tests/annotation/custom-grant/src/main/resources/application.yml b/tests/annotation/custom-grant/src/main/resources/application.yml index ccf06f8ba..8f7a77341 100644 --- a/tests/annotation/custom-grant/src/main/resources/application.yml +++ b/tests/annotation/custom-grant/src/main/resources/application.yml @@ -6,6 +6,9 @@ management: security: user: password: password + oauth2: + resource: + filter-order: 3 logging: level: # org.springframework.security: DEBUG \ No newline at end of file diff --git a/tests/annotation/custom-grant/src/test/java/demo/ApplicationTests.java b/tests/annotation/custom-grant/src/test/java/demo/ApplicationTests.java index 15eca8da6..34f49b849 100644 --- a/tests/annotation/custom-grant/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/custom-grant/src/test/java/demo/ApplicationTests.java @@ -2,15 +2,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest public class ApplicationTests { @Test diff --git a/tests/annotation/custom-grant/src/test/java/demo/AuthorizationCodeProviderTests.java b/tests/annotation/custom-grant/src/test/java/demo/AuthorizationCodeProviderTests.java index 49a38d4ab..34cdef81b 100755 --- a/tests/annotation/custom-grant/src/test/java/demo/AuthorizationCodeProviderTests.java +++ b/tests/annotation/custom-grant/src/test/java/demo/AuthorizationCodeProviderTests.java @@ -17,7 +17,6 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -28,7 +27,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderTests extends AbstractAuthorizationCodeProviderTests { @Test diff --git a/tests/annotation/custom-grant/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/custom-grant/src/test/java/demo/ClientCredentialsProviderTests.java index af8190074..857ff7fa0 100644 --- a/tests/annotation/custom-grant/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/custom-grant/src/test/java/demo/ClientCredentialsProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractClientCredentialsProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { diff --git a/tests/annotation/custom-grant/src/test/java/demo/CustomProviderTests.java b/tests/annotation/custom-grant/src/test/java/demo/CustomProviderTests.java index 219553795..bd6897227 100644 --- a/tests/annotation/custom-grant/src/test/java/demo/CustomProviderTests.java +++ b/tests/annotation/custom-grant/src/test/java/demo/CustomProviderTests.java @@ -1,11 +1,10 @@ package demo; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.util.Map; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,7 +16,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class CustomProviderTests extends AbstractIntegrationTests { @Test diff --git a/tests/annotation/custom-grant/src/test/java/demo/ImplicitProviderTests.java b/tests/annotation/custom-grant/src/test/java/demo/ImplicitProviderTests.java index 92379335a..0945dffce 100644 --- a/tests/annotation/custom-grant/src/test/java/demo/ImplicitProviderTests.java +++ b/tests/annotation/custom-grant/src/test/java/demo/ImplicitProviderTests.java @@ -13,8 +13,7 @@ import java.util.concurrent.Future; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -25,7 +24,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class ImplicitProviderTests extends AbstractImplicitProviderTests { @Test diff --git a/tests/annotation/custom-grant/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/custom-grant/src/test/java/demo/ProtectedResourceTests.java index c752cbe12..302b30f96 100644 --- a/tests/annotation/custom-grant/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/custom-grant/src/test/java/demo/ProtectedResourceTests.java @@ -13,15 +13,12 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractProtectedResourceTests; /** * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { } diff --git a/tests/annotation/custom-grant/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/custom-grant/src/test/java/demo/RefreshTokenSupportTests.java index 4ed370eea..417bac867 100644 --- a/tests/annotation/custom-grant/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/custom-grant/src/test/java/demo/RefreshTokenSupportTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractRefreshTokenSupportTests; /** * @author Ryan Heaton * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { } diff --git a/tests/annotation/custom-grant/src/test/java/demo/ResourceOwnerPasswordProviderTests.java b/tests/annotation/custom-grant/src/test/java/demo/ResourceOwnerPasswordProviderTests.java index aa5786098..5682e05a6 100644 --- a/tests/annotation/custom-grant/src/test/java/demo/ResourceOwnerPasswordProviderTests.java +++ b/tests/annotation/custom-grant/src/test/java/demo/ResourceOwnerPasswordProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractResourceOwnerPasswordProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ResourceOwnerPasswordProviderTests extends AbstractResourceOwnerPasswordProviderTests { } diff --git a/tests/annotation/form/src/main/java/demo/Application.java b/tests/annotation/form/src/main/java/demo/Application.java index 696029d71..cba6c9f6f 100644 --- a/tests/annotation/form/src/main/java/demo/Application.java +++ b/tests/annotation/form/src/main/java/demo/Application.java @@ -2,8 +2,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.context.annotation.ComponentScan; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; @@ -15,9 +14,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Configuration -@ComponentScan -@EnableAutoConfiguration +@SpringBootApplication @EnableResourceServer @RestController public class Application { diff --git a/tests/annotation/form/src/main/resources/application.yml b/tests/annotation/form/src/main/resources/application.yml index dbb4d4d3e..57c6bd9cc 100644 --- a/tests/annotation/form/src/main/resources/application.yml +++ b/tests/annotation/form/src/main/resources/application.yml @@ -6,6 +6,9 @@ management: security: user: password: password + oauth2: + resource: + filter-order: 3 logging: level: org.springframework.security: WARN diff --git a/tests/annotation/form/src/test/java/demo/ApplicationTests.java b/tests/annotation/form/src/test/java/demo/ApplicationTests.java index 15eca8da6..34f49b849 100644 --- a/tests/annotation/form/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/form/src/test/java/demo/ApplicationTests.java @@ -2,15 +2,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest public class ApplicationTests { @Test diff --git a/tests/annotation/form/src/test/java/demo/AuthorizationCodeProviderTests.java b/tests/annotation/form/src/test/java/demo/AuthorizationCodeProviderTests.java index 632098bf1..2ef50fde6 100755 --- a/tests/annotation/form/src/test/java/demo/AuthorizationCodeProviderTests.java +++ b/tests/annotation/form/src/test/java/demo/AuthorizationCodeProviderTests.java @@ -12,14 +12,11 @@ */ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractAuthorizationCodeProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderTests extends AbstractAuthorizationCodeProviderTests { } diff --git a/tests/annotation/form/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/form/src/test/java/demo/ClientCredentialsProviderTests.java index 3d9ccf4f6..d70b43c90 100644 --- a/tests/annotation/form/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/form/src/test/java/demo/ClientCredentialsProviderTests.java @@ -8,7 +8,6 @@ import java.io.IOException; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpResponse; @@ -24,7 +23,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { private HttpHeaders responseHeaders; diff --git a/tests/annotation/form/src/test/java/demo/ImplicitProviderTests.java b/tests/annotation/form/src/test/java/demo/ImplicitProviderTests.java index 7a8958187..0e34ccc7d 100644 --- a/tests/annotation/form/src/test/java/demo/ImplicitProviderTests.java +++ b/tests/annotation/form/src/test/java/demo/ImplicitProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractImplicitProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ImplicitProviderTests extends AbstractImplicitProviderTests { } diff --git a/tests/annotation/form/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/form/src/test/java/demo/ProtectedResourceTests.java index c752cbe12..302b30f96 100644 --- a/tests/annotation/form/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/form/src/test/java/demo/ProtectedResourceTests.java @@ -13,15 +13,12 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractProtectedResourceTests; /** * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { } diff --git a/tests/annotation/form/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/form/src/test/java/demo/RefreshTokenSupportTests.java index 4ed370eea..417bac867 100644 --- a/tests/annotation/form/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/form/src/test/java/demo/RefreshTokenSupportTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractRefreshTokenSupportTests; /** * @author Ryan Heaton * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { } diff --git a/tests/annotation/form/src/test/java/demo/ResourceOwnerPasswordProviderTests.java b/tests/annotation/form/src/test/java/demo/ResourceOwnerPasswordProviderTests.java index 6eaa554eb..7fe135878 100644 --- a/tests/annotation/form/src/test/java/demo/ResourceOwnerPasswordProviderTests.java +++ b/tests/annotation/form/src/test/java/demo/ResourceOwnerPasswordProviderTests.java @@ -1,6 +1,5 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.test.BeforeOAuth2Context; import org.springframework.security.oauth2.common.AuthenticationScheme; @@ -10,7 +9,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class ResourceOwnerPasswordProviderTests extends AbstractResourceOwnerPasswordProviderTests { diff --git a/tests/annotation/jaxb/src/main/java/demo/Application.java b/tests/annotation/jaxb/src/main/java/demo/Application.java index 956c80ffd..a9ec20e09 100644 --- a/tests/annotation/jaxb/src/main/java/demo/Application.java +++ b/tests/annotation/jaxb/src/main/java/demo/Application.java @@ -5,8 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.context.annotation.ComponentScan; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.security.authentication.AuthenticationManager; @@ -28,9 +27,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -@Configuration -@ComponentScan -@EnableAutoConfiguration +@SpringBootApplication @EnableResourceServer @RestController public class Application extends WebMvcConfigurerAdapter { diff --git a/tests/annotation/jaxb/src/main/resources/application.yml b/tests/annotation/jaxb/src/main/resources/application.yml index a9c0149f0..c88891b00 100644 --- a/tests/annotation/jaxb/src/main/resources/application.yml +++ b/tests/annotation/jaxb/src/main/resources/application.yml @@ -6,3 +6,6 @@ management: security: user: password: password + oauth2: + resource: + filter-order: 3 diff --git a/tests/annotation/jaxb/src/test/java/demo/ApplicationTests.java b/tests/annotation/jaxb/src/test/java/demo/ApplicationTests.java index 15eca8da6..34f49b849 100644 --- a/tests/annotation/jaxb/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/jaxb/src/test/java/demo/ApplicationTests.java @@ -2,15 +2,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest public class ApplicationTests { @Test diff --git a/tests/annotation/jaxb/src/test/java/demo/AuthorizationCodeProviderTests.java b/tests/annotation/jaxb/src/test/java/demo/AuthorizationCodeProviderTests.java index 7d8889a6c..0333fa789 100755 --- a/tests/annotation/jaxb/src/test/java/demo/AuthorizationCodeProviderTests.java +++ b/tests/annotation/jaxb/src/test/java/demo/AuthorizationCodeProviderTests.java @@ -18,7 +18,6 @@ import java.util.Collection; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; @@ -28,7 +27,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderTests extends AbstractAuthorizationCodeProviderTests { @Test diff --git a/tests/annotation/jaxb/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/jaxb/src/test/java/demo/ClientCredentialsProviderTests.java index ef9ade0cd..b24e666c8 100644 --- a/tests/annotation/jaxb/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/jaxb/src/test/java/demo/ClientCredentialsProviderTests.java @@ -3,7 +3,6 @@ import java.util.Collection; import java.util.List; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.converter.HttpMessageConverter; @@ -12,7 +11,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { @Override diff --git a/tests/annotation/jaxb/src/test/java/demo/ImplicitProviderTests.java b/tests/annotation/jaxb/src/test/java/demo/ImplicitProviderTests.java index 874e9ca09..d5bbfd381 100644 --- a/tests/annotation/jaxb/src/test/java/demo/ImplicitProviderTests.java +++ b/tests/annotation/jaxb/src/test/java/demo/ImplicitProviderTests.java @@ -13,8 +13,7 @@ import java.util.concurrent.Future; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; @@ -26,7 +25,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class ImplicitProviderTests extends AbstractImplicitProviderTests { @Test diff --git a/tests/annotation/jaxb/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/jaxb/src/test/java/demo/ProtectedResourceTests.java index d47fb4893..0a539c5f4 100644 --- a/tests/annotation/jaxb/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/jaxb/src/test/java/demo/ProtectedResourceTests.java @@ -15,7 +15,6 @@ import java.util.Collection; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.converter.HttpMessageConverter; import sparklr.common.AbstractProtectedResourceTests; @@ -24,7 +23,6 @@ * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { @Override diff --git a/tests/annotation/jaxb/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/jaxb/src/test/java/demo/RefreshTokenSupportTests.java index adcaa1326..06804a151 100644 --- a/tests/annotation/jaxb/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/jaxb/src/test/java/demo/RefreshTokenSupportTests.java @@ -2,7 +2,6 @@ import java.util.Collection; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.converter.HttpMessageConverter; import sparklr.common.AbstractRefreshTokenSupportTests; @@ -11,7 +10,6 @@ * @author Ryan Heaton * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { @Override diff --git a/tests/annotation/jaxb/src/test/java/demo/ResourceOwnerPasswordProviderTests.java b/tests/annotation/jaxb/src/test/java/demo/ResourceOwnerPasswordProviderTests.java index c41de197c..c69f87d8a 100644 --- a/tests/annotation/jaxb/src/test/java/demo/ResourceOwnerPasswordProviderTests.java +++ b/tests/annotation/jaxb/src/test/java/demo/ResourceOwnerPasswordProviderTests.java @@ -2,7 +2,6 @@ import java.util.Collection; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.converter.HttpMessageConverter; import sparklr.common.AbstractResourceOwnerPasswordProviderTests; @@ -10,7 +9,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ResourceOwnerPasswordProviderTests extends AbstractResourceOwnerPasswordProviderTests { @Override diff --git a/tests/annotation/jdbc/src/main/java/demo/Application.java b/tests/annotation/jdbc/src/main/java/demo/Application.java index 36f243d80..300e4f250 100644 --- a/tests/annotation/jdbc/src/main/java/demo/Application.java +++ b/tests/annotation/jdbc/src/main/java/demo/Application.java @@ -129,7 +129,7 @@ public void configure(ClientDetailsServiceConfigurer clients) throws Exception { public void init(AuthenticationManagerBuilder auth) throws Exception { // @formatter:off auth.jdbcAuthentication().dataSource(dataSource).withUser("dave") - .password("secret").roles("USER"); + .password("secret").roles("USER", "ACTUATOR"); // @formatter:on } diff --git a/tests/annotation/jdbc/src/main/resources/application.yml b/tests/annotation/jdbc/src/main/resources/application.yml index da08708a2..d83b7e17a 100644 --- a/tests/annotation/jdbc/src/main/resources/application.yml +++ b/tests/annotation/jdbc/src/main/resources/application.yml @@ -3,6 +3,10 @@ spring: name: jdbc management: context_path: /admin +security: + oauth2: + resource: + filter-order: 3 logging: level: # org.springframework.security: DEBUG diff --git a/tests/annotation/jdbc/src/test/java/demo/ApplicationTests.java b/tests/annotation/jdbc/src/test/java/demo/ApplicationTests.java index ac31a7866..20748d419 100644 --- a/tests/annotation/jdbc/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/jdbc/src/test/java/demo/ApplicationTests.java @@ -3,22 +3,17 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.context.ContextConfiguration; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") -public class ApplicationTests { +import sparklr.common.AbstractIntegrationTests; + +@ContextConfiguration(classes=Application.class) +public class ApplicationTests extends AbstractIntegrationTests { @Autowired private TokenStore tokenStore; diff --git a/tests/annotation/jdbc/src/test/java/demo/AuthorizationCodeProviderTests.java b/tests/annotation/jdbc/src/test/java/demo/AuthorizationCodeProviderTests.java index 40787edad..5882b7cd3 100755 --- a/tests/annotation/jdbc/src/test/java/demo/AuthorizationCodeProviderTests.java +++ b/tests/annotation/jdbc/src/test/java/demo/AuthorizationCodeProviderTests.java @@ -15,14 +15,14 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.ContextConfiguration; import sparklr.common.AbstractAuthorizationCodeProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) +@ContextConfiguration(classes=Application.class) public class AuthorizationCodeProviderTests extends AbstractAuthorizationCodeProviderTests { protected String getPassword() { diff --git a/tests/annotation/jdbc/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/jdbc/src/test/java/demo/ClientCredentialsProviderTests.java index af8190074..e23fe4f49 100644 --- a/tests/annotation/jdbc/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/jdbc/src/test/java/demo/ClientCredentialsProviderTests.java @@ -1,13 +1,13 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.ContextConfiguration; import sparklr.common.AbstractClientCredentialsProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) +@ContextConfiguration(classes=Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { diff --git a/tests/annotation/jdbc/src/test/java/demo/ImplicitProviderTests.java b/tests/annotation/jdbc/src/test/java/demo/ImplicitProviderTests.java index 89b5a1ef8..225c53741 100644 --- a/tests/annotation/jdbc/src/test/java/demo/ImplicitProviderTests.java +++ b/tests/annotation/jdbc/src/test/java/demo/ImplicitProviderTests.java @@ -1,13 +1,13 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.ContextConfiguration; import sparklr.common.AbstractImplicitProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) +@ContextConfiguration(classes=Application.class) public class ImplicitProviderTests extends AbstractImplicitProviderTests { protected String getPassword() { diff --git a/tests/annotation/jdbc/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/jdbc/src/test/java/demo/ProtectedResourceTests.java index c752cbe12..0e2acc8d9 100644 --- a/tests/annotation/jdbc/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/jdbc/src/test/java/demo/ProtectedResourceTests.java @@ -13,7 +13,7 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.ContextConfiguration; import sparklr.common.AbstractProtectedResourceTests; @@ -21,7 +21,7 @@ * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) +@ContextConfiguration(classes=Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { } diff --git a/tests/annotation/jdbc/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/jdbc/src/test/java/demo/RefreshTokenSupportTests.java index 299e66005..9d2a59ca6 100644 --- a/tests/annotation/jdbc/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/jdbc/src/test/java/demo/RefreshTokenSupportTests.java @@ -1,6 +1,6 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.ContextConfiguration; import sparklr.common.AbstractRefreshTokenSupportTests; @@ -8,7 +8,7 @@ * @author Ryan Heaton * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) +@ContextConfiguration(classes=Application.class) public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { protected String getPassword() { return "secret"; diff --git a/tests/annotation/jdbc/src/test/java/demo/ResourceOwnerPasswordProviderTests.java b/tests/annotation/jdbc/src/test/java/demo/ResourceOwnerPasswordProviderTests.java index 65c784f57..408aeae50 100644 --- a/tests/annotation/jdbc/src/test/java/demo/ResourceOwnerPasswordProviderTests.java +++ b/tests/annotation/jdbc/src/test/java/demo/ResourceOwnerPasswordProviderTests.java @@ -4,16 +4,16 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpStatus; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; +import org.springframework.test.context.ContextConfiguration; import sparklr.common.AbstractResourceOwnerPasswordProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) +@ContextConfiguration(classes=Application.class) public class ResourceOwnerPasswordProviderTests extends AbstractResourceOwnerPasswordProviderTests { protected String getPassword() { diff --git a/tests/annotation/jpa/src/main/java/demo/Application.java b/tests/annotation/jpa/src/main/java/demo/Application.java index 5cb7f67a1..20b5aecbc 100644 --- a/tests/annotation/jpa/src/main/java/demo/Application.java +++ b/tests/annotation/jpa/src/main/java/demo/Application.java @@ -95,7 +95,7 @@ public void configure(ClientDetailsServiceConfigurer clients) throws Exception { @Autowired public void authenticationManager(AuthenticationManagerBuilder builder, UserRepository repository) throws Exception { if (repository.count()==0) { - repository.save(new User("user", "password", Arrays.asList(new Role("USER")))); + repository.save(new User("user", "password", Arrays.asList(new Role("USER"), new Role("ACTUATOR")))); } builder.userDetailsService(userDetailsService(repository)); } diff --git a/tests/annotation/jpa/src/main/resources/application.yml b/tests/annotation/jpa/src/main/resources/application.yml index faea2bd5a..85e3c2c52 100644 --- a/tests/annotation/jpa/src/main/resources/application.yml +++ b/tests/annotation/jpa/src/main/resources/application.yml @@ -6,6 +6,9 @@ management: security: user: password: password + oauth2: + resource: + filter-order: 3 logging: level: # org.springframework.security: DEBUG diff --git a/tests/annotation/jpa/src/test/java/demo/ApplicationTests.java b/tests/annotation/jpa/src/test/java/demo/ApplicationTests.java index 15eca8da6..34f49b849 100644 --- a/tests/annotation/jpa/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/jpa/src/test/java/demo/ApplicationTests.java @@ -2,15 +2,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest public class ApplicationTests { @Test diff --git a/tests/annotation/jpa/src/test/java/demo/AuthorizationCodeProviderCookieTests.java b/tests/annotation/jpa/src/test/java/demo/AuthorizationCodeProviderCookieTests.java index 7b614e004..e9083e3f3 100644 --- a/tests/annotation/jpa/src/test/java/demo/AuthorizationCodeProviderCookieTests.java +++ b/tests/annotation/jpa/src/test/java/demo/AuthorizationCodeProviderCookieTests.java @@ -16,7 +16,6 @@ import static org.junit.Assert.assertNotNull; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -27,7 +26,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderCookieTests extends AbstractEmptyAuthorizationCodeProviderTests { @Test diff --git a/tests/annotation/jpa/src/test/java/demo/AuthorizationCodeProviderTests.java b/tests/annotation/jpa/src/test/java/demo/AuthorizationCodeProviderTests.java index 49a38d4ab..34cdef81b 100755 --- a/tests/annotation/jpa/src/test/java/demo/AuthorizationCodeProviderTests.java +++ b/tests/annotation/jpa/src/test/java/demo/AuthorizationCodeProviderTests.java @@ -17,7 +17,6 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -28,7 +27,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderTests extends AbstractAuthorizationCodeProviderTests { @Test diff --git a/tests/annotation/jpa/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/jpa/src/test/java/demo/ClientCredentialsProviderTests.java index af8190074..857ff7fa0 100644 --- a/tests/annotation/jpa/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/jpa/src/test/java/demo/ClientCredentialsProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractClientCredentialsProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { diff --git a/tests/annotation/jpa/src/test/java/demo/ImplicitProviderTests.java b/tests/annotation/jpa/src/test/java/demo/ImplicitProviderTests.java index 06ebe766b..ef7c254ce 100644 --- a/tests/annotation/jpa/src/test/java/demo/ImplicitProviderTests.java +++ b/tests/annotation/jpa/src/test/java/demo/ImplicitProviderTests.java @@ -14,8 +14,7 @@ import org.junit.BeforeClass; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -26,7 +25,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class ImplicitProviderTests extends AbstractImplicitProviderTests { @BeforeClass diff --git a/tests/annotation/jpa/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/jpa/src/test/java/demo/ProtectedResourceTests.java index c752cbe12..302b30f96 100644 --- a/tests/annotation/jpa/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/jpa/src/test/java/demo/ProtectedResourceTests.java @@ -13,15 +13,12 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractProtectedResourceTests; /** * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { } diff --git a/tests/annotation/jpa/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/jpa/src/test/java/demo/RefreshTokenSupportTests.java index 4ed370eea..417bac867 100644 --- a/tests/annotation/jpa/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/jpa/src/test/java/demo/RefreshTokenSupportTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractRefreshTokenSupportTests; /** * @author Ryan Heaton * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { } diff --git a/tests/annotation/jpa/src/test/java/demo/ResourceOwnerPasswordProviderTests.java b/tests/annotation/jpa/src/test/java/demo/ResourceOwnerPasswordProviderTests.java index 28a8e9f84..31a0f75b8 100644 --- a/tests/annotation/jpa/src/test/java/demo/ResourceOwnerPasswordProviderTests.java +++ b/tests/annotation/jpa/src/test/java/demo/ResourceOwnerPasswordProviderTests.java @@ -3,8 +3,7 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -14,7 +13,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ResourceOwnerPasswordProviderTests extends AbstractResourceOwnerPasswordProviderTests { @Test diff --git a/tests/annotation/jwt/src/main/java/demo/Application.java b/tests/annotation/jwt/src/main/java/demo/Application.java index 5d5c71d5f..0aebd4b1f 100644 --- a/tests/annotation/jwt/src/main/java/demo/Application.java +++ b/tests/annotation/jwt/src/main/java/demo/Application.java @@ -2,9 +2,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; @@ -17,9 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Configuration -@ComponentScan -@EnableAutoConfiguration +@SpringBootApplication @EnableResourceServer @RestController public class Application { diff --git a/tests/annotation/jwt/src/main/resources/application.yml b/tests/annotation/jwt/src/main/resources/application.yml index ccf06f8ba..8f7a77341 100644 --- a/tests/annotation/jwt/src/main/resources/application.yml +++ b/tests/annotation/jwt/src/main/resources/application.yml @@ -6,6 +6,9 @@ management: security: user: password: password + oauth2: + resource: + filter-order: 3 logging: level: # org.springframework.security: DEBUG \ No newline at end of file diff --git a/tests/annotation/jwt/src/test/java/demo/ApplicationTests.java b/tests/annotation/jwt/src/test/java/demo/ApplicationTests.java index 13b37ed7a..1bb5192c6 100644 --- a/tests/annotation/jwt/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/jwt/src/test/java/demo/ApplicationTests.java @@ -6,24 +6,21 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.util.ReflectionTestUtils; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) public class ApplicationTests { - @Value("${local.server.port}") + @LocalServerPort private int port; @Autowired diff --git a/tests/annotation/jwt/src/test/java/demo/AuthorizationCodeProviderTests.java b/tests/annotation/jwt/src/test/java/demo/AuthorizationCodeProviderTests.java index 632098bf1..2ef50fde6 100755 --- a/tests/annotation/jwt/src/test/java/demo/AuthorizationCodeProviderTests.java +++ b/tests/annotation/jwt/src/test/java/demo/AuthorizationCodeProviderTests.java @@ -12,14 +12,11 @@ */ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractAuthorizationCodeProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderTests extends AbstractAuthorizationCodeProviderTests { } diff --git a/tests/annotation/jwt/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/jwt/src/test/java/demo/ClientCredentialsProviderTests.java index f26763279..26ee5e7cf 100644 --- a/tests/annotation/jwt/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/jwt/src/test/java/demo/ClientCredentialsProviderTests.java @@ -6,8 +6,7 @@ import java.util.Map; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -23,7 +22,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { /** diff --git a/tests/annotation/jwt/src/test/java/demo/ImplicitProviderTests.java b/tests/annotation/jwt/src/test/java/demo/ImplicitProviderTests.java index 7a8958187..0e34ccc7d 100644 --- a/tests/annotation/jwt/src/test/java/demo/ImplicitProviderTests.java +++ b/tests/annotation/jwt/src/test/java/demo/ImplicitProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractImplicitProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ImplicitProviderTests extends AbstractImplicitProviderTests { } diff --git a/tests/annotation/jwt/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/jwt/src/test/java/demo/ProtectedResourceTests.java index c752cbe12..302b30f96 100644 --- a/tests/annotation/jwt/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/jwt/src/test/java/demo/ProtectedResourceTests.java @@ -13,15 +13,12 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractProtectedResourceTests; /** * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { } diff --git a/tests/annotation/jwt/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/jwt/src/test/java/demo/RefreshTokenSupportTests.java index 141d73969..28349045f 100644 --- a/tests/annotation/jwt/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/jwt/src/test/java/demo/RefreshTokenSupportTests.java @@ -3,7 +3,6 @@ import static org.junit.Assert.assertEquals; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpStatus; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; @@ -16,7 +15,6 @@ * @author Ryan Heaton * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { @Autowired diff --git a/tests/annotation/jwt/src/test/java/demo/ResourceOwnerPasswordProviderTests.java b/tests/annotation/jwt/src/test/java/demo/ResourceOwnerPasswordProviderTests.java index d233098a2..4625d154e 100644 --- a/tests/annotation/jwt/src/test/java/demo/ResourceOwnerPasswordProviderTests.java +++ b/tests/annotation/jwt/src/test/java/demo/ResourceOwnerPasswordProviderTests.java @@ -6,8 +6,7 @@ import java.util.Map; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -24,7 +23,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ResourceOwnerPasswordProviderTests extends AbstractResourceOwnerPasswordProviderTests { @Test diff --git a/tests/annotation/mappings/src/main/resources/application.yml b/tests/annotation/mappings/src/main/resources/application.yml index 3b09181d7..5f71e88df 100644 --- a/tests/annotation/mappings/src/main/resources/application.yml +++ b/tests/annotation/mappings/src/main/resources/application.yml @@ -6,6 +6,9 @@ management: security: user: password: password + oauth2: + resource: + filter-order: 3 logging: level: # org.springframework.security: DEBUG diff --git a/tests/annotation/mappings/src/test/java/demo/ApplicationTests.java b/tests/annotation/mappings/src/test/java/demo/ApplicationTests.java index 15eca8da6..34f49b849 100644 --- a/tests/annotation/mappings/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/mappings/src/test/java/demo/ApplicationTests.java @@ -2,15 +2,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest public class ApplicationTests { @Test diff --git a/tests/annotation/mappings/src/test/java/demo/AuthorizationCodeProviderTests.java b/tests/annotation/mappings/src/test/java/demo/AuthorizationCodeProviderTests.java index 04f4e3c31..5fba9cfe0 100755 --- a/tests/annotation/mappings/src/test/java/demo/AuthorizationCodeProviderTests.java +++ b/tests/annotation/mappings/src/test/java/demo/AuthorizationCodeProviderTests.java @@ -19,7 +19,6 @@ import java.util.Arrays; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; @@ -29,7 +28,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderTests extends AbstractAuthorizationCodeProviderTests { @Test diff --git a/tests/annotation/mappings/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/mappings/src/test/java/demo/ClientCredentialsProviderTests.java index 8c5a6ceac..3765b268e 100644 --- a/tests/annotation/mappings/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/mappings/src/test/java/demo/ClientCredentialsProviderTests.java @@ -6,8 +6,7 @@ import java.util.Map; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -23,7 +22,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { /** diff --git a/tests/annotation/mappings/src/test/java/demo/ImplicitProviderTests.java b/tests/annotation/mappings/src/test/java/demo/ImplicitProviderTests.java index 7a8958187..0e34ccc7d 100644 --- a/tests/annotation/mappings/src/test/java/demo/ImplicitProviderTests.java +++ b/tests/annotation/mappings/src/test/java/demo/ImplicitProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractImplicitProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ImplicitProviderTests extends AbstractImplicitProviderTests { } diff --git a/tests/annotation/mappings/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/mappings/src/test/java/demo/ProtectedResourceTests.java index 743d9158b..4f5e357cc 100644 --- a/tests/annotation/mappings/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/mappings/src/test/java/demo/ProtectedResourceTests.java @@ -17,7 +17,6 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -27,7 +26,6 @@ * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { @Test diff --git a/tests/annotation/mappings/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/mappings/src/test/java/demo/RefreshTokenSupportTests.java index 4ed370eea..417bac867 100644 --- a/tests/annotation/mappings/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/mappings/src/test/java/demo/RefreshTokenSupportTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractRefreshTokenSupportTests; /** * @author Ryan Heaton * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { } diff --git a/tests/annotation/mappings/src/test/java/demo/ResourceOwnerPasswordProviderTests.java b/tests/annotation/mappings/src/test/java/demo/ResourceOwnerPasswordProviderTests.java index aa5786098..5682e05a6 100644 --- a/tests/annotation/mappings/src/test/java/demo/ResourceOwnerPasswordProviderTests.java +++ b/tests/annotation/mappings/src/test/java/demo/ResourceOwnerPasswordProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractResourceOwnerPasswordProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ResourceOwnerPasswordProviderTests extends AbstractResourceOwnerPasswordProviderTests { } diff --git a/tests/annotation/mappings/src/test/java/demo/ServletPathClientCredentialsProviderTests.java b/tests/annotation/mappings/src/test/java/demo/ServletPathClientCredentialsProviderTests.java index e5344a065..967c864ef 100644 --- a/tests/annotation/mappings/src/test/java/demo/ServletPathClientCredentialsProviderTests.java +++ b/tests/annotation/mappings/src/test/java/demo/ServletPathClientCredentialsProviderTests.java @@ -5,21 +5,18 @@ import java.util.Map; import org.junit.Test; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; import sparklr.common.AbstractClientCredentialsProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) -@IntegrationTest({"server.servlet_path:/server", "server.port=0"}) -@DirtiesContext +@SpringBootTest(classes=Application.class, properties="server.servlet_path:/server", webEnvironment=WebEnvironment.RANDOM_PORT) public class ServletPathClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { @Test diff --git a/tests/annotation/multi/src/main/java/demo/Application.java b/tests/annotation/multi/src/main/java/demo/Application.java index a740fd5d6..e7e26ebda 100644 --- a/tests/annotation/multi/src/main/java/demo/Application.java +++ b/tests/annotation/multi/src/main/java/demo/Application.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -21,7 +22,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@SpringBootApplication +// TODO: remove the exclusion when Spring Boot 1.5.2 is out +@SpringBootApplication(exclude=OAuth2AutoConfiguration.class) @RestController public class Application { diff --git a/tests/annotation/multi/src/test/java/demo/ApplicationTests.java b/tests/annotation/multi/src/test/java/demo/ApplicationTests.java index 15eca8da6..34f49b849 100644 --- a/tests/annotation/multi/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/multi/src/test/java/demo/ApplicationTests.java @@ -2,15 +2,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest public class ApplicationTests { @Test diff --git a/tests/annotation/multi/src/test/java/demo/AuthorizationCodeProviderTests.java b/tests/annotation/multi/src/test/java/demo/AuthorizationCodeProviderTests.java index 632098bf1..2ef50fde6 100755 --- a/tests/annotation/multi/src/test/java/demo/AuthorizationCodeProviderTests.java +++ b/tests/annotation/multi/src/test/java/demo/AuthorizationCodeProviderTests.java @@ -12,14 +12,11 @@ */ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractAuthorizationCodeProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderTests extends AbstractAuthorizationCodeProviderTests { } diff --git a/tests/annotation/multi/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/multi/src/test/java/demo/ClientCredentialsProviderTests.java index af8190074..857ff7fa0 100644 --- a/tests/annotation/multi/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/multi/src/test/java/demo/ClientCredentialsProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractClientCredentialsProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { diff --git a/tests/annotation/multi/src/test/java/demo/ImplicitProviderTests.java b/tests/annotation/multi/src/test/java/demo/ImplicitProviderTests.java index 7a8958187..0e34ccc7d 100644 --- a/tests/annotation/multi/src/test/java/demo/ImplicitProviderTests.java +++ b/tests/annotation/multi/src/test/java/demo/ImplicitProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractImplicitProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ImplicitProviderTests extends AbstractImplicitProviderTests { } diff --git a/tests/annotation/multi/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/multi/src/test/java/demo/ProtectedResourceTests.java index aa30f20f6..bf1dfd35f 100644 --- a/tests/annotation/multi/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/multi/src/test/java/demo/ProtectedResourceTests.java @@ -17,7 +17,6 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -27,7 +26,6 @@ * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { @Test diff --git a/tests/annotation/multi/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/multi/src/test/java/demo/RefreshTokenSupportTests.java index 4ed370eea..417bac867 100644 --- a/tests/annotation/multi/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/multi/src/test/java/demo/RefreshTokenSupportTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractRefreshTokenSupportTests; /** * @author Ryan Heaton * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { } diff --git a/tests/annotation/multi/src/test/java/demo/ResourceOwnerPasswordProviderTests.java b/tests/annotation/multi/src/test/java/demo/ResourceOwnerPasswordProviderTests.java index 886c48e28..8a82b64dd 100644 --- a/tests/annotation/multi/src/test/java/demo/ResourceOwnerPasswordProviderTests.java +++ b/tests/annotation/multi/src/test/java/demo/ResourceOwnerPasswordProviderTests.java @@ -5,7 +5,6 @@ import java.util.Arrays; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpStatus; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -14,7 +13,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ResourceOwnerPasswordProviderTests extends AbstractResourceOwnerPasswordProviderTests { @Test diff --git a/tests/annotation/pom.xml b/tests/annotation/pom.xml index 411d5a2bf..6f139dded 100644 --- a/tests/annotation/pom.xml +++ b/tests/annotation/pom.xml @@ -30,7 +30,7 @@ org.springframework.boot spring-boot-starter-parent - 1.3.5.RELEASE + 1.5.1.RELEASE @@ -50,7 +50,7 @@ org.springframework.security spring-security-jwt - 1.0.3.RELEASE + 1.0.7.RELEASE diff --git a/tests/annotation/resource/src/main/java/demo/Application.java b/tests/annotation/resource/src/main/java/demo/Application.java index 2758734b1..55498e567 100644 --- a/tests/annotation/resource/src/main/java/demo/Application.java +++ b/tests/annotation/resource/src/main/java/demo/Application.java @@ -1,19 +1,15 @@ package demo; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Configuration -@ComponentScan -@EnableAutoConfiguration +@SpringBootApplication @EnableResourceServer @RestController public class Application { diff --git a/tests/annotation/resource/src/main/resources/application.yml b/tests/annotation/resource/src/main/resources/application.yml index a9c0149f0..c88891b00 100644 --- a/tests/annotation/resource/src/main/resources/application.yml +++ b/tests/annotation/resource/src/main/resources/application.yml @@ -6,3 +6,6 @@ management: security: user: password: password + oauth2: + resource: + filter-order: 3 diff --git a/tests/annotation/resource/src/test/java/demo/ApplicationTests.java b/tests/annotation/resource/src/test/java/demo/ApplicationTests.java index 15eca8da6..34f49b849 100644 --- a/tests/annotation/resource/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/resource/src/test/java/demo/ApplicationTests.java @@ -2,15 +2,11 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@RunWith(SpringRunner.class) +@SpringBootTest public class ApplicationTests { @Test diff --git a/tests/annotation/resource/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/resource/src/test/java/demo/ProtectedResourceTests.java index c752cbe12..302b30f96 100644 --- a/tests/annotation/resource/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/resource/src/test/java/demo/ProtectedResourceTests.java @@ -13,15 +13,12 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractProtectedResourceTests; /** * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { } diff --git a/tests/annotation/vanilla/src/main/resources/application.yml b/tests/annotation/vanilla/src/main/resources/application.yml index 5414a20b3..21a0bac83 100644 --- a/tests/annotation/vanilla/src/main/resources/application.yml +++ b/tests/annotation/vanilla/src/main/resources/application.yml @@ -6,6 +6,9 @@ management: security: user: password: password + oauth2: + resource: + filter-order: 3 logging: level: org.springframework.security: WARN \ No newline at end of file diff --git a/tests/annotation/vanilla/src/test/java/demo/ApplicationTests.java b/tests/annotation/vanilla/src/test/java/demo/ApplicationTests.java index 15eca8da6..6a1f865e8 100644 --- a/tests/annotation/vanilla/src/test/java/demo/ApplicationTests.java +++ b/tests/annotation/vanilla/src/test/java/demo/ApplicationTests.java @@ -2,15 +2,12 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; @RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest("server.port=0") +@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) public class ApplicationTests { @Test diff --git a/tests/annotation/vanilla/src/test/java/demo/AuthorizationCodeProviderCookieTests.java b/tests/annotation/vanilla/src/test/java/demo/AuthorizationCodeProviderCookieTests.java index 7b614e004..e9083e3f3 100644 --- a/tests/annotation/vanilla/src/test/java/demo/AuthorizationCodeProviderCookieTests.java +++ b/tests/annotation/vanilla/src/test/java/demo/AuthorizationCodeProviderCookieTests.java @@ -16,7 +16,6 @@ import static org.junit.Assert.assertNotNull; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -27,7 +26,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderCookieTests extends AbstractEmptyAuthorizationCodeProviderTests { @Test diff --git a/tests/annotation/vanilla/src/test/java/demo/AuthorizationCodeProviderTests.java b/tests/annotation/vanilla/src/test/java/demo/AuthorizationCodeProviderTests.java index 49a38d4ab..34cdef81b 100755 --- a/tests/annotation/vanilla/src/test/java/demo/AuthorizationCodeProviderTests.java +++ b/tests/annotation/vanilla/src/test/java/demo/AuthorizationCodeProviderTests.java @@ -17,7 +17,6 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -28,7 +27,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class AuthorizationCodeProviderTests extends AbstractAuthorizationCodeProviderTests { @Test diff --git a/tests/annotation/vanilla/src/test/java/demo/ClientCredentialsProviderTests.java b/tests/annotation/vanilla/src/test/java/demo/ClientCredentialsProviderTests.java index af8190074..857ff7fa0 100644 --- a/tests/annotation/vanilla/src/test/java/demo/ClientCredentialsProviderTests.java +++ b/tests/annotation/vanilla/src/test/java/demo/ClientCredentialsProviderTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractClientCredentialsProviderTests; /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ClientCredentialsProviderTests extends AbstractClientCredentialsProviderTests { diff --git a/tests/annotation/vanilla/src/test/java/demo/GlobalMethodSecurityTests.java b/tests/annotation/vanilla/src/test/java/demo/GlobalMethodSecurityTests.java index 5977d393d..fcbf10952 100644 --- a/tests/annotation/vanilla/src/test/java/demo/GlobalMethodSecurityTests.java +++ b/tests/annotation/vanilla/src/test/java/demo/GlobalMethodSecurityTests.java @@ -1,23 +1,23 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; -import sparklr.common.AbstractProtectedResourceTests; import demo.GlobalMethodSecurityTests.GlobalSecurityConfiguration; +import sparklr.common.AbstractProtectedResourceTests; -@SpringApplicationConfiguration(classes = { Application.class, - GlobalSecurityConfiguration.class }) +@SpringBootTest(classes = { Application.class, GlobalSecurityConfiguration.class }, webEnvironment=WebEnvironment.RANDOM_PORT) public class GlobalMethodSecurityTests extends AbstractProtectedResourceTests { @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true) - protected static class GlobalSecurityConfiguration extends - GlobalMethodSecurityConfiguration { + protected static class GlobalSecurityConfiguration + extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityExpressionHandler createExpressionHandler() { diff --git a/tests/annotation/vanilla/src/test/java/demo/ImplicitProviderTests.java b/tests/annotation/vanilla/src/test/java/demo/ImplicitProviderTests.java index 06ebe766b..ef7c254ce 100644 --- a/tests/annotation/vanilla/src/test/java/demo/ImplicitProviderTests.java +++ b/tests/annotation/vanilla/src/test/java/demo/ImplicitProviderTests.java @@ -14,8 +14,7 @@ import org.junit.BeforeClass; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -26,7 +25,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes = Application.class) public class ImplicitProviderTests extends AbstractImplicitProviderTests { @BeforeClass diff --git a/tests/annotation/vanilla/src/test/java/demo/ProtectedResourceTests.java b/tests/annotation/vanilla/src/test/java/demo/ProtectedResourceTests.java index c752cbe12..302b30f96 100644 --- a/tests/annotation/vanilla/src/test/java/demo/ProtectedResourceTests.java +++ b/tests/annotation/vanilla/src/test/java/demo/ProtectedResourceTests.java @@ -13,15 +13,12 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractProtectedResourceTests; /** * @author Dave Syer * */ -@SpringApplicationConfiguration(classes = Application.class) public class ProtectedResourceTests extends AbstractProtectedResourceTests { } diff --git a/tests/annotation/vanilla/src/test/java/demo/RefreshTokenSupportTests.java b/tests/annotation/vanilla/src/test/java/demo/RefreshTokenSupportTests.java index 4ed370eea..417bac867 100644 --- a/tests/annotation/vanilla/src/test/java/demo/RefreshTokenSupportTests.java +++ b/tests/annotation/vanilla/src/test/java/demo/RefreshTokenSupportTests.java @@ -1,13 +1,10 @@ package demo; -import org.springframework.boot.test.SpringApplicationConfiguration; - import sparklr.common.AbstractRefreshTokenSupportTests; /** * @author Ryan Heaton * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class RefreshTokenSupportTests extends AbstractRefreshTokenSupportTests { } diff --git a/tests/annotation/vanilla/src/test/java/demo/ResourceOwnerPasswordProviderTests.java b/tests/annotation/vanilla/src/test/java/demo/ResourceOwnerPasswordProviderTests.java index 28a8e9f84..31a0f75b8 100644 --- a/tests/annotation/vanilla/src/test/java/demo/ResourceOwnerPasswordProviderTests.java +++ b/tests/annotation/vanilla/src/test/java/demo/ResourceOwnerPasswordProviderTests.java @@ -3,8 +3,7 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.test.OAuth2ContextConfiguration; @@ -14,7 +13,6 @@ /** * @author Dave Syer */ -@SpringApplicationConfiguration(classes=Application.class) public class ResourceOwnerPasswordProviderTests extends AbstractResourceOwnerPasswordProviderTests { @Test From 2030fbc180776e7301c43540ca037649cab57f69 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 22 Feb 2017 12:38:04 -0500 Subject: [PATCH 12/15] Polish TokenStore supporting Jwk verification Add tests Add javadoc Fix bug to work with UAA 3.11.0 Issue gh-977 --- .../token/store/jwk/JwkAttributes.java | 27 ++ .../token/store/jwk/JwkDefinition.java | 38 ++- .../token/store/jwk/JwkDefinitionSource.java | 55 +++- .../token/store/jwk/JwkException.java | 21 +- .../token/store/jwk/JwkSetConverter.java | 45 ++- .../token/store/jwk/JwkTokenStore.java | 151 +++++++++- .../JwkVerifyingJwtAccessTokenConverter.java | 121 +++++--- .../token/store/jwk/JwtHeaderConverter.java | 17 +- .../token/store/jwk/RSAJwkDefinition.java | 20 ++ .../store/jwk/JwkDefinitionSourceTest.java | 40 +++ .../token/store/jwk/JwkSetConverterTest.java | 284 ++++++++++++++++++ .../token/store/jwk/JwkTokenStoreTest.java | 87 ++++++ ...kVerifyingJwtAccessTokenConverterTest.java | 109 +++++++ .../store/jwk/JwtHeaderConverterTest.java | 58 ++++ .../provider/token/store/jwk/JwtTestUtil.java | 86 ++++++ 15 files changed, 1103 insertions(+), 56 deletions(-) create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSourceTest.java create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverterTest.java create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStoreTest.java create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverterTest.java create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverterTest.java create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtTestUtil.java diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java index 72769bbc0..75343999a 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java @@ -16,20 +16,47 @@ package org.springframework.security.oauth2.provider.token.store.jwk; /** + * Shared attribute values used by {@link JwkTokenStore} and associated collaborators. + * * @author Joe Grandja */ final class JwkAttributes { + + /** + * The "kid" (key ID) parameter used in a JWT header and in a JWK. + */ static final String KEY_ID = "kid"; + /** + * The "kty" (key type) parameter identifies the cryptographic algorithm family + * used by a JWK, for example, "RSA" or "EC". + */ static final String KEY_TYPE = "kty"; + /** + * The "alg" (algorithm) parameter used in a JWT header and in a JWK. + */ static final String ALGORITHM = "alg"; + /** + * The "use" (public key use) parameter identifies the intended use of the public key. + * For example, whether a public key is used for encrypting data or verifying the signature on data. + */ static final String PUBLIC_KEY_USE = "use"; + /** + * The "n" (modulus) parameter contains the modulus value for a RSA public key. + */ static final String RSA_PUBLIC_KEY_MODULUS = "n"; + /** + * The "e" (exponent) parameter contains the exponent value for a RSA public key. + */ static final String RSA_PUBLIC_KEY_EXPONENT = "e"; + /** + * A JWK Set is a JSON object that has a "keys" member + * and its value is an array (set) of JWKs. + */ static final String KEYS = "keys"; } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java index 93caa5a5c..ac33281bd 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java @@ -16,6 +16,10 @@ package org.springframework.security.oauth2.provider.token.store.jwk; /** + * The base representation of a JSON Web Key (JWK). + * + * @see JSON Web Key (JWK) + * * @author Joe Grandja */ abstract class JwkDefinition { @@ -24,6 +28,14 @@ abstract class JwkDefinition { private final PublicKeyUse publicKeyUse; private final CryptoAlgorithm algorithm; + /** + * Creates an instance with the common attributes of a JWK. + * + * @param keyId the Key ID + * @param keyType the Key Type + * @param publicKeyUse the intended use of the Public Key + * @param algorithm the algorithm intended to be used + */ protected JwkDefinition(String keyId, KeyType keyType, PublicKeyUse publicKeyUse, @@ -34,18 +46,31 @@ protected JwkDefinition(String keyId, this.algorithm = algorithm; } + /** + * @return the Key ID ("kid") + */ String getKeyId() { return this.keyId; } + /** + * @return the Key Type ("kty") + */ KeyType getKeyType() { return this.keyType; } + /** + * @return the intended use of the Public Key ("use") + */ PublicKeyUse getPublicKeyUse() { return this.publicKeyUse; } + /** + * + * @return the algorithm intended to be used ("alg") + */ CryptoAlgorithm getAlgorithm() { return this.algorithm; } @@ -74,6 +99,9 @@ public int hashCode() { return result; } + /** + * The defined Key Type ("kty") values. + */ enum KeyType { RSA("RSA"), EC("EC"), @@ -101,6 +129,9 @@ static KeyType fromValue(String value) { } } + /** + * The defined Public Key Use ("use") values. + */ enum PublicKeyUse { SIG("sig"), ENC("enc"); @@ -127,6 +158,9 @@ static PublicKeyUse fromValue(String value) { } } + /** + * The defined Algorithm ("alg") values. + */ enum CryptoAlgorithm { RS256("SHA256withRSA", "RS256", "RSASSA-PKCS1-v1_5 using SHA-256"), RS384("SHA384withRSA", "RS384", "RSASSA-PKCS1-v1_5 using SHA-384"), @@ -154,10 +188,10 @@ String description() { return this.description; } - static CryptoAlgorithm fromStandardName(String standardName) { + static CryptoAlgorithm fromHeaderParamValue(String headerParamValue) { CryptoAlgorithm result = null; for (CryptoAlgorithm algorithm : values()) { - if (algorithm.standardName().equals(standardName)) { + if (algorithm.headerParamValue().equals(headerParamValue)) { result = algorithm; break; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java index a9f241f92..83bf4b85f 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java @@ -20,6 +20,7 @@ import org.springframework.security.jwt.crypto.sign.SignatureVerifier; import java.io.IOException; +import java.io.InputStream; import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; @@ -33,6 +34,14 @@ import java.util.concurrent.atomic.AtomicReference; /** + * A source for JSON Web Key(s) (JWK) that is solely responsible for fetching (and caching) + * the JWK Set (a set of JWKs) from the URL supplied to the constructor. + * + * @see JwkSetConverter + * @see JwkDefinition + * @see SignatureVerifier + * @see JWK Set Format + * * @author Joe Grandja */ class JwkDefinitionSource { @@ -41,6 +50,11 @@ class JwkDefinitionSource { private final AtomicReference> jwkDefinitions = new AtomicReference>(new HashMap()); + /** + * Creates a new instance using the provided URL as the location for the JWK Set. + * + * @param jwkSetUrl the JWK Set URL + */ JwkDefinitionSource(String jwkSetUrl) { try { this.jwkSetUrl = new URL(jwkSetUrl); @@ -49,6 +63,12 @@ class JwkDefinitionSource { } } + /** + * Returns the JWK definition matching the provided keyId ("kid"). + * + * @param keyId the Key ID ("kid") + * @return the matching {@link JwkDefinition} or null if not found + */ JwkDefinition getDefinition(String keyId) { JwkDefinition result = null; for (JwkDefinition jwkDefinition : this.jwkDefinitions.get().keySet()) { @@ -60,6 +80,14 @@ JwkDefinition getDefinition(String keyId) { return result; } + /** + * Returns the JWK definition matching the provided keyId ("kid"). + * If the JWK definition is not available in the internal cache then {@link #refreshJwkDefinitions()} + * will be called (to refresh the cache) and then followed-up with a second attempt to locate the JWK definition. + * + * @param keyId the Key ID ("kid") + * @return the matching {@link JwkDefinition} or null if not found + */ JwkDefinition getDefinitionRefreshIfNecessary(String keyId) { JwkDefinition result = this.getDefinition(keyId); if (result != null) { @@ -69,6 +97,12 @@ JwkDefinition getDefinitionRefreshIfNecessary(String keyId) { return this.getDefinition(keyId); } + /** + * Returns the {@link SignatureVerifier} matching the provided keyId ("kid"). + * + * @param keyId the Key ID ("kid") + * @return the matching {@link SignatureVerifier} or null if not found + */ SignatureVerifier getVerifier(String keyId) { SignatureVerifier result = null; JwkDefinition jwkDefinition = this.getDefinitionRefreshIfNecessary(keyId); @@ -78,14 +112,23 @@ SignatureVerifier getVerifier(String keyId) { return result; } - private void refreshJwkDefinitions() { - Set jwkDefinitionSet; + /** + * Refreshes the internal cache of association(s) between {@link JwkDefinition} and {@link SignatureVerifier}. + * Uses a {@link JwkSetConverter} to convert the JWK Set URL source to a set of {@link JwkDefinition}(s) + * followed by the instantiation of a {@link SignatureVerifier} which is mapped to it's {@link JwkDefinition}. + * + * @see JwkSetConverter + */ + void refreshJwkDefinitions() { + InputStream jwkSetSource; try { - jwkDefinitionSet = this.jwkSetConverter.convert(this.jwkSetUrl.openStream()); + jwkSetSource = this.jwkSetUrl.openStream(); } catch (IOException ex) { - throw new JwkException("An I/O error occurred while refreshing the JWK Set: " + ex.getMessage(), ex); + throw new JwkException("An I/O error occurred while reading from the JWK Set source: " + ex.getMessage(), ex); } + Set jwkDefinitionSet = this.jwkSetConverter.convert(jwkSetSource); + Map refreshedJwkDefinitions = new LinkedHashMap(); for (JwkDefinition jwkDefinition : jwkDefinitionSet) { @@ -109,8 +152,8 @@ private RsaVerifier createRSAVerifier(RSAJwkDefinition rsaDefinition) { result = new RsaVerifier(rsaPublicKey, rsaDefinition.getAlgorithm().standardName()); } catch (Exception ex) { - throw new JwkException("An error occurred while creating a RSA Public Key Verifier for \"" + - rsaDefinition.getKeyId() + "\" : " + ex.getMessage(), ex); + throw new JwkException("An error occurred while creating a RSA Public Key Verifier for " + + rsaDefinition.getKeyId() + " : " + ex.getMessage(), ex); } return result; } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java index f9a5e0032..f47ef672a 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java @@ -18,9 +18,14 @@ import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; /** + * General exception for JSON Web Key (JWK) related errors. + * * @author Joe Grandja */ public class JwkException extends OAuth2Exception { + private static final String SERVER_ERROR_ERROR_CODE = "server_error"; + private String errorCode = SERVER_ERROR_ERROR_CODE; + private int httpStatus = 500; public JwkException(String message) { super(message); @@ -30,13 +35,25 @@ public JwkException(String message, Throwable cause) { super(message, cause); } + /** + * Returns the error used in the OAuth2 Error Response + * sent back to the caller. The default is "server_error". + * + * @return the error used in the OAuth2 Error Response + */ @Override public String getOAuth2ErrorCode() { - return "server_error"; + return this.errorCode; } + /** + * Returns the Http Status used in the OAuth2 Error Response + * sent back to the caller. The default is 500. + * + * @return the Http Status set on the OAuth2 Error Response + */ @Override public int getHttpErrorCode() { - return 500; + return this.httpStatus; } } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java index 1b6964adb..2a31e37b6 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java @@ -30,13 +30,32 @@ import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.*; - /** + * A {@link Converter} that converts the supplied InputStream to a Set of {@link JwkDefinition}(s). + * The source of the InputStream must be a JWK Set representation which is a JSON object + * that has a "keys" member and its value is an array of JWKs. + *
+ *
+ * + * NOTE: The Key Type ("kty") currently supported by this {@link Converter} is {@link JwkDefinition.KeyType#RSA}. + *
+ *
+ * + * @see JwkDefinition + * @see JWK Set Format + * * @author Joe Grandja */ class JwkSetConverter implements Converter> { private final JsonFactory factory = new JsonFactory(); + /** + * Converts the supplied InputStream to a Set of {@link JwkDefinition}(s). + * + * @param jwkSetSource the source for the JWK Set + * @return a Set of {@link JwkDefinition}(s) + * @throws JwkException if the JWK Set JSON object is invalid + */ @Override public Set convert(InputStream jwkSetSource) { Set jwkDefinitions; @@ -52,7 +71,7 @@ public Set convert(InputStream jwkSetSource) { throw new JwkException("Invalid JWK Set Object."); } if (!parser.getCurrentName().equals(KEYS)) { - throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have a \"" + KEYS + "\" attribute."); + throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have a " + KEYS + " attribute."); } if (parser.nextToken() != JsonToken.START_ARRAY) { throw new JwkException("Invalid JWK Set Object. The JWK Set MUST have an array of JWK(s)."); @@ -87,6 +106,13 @@ public Set convert(InputStream jwkSetSource) { return jwkDefinitions; } + /** + * Creates a {@link JwkDefinition} based on the supplied attributes. + * + * @param attributes the attributes used to create the {@link JwkDefinition} + * @return a {@link JwkDefinition} + * @throws JwkException if the Key Type ("kty") attribute value is not {@link JwkDefinition.KeyType#RSA} + */ private JwkDefinition createJwkDefinition(Map attributes) { JwkDefinition.KeyType keyType = JwkDefinition.KeyType.fromValue(attributes.get(KEY_TYPE)); @@ -99,11 +125,18 @@ private JwkDefinition createJwkDefinition(Map attributes) { return this.createRSAJwkDefinition(attributes); } + /** + * Creates a {@link RSAJwkDefinition} based on the supplied attributes. + * + * @param attributes the attributes used to create the {@link RSAJwkDefinition} + * @return a {@link JwkDefinition} representation of a RSA Key + * @throws JwkException if at least one attribute value is missing or invalid for a RSA Key + */ private JwkDefinition createRSAJwkDefinition(Map attributes) { // kid String keyId = attributes.get(KEY_ID); if (!StringUtils.hasText(keyId)) { - throw new JwkException("\"" + KEY_ID + "\" is a required attribute for a JWK."); + throw new JwkException(KEY_ID + " is a required attribute for a JWK."); } // use @@ -116,7 +149,7 @@ private JwkDefinition createRSAJwkDefinition(Map attributes) { // alg JwkDefinition.CryptoAlgorithm algorithm = - JwkDefinition.CryptoAlgorithm.fromStandardName(attributes.get(ALGORITHM)); + JwkDefinition.CryptoAlgorithm.fromHeaderParamValue(attributes.get(ALGORITHM)); if (!JwkDefinition.CryptoAlgorithm.RS256.equals(algorithm) && !JwkDefinition.CryptoAlgorithm.RS384.equals(algorithm) && !JwkDefinition.CryptoAlgorithm.RS512.equals(algorithm)) { @@ -127,13 +160,13 @@ private JwkDefinition createRSAJwkDefinition(Map attributes) { // n String modulus = attributes.get(RSA_PUBLIC_KEY_MODULUS); if (!StringUtils.hasText(modulus)) { - throw new JwkException("\"" + RSA_PUBLIC_KEY_MODULUS + "\" is a required attribute for a RSA JWK."); + throw new JwkException(RSA_PUBLIC_KEY_MODULUS + " is a required attribute for a RSA JWK."); } // e String exponent = attributes.get(RSA_PUBLIC_KEY_EXPONENT); if (!StringUtils.hasText(exponent)) { - throw new JwkException("\"" + RSA_PUBLIC_KEY_EXPONENT + "\" is a required attribute for a RSA JWK."); + throw new JwkException(RSA_PUBLIC_KEY_EXPONENT + " is a required attribute for a RSA JWK."); } RSAJwkDefinition jwkDefinition = new RSAJwkDefinition( diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java index cbfd1696f..1f62f6467 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java @@ -15,21 +15,88 @@ */ package org.springframework.security.oauth2.provider.token.store.jwk; +import org.springframework.security.jwt.crypto.sign.SignatureVerifier; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.util.Assert; import java.util.Collection; /** + * A {@link TokenStore} implementation that provides support for verifying the + * JSON Web Signature (JWS) for a JSON Web Token (JWT) using a JSON Web Key (JWK). + *
+ *
+ * + * This {@link TokenStore} implementation is exclusively meant to be used by a Resource Server as + * it's sole responsibility is to decode a JWT and verify it's signature (JWS) using the corresponding JWK. + *
+ *
+ * + * NOTE: + * There are a few operations defined by {@link TokenStore} that are not applicable for a Resource Server. + * In these cases, the method implementation will explicitly throw a + * {@link JwkException} reporting "This operation is not supported". + *
+ *
+ * + * The unsupported operations are as follows: + *
    + *
  • {@link #storeAccessToken(OAuth2AccessToken, OAuth2Authentication)}
  • + *
  • {@link #removeAccessToken(OAuth2AccessToken)}
  • + *
  • {@link #storeRefreshToken(OAuth2RefreshToken, OAuth2Authentication)}
  • + *
  • {@link #readRefreshToken(String)}
  • + *
  • {@link #readAuthenticationForRefreshToken(OAuth2RefreshToken)}
  • + *
  • {@link #removeRefreshToken(OAuth2RefreshToken)}
  • + *
  • {@link #removeAccessTokenUsingRefreshToken(OAuth2RefreshToken)}
  • + *
  • {@link #getAccessToken(OAuth2Authentication)}
  • + *
  • {@link #findTokensByClientIdAndUserName(String, String)}
  • + *
  • {@link #findTokensByClientId(String)}
  • + *
+ *
+ * + * This implementation delegates to an internal instance of a {@link JwtTokenStore} which uses a + * specialized extension of {@link JwtAccessTokenConverter}, specifically, {@link JwkVerifyingJwtAccessTokenConverter}. + * The {@link JwkVerifyingJwtAccessTokenConverter} is associated with a {@link JwkDefinitionSource} which is responsible + * for fetching (and caching) the JWK Set (a set of JWKs) from the URL supplied to the constructor of this implementation. + *
+ *
+ * + * The {@link JwkVerifyingJwtAccessTokenConverter} will verify the JWS in the following step sequence: + *
+ *
+ *
    + *
  1. Extract the "kid" parameter from the JWT header.
  2. + *
  3. Find the matching {@link JwkDefinition} from the {@link JwkDefinitionSource} with the corresponding "kid" attribute.
  4. + *
  5. Obtain the {@link SignatureVerifier} associated with the {@link JwkDefinition} via the {@link JwkDefinitionSource} and verify the signature.
  6. + *
+ *
+ * NOTE: The algorithms currently supported by this implementation are: RS256, RS384 and RS512. + *
+ *
+ * + * @see JwtTokenStore + * @see JwkVerifyingJwtAccessTokenConverter + * @see JwkDefinitionSource + * @see JwkDefinition + * @see JSON Web Key (JWK) + * @see JSON Web Token (JWT) + * @see JSON Web Signature (JWS) + * * @author Joe Grandja */ public class JwkTokenStore implements TokenStore { private final JwtTokenStore delegate; + /** + * Creates a new instance using the provided URL as the location for the JWK Set. + * + * @param jwkSetUrl the JWK Set URL + */ public JwkTokenStore(String jwkSetUrl) { Assert.hasText(jwkSetUrl, "jwkSetUrl cannot be empty"); JwkDefinitionSource jwkDefinitionSource = new JwkDefinitionSource(jwkSetUrl); @@ -38,72 +105,150 @@ public JwkTokenStore(String jwkSetUrl) { this.delegate = new JwtTokenStore(accessTokenConverter); } + /** + * Delegates to the internal instance {@link JwtTokenStore#readAuthentication(OAuth2AccessToken)}. + * + * @param token the access token + * @return the {@link OAuth2Authentication} representation of the access token + */ @Override public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { return this.delegate.readAuthentication(token); } + /** + * Delegates to the internal instance {@link JwtTokenStore#readAuthentication(String)}. + * + * @param tokenValue the access token value + * @return the {@link OAuth2Authentication} representation of the access token + */ @Override - public OAuth2Authentication readAuthentication(String token) { - return this.delegate.readAuthentication(token); + public OAuth2Authentication readAuthentication(String tokenValue) { + return this.delegate.readAuthentication(tokenValue); } + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ @Override public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { throw this.operationNotSupported(); } + /** + * Delegates to the internal instance {@link JwtTokenStore#readAccessToken(String)}. + * + * @param tokenValue the access token value + * @return the {@link OAuth2AccessToken} representation of the access token value + */ @Override public OAuth2AccessToken readAccessToken(String tokenValue) { return this.delegate.readAccessToken(tokenValue); } + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ @Override public void removeAccessToken(OAuth2AccessToken token) { throw this.operationNotSupported(); } + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ @Override public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { throw this.operationNotSupported(); } + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ @Override public OAuth2RefreshToken readRefreshToken(String tokenValue) { throw this.operationNotSupported(); } + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ @Override public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { throw this.operationNotSupported(); } + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ @Override public void removeRefreshToken(OAuth2RefreshToken token) { throw this.operationNotSupported(); } + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ @Override public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { throw this.operationNotSupported(); } + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ @Override public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { throw this.operationNotSupported(); } + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ @Override public Collection findTokensByClientIdAndUserName(String clientId, String userName) { throw this.operationNotSupported(); } + /** + * This operation is not applicable for a Resource Server + * and if called, will throw a {@link JwkException}. + * + * @throws JwkException reporting this operation is not supported + */ @Override public Collection findTokensByClientId(String clientId) { throw this.operationNotSupported(); } private JwkException operationNotSupported() { - return new JwkException("This operation is currently not supported."); + return new JwkException("This operation is not supported."); } } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java index dc7ea2606..f9a1a7e35 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java @@ -19,6 +19,7 @@ import org.springframework.security.jwt.JwtHelper; import org.springframework.security.jwt.crypto.sign.SignatureVerifier; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.util.JsonParser; import org.springframework.security.oauth2.common.util.JsonParserFactory; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -30,6 +31,41 @@ import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.KEY_ID; /** + * A specialized extension of {@link JwtAccessTokenConverter} that is responsible for verifying + * the JSON Web Signature (JWS) for a JSON Web Token (JWT) using the corresponding JSON Web Key (JWK). + * This implementation is associated with a {@link JwkDefinitionSource} for looking up + * the matching {@link JwkDefinition} using the value of the JWT header parameter "kid". + *
+ *
+ * + * The JWS is verified in the following step sequence: + *
+ *
+ *
    + *
  1. Extract the "kid" parameter from the JWT header.
  2. + *
  3. Find the matching {@link JwkDefinition} from the {@link JwkDefinitionSource} with the corresponding "kid" attribute.
  4. + *
  5. Obtain the {@link SignatureVerifier} associated with the {@link JwkDefinition} via the {@link JwkDefinitionSource} and verify the signature.
  6. + *
+ *
+ * NOTE: The algorithms currently supported by this implementation are: RS256, RS384 and RS512. + *
+ *
+ * + * NOTE: This {@link JwtAccessTokenConverter} does not support signing JWTs (JWS) and therefore + * the {@link #encode(OAuth2AccessToken, OAuth2Authentication)} method implementation, if called, + * will explicitly throw a {@link JwkException} reporting "JWT signing (JWS) is not supported.". + *
+ *
+ * + * @see JwtAccessTokenConverter + * @see JwtHeaderConverter + * @see JwkDefinitionSource + * @see JwkDefinition + * @see SignatureVerifier + * @see JSON Web Key (JWK) + * @see JSON Web Token (JWT) + * @see JSON Web Signature (JWS) + * * @author Joe Grandja */ class JwkVerifyingJwtAccessTokenConverter extends JwtAccessTokenConverter { @@ -37,55 +73,70 @@ class JwkVerifyingJwtAccessTokenConverter extends JwtAccessTokenConverter { private final JwtHeaderConverter jwtHeaderConverter = new JwtHeaderConverter(); private final JsonParser jsonParser = JsonParserFactory.create(); + /** + * Creates a new instance using the provided {@link JwkDefinitionSource} + * as the primary source for looking up {@link JwkDefinition}(s). + * + * @param jwkDefinitionSource the source for {@link JwkDefinition}(s) + */ JwkVerifyingJwtAccessTokenConverter(JwkDefinitionSource jwkDefinitionSource) { this.jwkDefinitionSource = jwkDefinitionSource; } + /** + * Decodes and validates the supplied JWT followed by signature verification + * before returning the Claims from the JWT Payload. + * + * @param token the JSON Web Token + * @return a Map of the JWT Claims + * @throws JwkException if the JWT is invalid or if the JWS could not be verified + */ @Override protected Map decode(String token) { - try { - Map headers = this.jwtHeaderConverter.convert(token); - - // Validate "kid" header - String keyIdHeader = headers.get(KEY_ID); - if (keyIdHeader == null) { - throw new JwkException("Invalid JWT/JWS: \"" + KEY_ID + "\" is a required JOSE Header."); - } - JwkDefinition jwkDefinition = this.jwkDefinitionSource.getDefinitionRefreshIfNecessary(keyIdHeader); - if (jwkDefinition == null) { - throw new JwkException("Invalid JOSE Header \"" + KEY_ID + "\" (" + keyIdHeader + ")"); - } - - // Validate "alg" header - String algorithmHeader = headers.get(ALGORITHM); - if (algorithmHeader == null) { - throw new JwkException("Invalid JWT/JWS: \"" + ALGORITHM + "\" is a required JOSE Header."); - } - if (!algorithmHeader.equals(jwkDefinition.getAlgorithm().headerParamValue())) { - throw new JwkException("Invalid JOSE Header \"" + ALGORITHM + "\" (" + algorithmHeader + ")" + - " does not match algorithm associated with \"" + KEY_ID + "\" (" + keyIdHeader + ")"); - } + Map headers = this.jwtHeaderConverter.convert(token); - // Verify signature - SignatureVerifier verifier = this.jwkDefinitionSource.getVerifier(keyIdHeader); - Jwt jwt = JwtHelper.decode(token); - jwt.verifySignature(verifier); + // Validate "kid" header + String keyIdHeader = headers.get(KEY_ID); + if (keyIdHeader == null) { + throw new InvalidTokenException("Invalid JWT/JWS: " + KEY_ID + " is a required JOSE Header"); + } + JwkDefinition jwkDefinition = this.jwkDefinitionSource.getDefinitionRefreshIfNecessary(keyIdHeader); + if (jwkDefinition == null) { + throw new InvalidTokenException("Invalid JOSE Header " + KEY_ID + " (" + keyIdHeader + ")"); + } - Map claims = this.jsonParser.parseMap(jwt.getClaims()); - if (claims.containsKey(EXP) && claims.get(EXP) instanceof Integer) { - Integer expiryInt = (Integer) claims.get(EXP); - claims.put(EXP, new Long(expiryInt)); - } + // Validate "alg" header + String algorithmHeader = headers.get(ALGORITHM); + if (algorithmHeader == null) { + throw new InvalidTokenException("Invalid JWT/JWS: " + ALGORITHM + " is a required JOSE Header"); + } + if (!algorithmHeader.equals(jwkDefinition.getAlgorithm().headerParamValue())) { + throw new InvalidTokenException("Invalid JOSE Header " + ALGORITHM + " (" + algorithmHeader + ")" + + " does not match algorithm associated to JWK with " + KEY_ID + " (" + keyIdHeader + ")"); + } - return claims; + // Verify signature + SignatureVerifier verifier = this.jwkDefinitionSource.getVerifier(keyIdHeader); + Jwt jwt = JwtHelper.decode(token); + jwt.verifySignature(verifier); - } catch (Exception ex) { - throw new JwkException("Failed to decode/verify the JWT/JWS: " + ex.getMessage(), ex); + Map claims = this.jsonParser.parseMap(jwt.getClaims()); + if (claims.containsKey(EXP) && claims.get(EXP) instanceof Integer) { + Integer expiryInt = (Integer) claims.get(EXP); + claims.put(EXP, new Long(expiryInt)); } + + return claims; } + /** + * This operation (JWT signing) is not supported and if called, + * will throw a {@link JwkException}. + * + * @throws JwkException + */ @Override protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { - throw new JwkException("JWT/JWS (signing) is currently not supported."); + throw new JwkException("JWT signing (JWS) is not supported."); } } \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java index 2afa65a8d..1fb2daf27 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java @@ -20,24 +20,37 @@ import com.fasterxml.jackson.core.JsonToken; import org.springframework.core.convert.converter.Converter; import org.springframework.security.jwt.codec.Codecs; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** + * A {@link Converter} that converts the supplied String representation of a JWT + * to a Map of JWT Header Parameters. + * + * @see JSON Web Token (JWT) + * * @author Joe Grandja */ class JwtHeaderConverter implements Converter> { private final JsonFactory factory = new JsonFactory(); + /** + * Converts the supplied JSON Web Token to a Map of JWT Header Parameters. + * + * @param token the JSON Web Token + * @return a Map of JWT Header Parameters + * @throws JwkException if the JWT is invalid + */ @Override public Map convert(String token) { Map headers; int headerEndIndex = token.indexOf('.'); if (headerEndIndex == -1) { - throw new JwkException("Invalid JWT. Missing JOSE Header."); + throw new InvalidTokenException("Invalid JWT. Missing JOSE Header."); } byte[] decodedHeader = Codecs.b64UrlDecode(token.substring(0, headerEndIndex)); @@ -56,7 +69,7 @@ public Map convert(String token) { } } catch (IOException ex) { - throw new JwkException("An I/O error occurred while reading the JWT: " + ex.getMessage(), ex); + throw new InvalidTokenException("An I/O error occurred while reading the JWT: " + ex.getMessage(), ex); } finally { try { if (parser != null) parser.close(); diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java index 6361784af..d94ebfb6e 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java @@ -16,12 +16,26 @@ package org.springframework.security.oauth2.provider.token.store.jwk; /** + * A JSON Web Key (JWK) representation of a RSA key. + * + * @see JSON Web Key (JWK) + * @see JSON Web Algorithms (JWA) + * * @author Joe Grandja */ final class RSAJwkDefinition extends JwkDefinition { private final String modulus; private final String exponent; + /** + * Creates an instance of a RSA JSON Web Key (JWK). + * + * @param keyId the Key ID + * @param publicKeyUse the intended use of the Public Key + * @param algorithm the algorithm intended to be used + * @param modulus the modulus value for the Public Key + * @param exponent the exponent value for the Public Key + */ RSAJwkDefinition(String keyId, PublicKeyUse publicKeyUse, CryptoAlgorithm algorithm, @@ -33,10 +47,16 @@ final class RSAJwkDefinition extends JwkDefinition { this.exponent = exponent; } + /** + * @return the modulus value for the Public Key + */ String getModulus() { return this.modulus; } + /** + * @return the exponent value for the Public Key + */ String getExponent() { return this.exponent; } diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSourceTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSourceTest.java new file mode 100644 index 000000000..36b1bb459 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSourceTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.junit.Test; + +import static org.mockito.Mockito.*; + +/** + * @author jgrandja + */ +public class JwkDefinitionSourceTest { + private static final String DEFAULT_JWK_SET_URL = "https://identity.server1.io/token_keys"; + + @Test(expected = IllegalArgumentException.class) + public void constructorWhenInvalidJwkSetUrlThenThrowIllegalArgumentException() throws Exception { + new JwkDefinitionSource(DEFAULT_JWK_SET_URL.substring(1)); + } + + @Test + public void getDefinitionRefreshIfNecessaryWhenKeyIdNotFoundThenRefreshJwkDefinitions() throws Exception { + JwkDefinitionSource jwkDefinitionSource = spy(new JwkDefinitionSource(DEFAULT_JWK_SET_URL)); + doNothing().when(jwkDefinitionSource).refreshJwkDefinitions(); + jwkDefinitionSource.getDefinitionRefreshIfNecessary("invalid-key-id"); + verify(jwkDefinitionSource).refreshJwkDefinitions(); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverterTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverterTest.java new file mode 100644 index 000000000..834f9e559 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverterTest.java @@ -0,0 +1,284 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.KEYS; + +/** + * @author jgrandja + */ +public class JwkSetConverterTest { + private final JwkSetConverter converter = new JwkSetConverter(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + + @Test + public void convertWhenJwkSetStreamIsNullThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("Invalid JWK Set Object."); + this.converter.convert(null); + } + + @Test + public void convertWhenJwkSetStreamIsEmptyThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("Invalid JWK Set Object."); + this.converter.convert(new ByteArrayInputStream(new byte[0])); + } + + @Test + public void convertWhenJwkSetStreamNotAnObjectThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("Invalid JWK Set Object."); + this.converter.convert(new ByteArrayInputStream("".getBytes())); + } + + @Test + public void convertWhenJwkSetStreamHasMissingKeysAttributeThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("Invalid JWK Set Object."); + Map jwkSetObject = new HashMap(); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasInvalidKeysAttributeThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("Invalid JWK Set Object. The JWK Set MUST have a keys attribute."); + Map jwkSetObject = new HashMap(); + jwkSetObject.put(KEYS + "-invalid", new Map[0]); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasInvalidJwkElementsThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("Invalid JWK Set Object. The JWK Set MUST have an array of JWK(s)."); + Map jwkSetObject = new HashMap(); + jwkSetObject.put(JwkAttributes.KEYS, ""); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasEmptyJwkElementsThenReturnEmptyJwkSet() throws Exception { + Map jwkSetObject = new HashMap(); + jwkSetObject.put(JwkAttributes.KEYS, new Map[0]); + Set jwkSet = this.converter.convert(this.asInputStream(jwkSetObject)); + assertTrue("JWK Set NOT empty", jwkSet.isEmpty()); + } + + @Test + public void convertWhenJwkSetStreamHasEmptyJwkElementThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("unknown (kty) is currently not supported."); + Map jwkSetObject = new HashMap(); + Map jwkObject = new HashMap(); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject}); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasJwkElementWithECKeyTypeThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("EC (kty) is currently not supported."); + Map jwkSetObject = new HashMap(); + Map jwkObject = this.createJwkObject(JwkDefinition.KeyType.EC); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject}); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasJwkElementWithOCTKeyTypeThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("oct (kty) is currently not supported."); + Map jwkSetObject = new HashMap(); + Map jwkObject = this.createJwkObject(JwkDefinition.KeyType.OCT); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject}); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasJwkElementWithMissingKeyIdAttributeThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("kid is a required attribute for a JWK."); + Map jwkSetObject = new HashMap(); + Map jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, null); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject}); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasJwkElementWithMissingPublicKeyUseAttributeThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("unknown (use) is currently not supported."); + Map jwkSetObject = new HashMap(); + Map jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1"); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject}); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasJwkElementWithENCPublicKeyUseAttributeThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("enc (use) is currently not supported."); + Map jwkSetObject = new HashMap(); + Map jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1", JwkDefinition.PublicKeyUse.ENC); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject}); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasJwkElementWithMissingAlgorithmAttributeThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("unknown (alg) is currently not supported."); + Map jwkSetObject = new HashMap(); + Map jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1", JwkDefinition.PublicKeyUse.SIG); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject}); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasJwkElementWithMissingRSAModulusAttributeThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("n is a required attribute for a RSA JWK."); + Map jwkSetObject = new HashMap(); + Map jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1", + JwkDefinition.PublicKeyUse.SIG, JwkDefinition.CryptoAlgorithm.RS256); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject}); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamHasJwkElementWithMissingRSAExponentAttributeThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("e is a required attribute for a RSA JWK."); + Map jwkSetObject = new HashMap(); + Map jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1", + JwkDefinition.PublicKeyUse.SIG, JwkDefinition.CryptoAlgorithm.RS256, "AMh-pGAj9vX2gwFDyrXot1f2YfHgh8h0Qx6w9IqLL"); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject}); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + @Test + public void convertWhenJwkSetStreamIsValidThenReturnJwkSet() throws Exception { + Map jwkSetObject = new HashMap(); + Map jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1", + JwkDefinition.PublicKeyUse.SIG, JwkDefinition.CryptoAlgorithm.RS256, "AMh-pGAj9vX2gwFDyrXot1f2YfHgh8h0Qx6w9IqLL", "AQAB"); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject}); + Set jwkSet = this.converter.convert(this.asInputStream(jwkSetObject)); + assertNotNull(jwkSet); + assertEquals("JWK Set NOT size=1", 1, jwkSet.size()); + + Map jwkObject2 = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-2", + JwkDefinition.PublicKeyUse.SIG, JwkDefinition.CryptoAlgorithm.RS512, "AMh-pGAj9vX2gwFDyrXot1f2YfHgh8h0Qx6w9IqLL", "AQAB"); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject, jwkObject2}); + jwkSet = this.converter.convert(this.asInputStream(jwkSetObject)); + assertNotNull(jwkSet); + assertEquals("JWK Set NOT size=2", 2, jwkSet.size()); + } + + @Test + public void convertWhenJwkSetStreamHasDuplicateJwkElementsThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("Duplicate JWK found in Set: key-id-1 (kid)"); + Map jwkSetObject = new HashMap(); + Map jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1", + JwkDefinition.PublicKeyUse.SIG, JwkDefinition.CryptoAlgorithm.RS256, "AMh-pGAj9vX2gwFDyrXot1f2YfHgh8h0Qx6w9IqLL", "AQAB"); + jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject, jwkObject}); + this.converter.convert(this.asInputStream(jwkSetObject)); + } + + private Map createJwkObject(JwkDefinition.KeyType keyType) { + return this.createJwkObject(keyType, null); + } + + private Map createJwkObject(JwkDefinition.KeyType keyType, String keyId) { + return this.createJwkObject(keyType, keyId, null); + } + + private Map createJwkObject(JwkDefinition.KeyType keyType, + String keyId, + JwkDefinition.PublicKeyUse publicKeyUse) { + + return this.createJwkObject(keyType, keyId, publicKeyUse, null); + } + + private Map createJwkObject(JwkDefinition.KeyType keyType, + String keyId, + JwkDefinition.PublicKeyUse publicKeyUse, + JwkDefinition.CryptoAlgorithm algorithm) { + + return this.createJwkObject(keyType, keyId, publicKeyUse, algorithm, null); + } + + private Map createJwkObject(JwkDefinition.KeyType keyType, + String keyId, + JwkDefinition.PublicKeyUse publicKeyUse, + JwkDefinition.CryptoAlgorithm algorithm, + String rsaModulus) { + + return this.createJwkObject(keyType, keyId, publicKeyUse, algorithm, rsaModulus, null); + } + + private Map createJwkObject(JwkDefinition.KeyType keyType, + String keyId, + JwkDefinition.PublicKeyUse publicKeyUse, + JwkDefinition.CryptoAlgorithm algorithm, + String rsaModulus, + String rsaExponent) { + + Map jwkObject = new HashMap(); + jwkObject.put(JwkAttributes.KEY_TYPE, keyType.value()); + if (keyId != null) { + jwkObject.put(JwkAttributes.KEY_ID, keyId); + } + if (publicKeyUse != null) { + jwkObject.put(JwkAttributes.PUBLIC_KEY_USE, publicKeyUse.value()); + } + if (algorithm != null) { + jwkObject.put(JwkAttributes.ALGORITHM, algorithm.headerParamValue()); + } + if (rsaModulus != null) { + jwkObject.put(JwkAttributes.RSA_PUBLIC_KEY_MODULUS, rsaModulus); + } + if (rsaExponent != null) { + jwkObject.put(JwkAttributes.RSA_PUBLIC_KEY_EXPONENT, rsaExponent); + } + return jwkObject; + } + + private InputStream asInputStream(Map content) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + this.objectMapper.writeValue(out, content); + return new ByteArrayInputStream(out.toByteArray()); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStoreTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStoreTest.java new file mode 100644 index 000000000..34353d37d --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStoreTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +/** + * @author jgrandja + */ +public class JwkTokenStoreTest { + private JwkTokenStore jwkTokenStore = new JwkTokenStore("https://identity.server1.io/token_keys"); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void setUp() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("This operation is not supported."); + } + + @Test + public void storeAccessTokenWhenCalledThenThrowJwkException() throws Exception { + this.jwkTokenStore.storeAccessToken(null, null); + } + + @Test + public void removeAccessTokenWhenCalledThenThrowJwkException() throws Exception { + this.jwkTokenStore.removeAccessToken(null); + } + + @Test + public void storeRefreshTokenWhenCalledThenThrowJwkException() throws Exception { + this.jwkTokenStore.storeRefreshToken(null, null); + } + + @Test + public void readRefreshTokenWhenCalledThenThrowJwkException() throws Exception { + this.jwkTokenStore.readRefreshToken(null); + } + + @Test + public void readAuthenticationForRefreshTokenWhenCalledThenThrowJwkException() throws Exception { + this.jwkTokenStore.readAuthenticationForRefreshToken(null); + } + + @Test + public void removeRefreshTokenWhenCalledThenThrowJwkException() throws Exception { + this.jwkTokenStore.removeRefreshToken(null); + } + + @Test + public void removeAccessTokenUsingRefreshTokenWhenCalledThenThrowJwkException() throws Exception { + this.jwkTokenStore.removeAccessTokenUsingRefreshToken(null); + } + + @Test + public void getAccessTokenWhenCalledThenThrowJwkException() throws Exception { + this.jwkTokenStore.getAccessToken(null); + } + + @Test + public void findTokensByClientIdAndUserNameWhenCalledThenThrowJwkException() throws Exception { + this.jwkTokenStore.findTokensByClientIdAndUserName(null, null); + } + + @Test + public void findTokensByClientIdWhenCalledThenThrowJwkException() throws Exception { + this.jwkTokenStore.findTokensByClientId(null); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverterTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverterTest.java new file mode 100644 index 000000000..a27db63f8 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverterTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.security.oauth2.provider.token.store.jwk.JwtTestUtil.createJwt; +import static org.springframework.security.oauth2.provider.token.store.jwk.JwtTestUtil.createJwtHeader; + +/** + * @author jgrandja + */ +public class JwkVerifyingJwtAccessTokenConverterTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void encodeWhenCalledThenThrowJwkException() throws Exception { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("JWT signing (JWS) is not supported."); + JwkVerifyingJwtAccessTokenConverter accessTokenConverter = + new JwkVerifyingJwtAccessTokenConverter(mock(JwkDefinitionSource.class)); + accessTokenConverter.encode(null, null); + } + + @Test + public void decodeWhenKeyIdHeaderMissingThenThrowJwkException() throws Exception { + this.thrown.expect(InvalidTokenException.class); + this.thrown.expectMessage("Invalid JWT/JWS: kid is a required JOSE Header"); + JwkVerifyingJwtAccessTokenConverter accessTokenConverter = + new JwkVerifyingJwtAccessTokenConverter(mock(JwkDefinitionSource.class)); + String jwt = createJwt(createJwtHeader(null, JwkDefinition.CryptoAlgorithm.RS256)); + accessTokenConverter.decode(jwt); + } + + @Test + public void decodeWhenKeyIdHeaderInvalidThenThrowJwkException() throws Exception { + this.thrown.expect(InvalidTokenException.class); + this.thrown.expectMessage("Invalid JOSE Header kid (invalid-key-id)"); + JwkDefinition jwkDefinition = this.createRSAJwkDefinition("key-id-1", JwkDefinition.CryptoAlgorithm.RS256); + JwkDefinitionSource jwkDefinitionSource = mock(JwkDefinitionSource.class); + when(jwkDefinitionSource.getDefinitionRefreshIfNecessary("key-id-1")).thenReturn(jwkDefinition); + JwkVerifyingJwtAccessTokenConverter accessTokenConverter = + new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource); + String jwt = createJwt(createJwtHeader("invalid-key-id", JwkDefinition.CryptoAlgorithm.RS256)); + accessTokenConverter.decode(jwt); + } + + @Test + public void decodeWhenAlgorithmHeaderMissingThenThrowJwkException() throws Exception { + this.thrown.expect(InvalidTokenException.class); + this.thrown.expectMessage("Invalid JWT/JWS: alg is a required JOSE Header"); + JwkDefinition jwkDefinition = this.createRSAJwkDefinition("key-id-1", JwkDefinition.CryptoAlgorithm.RS256); + JwkDefinitionSource jwkDefinitionSource = mock(JwkDefinitionSource.class); + when(jwkDefinitionSource.getDefinitionRefreshIfNecessary("key-id-1")).thenReturn(jwkDefinition); + JwkVerifyingJwtAccessTokenConverter accessTokenConverter = + new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource); + String jwt = createJwt(createJwtHeader("key-id-1", null)); + accessTokenConverter.decode(jwt); + } + + @Test + public void decodeWhenAlgorithmHeaderDoesNotMatchJwkAlgorithmThenThrowJwkException() throws Exception { + this.thrown.expect(InvalidTokenException.class); + this.thrown.expectMessage("Invalid JOSE Header alg (RS512) " + + "does not match algorithm associated to JWK with kid (key-id-1)"); + JwkDefinition jwkDefinition = this.createRSAJwkDefinition("key-id-1", JwkDefinition.CryptoAlgorithm.RS256); + JwkDefinitionSource jwkDefinitionSource = mock(JwkDefinitionSource.class); + when(jwkDefinitionSource.getDefinitionRefreshIfNecessary("key-id-1")).thenReturn(jwkDefinition); + JwkVerifyingJwtAccessTokenConverter accessTokenConverter = + new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource); + String jwt = createJwt(createJwtHeader("key-id-1", JwkDefinition.CryptoAlgorithm.RS512)); + accessTokenConverter.decode(jwt); + } + + private JwkDefinition createRSAJwkDefinition(String keyId, JwkDefinition.CryptoAlgorithm algorithm) { + return createRSAJwkDefinition(JwkDefinition.KeyType.RSA, keyId, + JwkDefinition.PublicKeyUse.SIG, algorithm, "AMh-pGAj9vX2gwFDyrXot1f2YfHgh8h0Qx6w9IqLL", "AQAB"); + } + + private JwkDefinition createRSAJwkDefinition(JwkDefinition.KeyType keyType, + String keyId, + JwkDefinition.PublicKeyUse publicKeyUse, + JwkDefinition.CryptoAlgorithm algorithm, + String modulus, + String exponent) { + + return new RSAJwkDefinition(keyId, publicKeyUse, algorithm, modulus, exponent); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverterTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverterTest.java new file mode 100644 index 000000000..f652e61c7 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverterTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.springframework.security.oauth2.provider.token.store.jwk.JwtTestUtil.createJwt; + +/** + * @author jgrandja + */ +public class JwtHeaderConverterTest { + private final JwtHeaderConverter converter = new JwtHeaderConverter(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + + @Test + public void convertWhenJwtTokenIsNullThenThrowNullPointerException() throws Exception { + this.thrown.expect(NullPointerException.class); + this.converter.convert(null); + } + + @Test + public void convertWhenJwtTokenInvalidThenThrowJwkException() throws Exception { + this.thrown.expect(InvalidTokenException.class); + this.thrown.expectMessage("Invalid JWT. Missing JOSE Header."); + this.converter.convert(""); + } + + @Test + public void convertWhenJwtTokenValidThenReturnJwtHeaders() throws Exception { + Map jwtHeaders = this.converter.convert(createJwt()); + assertEquals("key-id-1", jwtHeaders.get(JwkAttributes.KEY_ID)); + assertEquals(JwkDefinition.CryptoAlgorithm.RS256.headerParamValue(), jwtHeaders.get(JwkAttributes.ALGORITHM)); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtTestUtil.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtTestUtil.java new file mode 100644 index 000000000..b8df0e87b --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtTestUtil.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.security.jwt.codec.Codecs; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Joe Grandja + */ +class JwtTestUtil { + private static final ObjectMapper objectMapper = new ObjectMapper(); + + static String createJwt() throws Exception { + return createJwt(createDefaultJwtHeader()); + } + + static String createJwt(byte[] jwtHeader) throws Exception { + return createJwt(jwtHeader, createDefaultJwtPayload()); + } + + static String createJwt(byte[] jwtHeader, byte[] jwtPayload) throws Exception { + byte[] encodedJwtHeader = Codecs.b64UrlEncode(jwtHeader); + byte[] encodedJwtPayload = Codecs.b64UrlEncode(jwtPayload); + byte[] period = Codecs.utf8Encode("."); + return new String(join(encodedJwtHeader, period, encodedJwtPayload)); + } + + static byte[] createDefaultJwtHeader() throws Exception { + return createJwtHeader("key-id-1", JwkDefinition.CryptoAlgorithm.RS256); + } + + static byte[] createJwtHeader(String keyId, JwkDefinition.CryptoAlgorithm algorithm) throws Exception { + Map jwtHeader = new HashMap(); + if (keyId != null) { + jwtHeader.put(JwkAttributes.KEY_ID, keyId); + } + if (algorithm != null) { + jwtHeader.put(JwkAttributes.ALGORITHM, algorithm.headerParamValue()); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + objectMapper.writeValue(out, jwtHeader); + return out.toByteArray(); + } + + static byte[] createDefaultJwtPayload() throws Exception { + Map jwtPayload = new HashMap(); + jwtPayload.put("claim-name-1", "claim-value-1"); + jwtPayload.put("claim-name-2", "claim-value-2"); + jwtPayload.put("claim-name-3", "claim-value-3"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + objectMapper.writeValue(out, jwtPayload); + return out.toByteArray(); + } + + private static byte[] join(byte[]... byteArrays) { + int size = 0; + for (byte[] bytes : byteArrays) { + size += bytes.length; + } + byte[] result = new byte[size]; + int index = 0; + for (byte[] bytes : byteArrays) { + System.arraycopy(bytes, 0, result, index, bytes.length); + index += bytes.length; + } + return result; + } +} \ No newline at end of file From 082d0696932059051b00c5d1b5de724069bf3a5e Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 1 Mar 2017 11:44:46 -0500 Subject: [PATCH 13/15] Polish gh-977 Fixes gh-994 --- .../token/store/jwk/JwkAttributes.java | 2 +- .../token/store/jwk/JwkDefinition.java | 16 ++-- .../token/store/jwk/JwkDefinitionSource.java | 74 ++++++++++------- .../token/store/jwk/JwkException.java | 2 +- .../token/store/jwk/JwkSetConverter.java | 15 ++-- .../token/store/jwk/JwkTokenStore.java | 20 ++--- .../JwkVerifyingJwtAccessTokenConverter.java | 4 +- .../token/store/jwk/JwtHeaderConverter.java | 2 +- ...kDefinition.java => RsaJwkDefinition.java} | 6 +- .../store/jwk/JwkDefinitionSourceTest.java | 26 ++++-- .../token/store/jwk/JwkDefinitionTest.java | 51 ++++++++++++ .../token/store/jwk/JwkSetConverterTest.java | 4 +- .../token/store/jwk/JwkTokenStoreTest.java | 80 +++++++++++++++++-- ...kVerifyingJwtAccessTokenConverterTest.java | 12 +-- .../store/jwk/JwtHeaderConverterTest.java | 4 +- .../provider/token/store/jwk/JwtTestUtil.java | 2 +- .../token/store/jwk/RsaJwkDefinitionTest.java | 45 +++++++++++ 17 files changed, 275 insertions(+), 90 deletions(-) rename spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/{RSAJwkDefinition.java => RsaJwkDefinition.java} (92%) create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionTest.java create mode 100644 spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/RsaJwkDefinitionTest.java diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java index 75343999a..642dcc430 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java index ac33281bd..2768ca3f1 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -162,18 +162,16 @@ static PublicKeyUse fromValue(String value) { * The defined Algorithm ("alg") values. */ enum CryptoAlgorithm { - RS256("SHA256withRSA", "RS256", "RSASSA-PKCS1-v1_5 using SHA-256"), - RS384("SHA384withRSA", "RS384", "RSASSA-PKCS1-v1_5 using SHA-384"), - RS512("SHA512withRSA", "RS512", "RSASSA-PKCS1-v1_5 using SHA-512"); + RS256("SHA256withRSA", "RS256"), + RS384("SHA384withRSA", "RS384"), + RS512("SHA512withRSA", "RS512"); private final String standardName; // JCA Standard Name private final String headerParamValue; - private final String description; - CryptoAlgorithm(String standardName, String headerParamValue, String description) { + CryptoAlgorithm(String standardName, String headerParamValue) { this.standardName = standardName; this.headerParamValue = headerParamValue; - this.description = description; } String standardName() { @@ -184,10 +182,6 @@ String headerParamValue() { return this.headerParamValue; } - String description() { - return this.description; - } - static CryptoAlgorithm fromHeaderParamValue(String headerParamValue) { CryptoAlgorithm result = null; for (CryptoAlgorithm algorithm : values()) { diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java index 83bf4b85f..de643ff03 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,11 +27,10 @@ import java.security.KeyFactory; import java.security.interfaces.RSAPublicKey; import java.security.spec.RSAPublicKeySpec; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ConcurrentHashMap; /** * A source for JSON Web Key(s) (JWK) that is solely responsible for fetching (and caching) @@ -46,9 +45,8 @@ */ class JwkDefinitionSource { private final URL jwkSetUrl; - private final JwkSetConverter jwkSetConverter = new JwkSetConverter(); - private final AtomicReference> jwkDefinitions = - new AtomicReference>(new HashMap()); + private final Map jwkDefinitions = new ConcurrentHashMap(); + private static final JwkSetConverter jwkSetConverter = new JwkSetConverter(); /** * Creates a new instance using the provided URL as the location for the JWK Set. @@ -71,29 +69,28 @@ class JwkDefinitionSource { */ JwkDefinition getDefinition(String keyId) { JwkDefinition result = null; - for (JwkDefinition jwkDefinition : this.jwkDefinitions.get().keySet()) { - if (jwkDefinition.getKeyId().equals(keyId)) { - result = jwkDefinition; - break; - } + JwkDefinitionHolder jwkDefinitionHolder = this.jwkDefinitions.get(keyId); + if (jwkDefinitionHolder != null) { + result = jwkDefinitionHolder.getJwkDefinition(); } return result; } /** * Returns the JWK definition matching the provided keyId ("kid"). - * If the JWK definition is not available in the internal cache then {@link #refreshJwkDefinitions()} - * will be called (to refresh the cache) and then followed-up with a second attempt to locate the JWK definition. + * If the JWK definition is not available in the internal cache then {@link #loadJwkDefinitions(URL)} + * will be called (to re-load the cache) and then followed-up with a second attempt to locate the JWK definition. * * @param keyId the Key ID ("kid") * @return the matching {@link JwkDefinition} or null if not found */ - JwkDefinition getDefinitionRefreshIfNecessary(String keyId) { + JwkDefinition getDefinitionLoadIfNecessary(String keyId) { JwkDefinition result = this.getDefinition(keyId); if (result != null) { return result; } - this.refreshJwkDefinitions(); + this.jwkDefinitions.clear(); + this.jwkDefinitions.putAll(loadJwkDefinitions(this.jwkSetUrl)); return this.getDefinition(keyId); } @@ -105,42 +102,47 @@ JwkDefinition getDefinitionRefreshIfNecessary(String keyId) { */ SignatureVerifier getVerifier(String keyId) { SignatureVerifier result = null; - JwkDefinition jwkDefinition = this.getDefinitionRefreshIfNecessary(keyId); + JwkDefinition jwkDefinition = this.getDefinitionLoadIfNecessary(keyId); if (jwkDefinition != null) { - result = this.jwkDefinitions.get().get(jwkDefinition); + result = this.jwkDefinitions.get(keyId).getSignatureVerifier(); } return result; } /** - * Refreshes the internal cache of association(s) between {@link JwkDefinition} and {@link SignatureVerifier}. + * Fetches the JWK Set from the provided URL and + * returns a Map keyed by the JWK keyId ("kid") + * and mapped to an association of the {@link JwkDefinition} and {@link SignatureVerifier}. * Uses a {@link JwkSetConverter} to convert the JWK Set URL source to a set of {@link JwkDefinition}(s) - * followed by the instantiation of a {@link SignatureVerifier} which is mapped to it's {@link JwkDefinition}. + * followed by the instantiation of a {@link SignatureVerifier} which is associated to it's {@link JwkDefinition}. * + * @param jwkSetUrl the JWK Set URL + * @return a Map keyed by the JWK keyId and mapped to an association of {@link JwkDefinition} and {@link SignatureVerifier} * @see JwkSetConverter */ - void refreshJwkDefinitions() { + static Map loadJwkDefinitions(URL jwkSetUrl) { InputStream jwkSetSource; try { - jwkSetSource = this.jwkSetUrl.openStream(); + jwkSetSource = jwkSetUrl.openStream(); } catch (IOException ex) { throw new JwkException("An I/O error occurred while reading from the JWK Set source: " + ex.getMessage(), ex); } - Set jwkDefinitionSet = this.jwkSetConverter.convert(jwkSetSource); + Set jwkDefinitionSet = jwkSetConverter.convert(jwkSetSource); - Map refreshedJwkDefinitions = new LinkedHashMap(); + Map jwkDefinitions = new LinkedHashMap(); for (JwkDefinition jwkDefinition : jwkDefinitionSet) { if (JwkDefinition.KeyType.RSA.equals(jwkDefinition.getKeyType())) { - refreshedJwkDefinitions.put(jwkDefinition, this.createRSAVerifier((RSAJwkDefinition)jwkDefinition)); + jwkDefinitions.put(jwkDefinition.getKeyId(), + new JwkDefinitionHolder(jwkDefinition, createRsaVerifier((RsaJwkDefinition) jwkDefinition))); } } - this.jwkDefinitions.set(refreshedJwkDefinitions); + return jwkDefinitions; } - private RsaVerifier createRSAVerifier(RSAJwkDefinition rsaDefinition) { + private static RsaVerifier createRsaVerifier(RsaJwkDefinition rsaDefinition) { RsaVerifier result; try { BigInteger modulus = new BigInteger(Codecs.b64UrlDecode(rsaDefinition.getModulus())); @@ -157,4 +159,22 @@ private RsaVerifier createRSAVerifier(RSAJwkDefinition rsaDefinition) { } return result; } -} + + static class JwkDefinitionHolder { + private final JwkDefinition jwkDefinition; + private final SignatureVerifier signatureVerifier; + + private JwkDefinitionHolder(JwkDefinition jwkDefinition, SignatureVerifier signatureVerifier) { + this.jwkDefinition = jwkDefinition; + this.signatureVerifier = signatureVerifier; + } + + private JwkDefinition getJwkDefinition() { + return jwkDefinition; + } + + private SignatureVerifier getSignatureVerifier() { + return signatureVerifier; + } + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java index f47ef672a..1d3211dfb 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkException.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java index 2a31e37b6..e0284261c 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -119,20 +119,21 @@ private JwkDefinition createJwkDefinition(Map attributes) { if (!JwkDefinition.KeyType.RSA.equals(keyType)) { throw new JwkException((keyType != null ? keyType.value() : "unknown") + - " (" + KEY_TYPE + ") is currently not supported."); + " (" + KEY_TYPE + ") is currently not supported." + + " Valid values for '" + KEY_TYPE + "' are: " + JwkDefinition.KeyType.RSA.value()); } - return this.createRSAJwkDefinition(attributes); + return this.createRsaJwkDefinition(attributes); } /** - * Creates a {@link RSAJwkDefinition} based on the supplied attributes. + * Creates a {@link RsaJwkDefinition} based on the supplied attributes. * - * @param attributes the attributes used to create the {@link RSAJwkDefinition} + * @param attributes the attributes used to create the {@link RsaJwkDefinition} * @return a {@link JwkDefinition} representation of a RSA Key * @throws JwkException if at least one attribute value is missing or invalid for a RSA Key */ - private JwkDefinition createRSAJwkDefinition(Map attributes) { + private JwkDefinition createRsaJwkDefinition(Map attributes) { // kid String keyId = attributes.get(KEY_ID); if (!StringUtils.hasText(keyId)) { @@ -169,7 +170,7 @@ private JwkDefinition createRSAJwkDefinition(Map attributes) { throw new JwkException(RSA_PUBLIC_KEY_EXPONENT + " is a required attribute for a RSA JWK."); } - RSAJwkDefinition jwkDefinition = new RSAJwkDefinition( + RsaJwkDefinition jwkDefinition = new RsaJwkDefinition( keyId, publicKeyUse, algorithm, modulus, exponent); return jwkDefinition; diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java index 1f62f6467..85706ba4a 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package org.springframework.security.oauth2.provider.token.store.jwk; -import org.springframework.security.jwt.crypto.sign.SignatureVerifier; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -60,19 +59,19 @@ *
* * This implementation delegates to an internal instance of a {@link JwtTokenStore} which uses a - * specialized extension of {@link JwtAccessTokenConverter}, specifically, {@link JwkVerifyingJwtAccessTokenConverter}. - * The {@link JwkVerifyingJwtAccessTokenConverter} is associated with a {@link JwkDefinitionSource} which is responsible - * for fetching (and caching) the JWK Set (a set of JWKs) from the URL supplied to the constructor of this implementation. + * specialized extension of {@link JwtAccessTokenConverter}. + * This specialized {@link JwtAccessTokenConverter} is capable of fetching (and caching) + * the JWK Set (a set of JWKs) from the URL supplied to the constructor of this implementation. *
*
* - * The {@link JwkVerifyingJwtAccessTokenConverter} will verify the JWS in the following step sequence: + * The {@link JwtAccessTokenConverter} will verify the JWS in the following step sequence: *
*
*
    *
  1. Extract the "kid" parameter from the JWT header.
  2. - *
  3. Find the matching {@link JwkDefinition} from the {@link JwkDefinitionSource} with the corresponding "kid" attribute.
  4. - *
  5. Obtain the {@link SignatureVerifier} associated with the {@link JwkDefinition} via the {@link JwkDefinitionSource} and verify the signature.
  6. + *
  7. Find the matching JWK with the corresponding "kid" attribute.
  8. + *
  9. Obtain the SignatureVerifier associated with the JWK and verify the signature.
  10. *
*
* NOTE: The algorithms currently supported by this implementation are: RS256, RS384 and RS512. @@ -80,16 +79,13 @@ *
* * @see JwtTokenStore - * @see JwkVerifyingJwtAccessTokenConverter - * @see JwkDefinitionSource - * @see JwkDefinition * @see JSON Web Key (JWK) * @see JSON Web Token (JWT) * @see JSON Web Signature (JWS) * * @author Joe Grandja */ -public class JwkTokenStore implements TokenStore { +public final class JwkTokenStore implements TokenStore { private final JwtTokenStore delegate; /** diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java index f9a1a7e35..431ade84a 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,7 +100,7 @@ protected Map decode(String token) { if (keyIdHeader == null) { throw new InvalidTokenException("Invalid JWT/JWS: " + KEY_ID + " is a required JOSE Header"); } - JwkDefinition jwkDefinition = this.jwkDefinitionSource.getDefinitionRefreshIfNecessary(keyIdHeader); + JwkDefinition jwkDefinition = this.jwkDefinitionSource.getDefinitionLoadIfNecessary(keyIdHeader); if (jwkDefinition == null) { throw new InvalidTokenException("Invalid JOSE Header " + KEY_ID + " (" + keyIdHeader + ")"); } diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java index 1fb2daf27..62981f33b 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RsaJwkDefinition.java similarity index 92% rename from spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java rename to spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RsaJwkDefinition.java index d94ebfb6e..41655c7af 100644 --- a/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RSAJwkDefinition.java +++ b/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/token/store/jwk/RsaJwkDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ * * @author Joe Grandja */ -final class RSAJwkDefinition extends JwkDefinition { +final class RsaJwkDefinition extends JwkDefinition { private final String modulus; private final String exponent; @@ -36,7 +36,7 @@ final class RSAJwkDefinition extends JwkDefinition { * @param modulus the modulus value for the Public Key * @param exponent the exponent value for the Public Key */ - RSAJwkDefinition(String keyId, + RsaJwkDefinition(String keyId, PublicKeyUse publicKeyUse, CryptoAlgorithm algorithm, String modulus, diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSourceTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSourceTest.java index 36b1bb459..75a545379 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSourceTest.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,23 @@ package org.springframework.security.oauth2.provider.token.store.jwk; import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.net.URL; +import java.util.Collections; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.*; -import static org.mockito.Mockito.*; /** - * @author jgrandja + * @author Joe Grandja */ +@RunWith(PowerMockRunner.class) +@PrepareForTest(JwkDefinitionSource.class) public class JwkDefinitionSourceTest { private static final String DEFAULT_JWK_SET_URL = "https://identity.server1.io/token_keys"; @@ -31,10 +42,11 @@ public void constructorWhenInvalidJwkSetUrlThenThrowIllegalArgumentException() t } @Test - public void getDefinitionRefreshIfNecessaryWhenKeyIdNotFoundThenRefreshJwkDefinitions() throws Exception { + public void getDefinitionLoadIfNecessaryWhenKeyIdNotFoundThenLoadJwkDefinitions() throws Exception { JwkDefinitionSource jwkDefinitionSource = spy(new JwkDefinitionSource(DEFAULT_JWK_SET_URL)); - doNothing().when(jwkDefinitionSource).refreshJwkDefinitions(); - jwkDefinitionSource.getDefinitionRefreshIfNecessary("invalid-key-id"); - verify(jwkDefinitionSource).refreshJwkDefinitions(); + mockStatic(JwkDefinitionSource.class); + when(JwkDefinitionSource.loadJwkDefinitions(any(URL.class))).thenReturn(Collections.emptyMap()); + jwkDefinitionSource.getDefinitionLoadIfNecessary("invalid-key-id"); + verifyStatic(); } } \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionTest.java new file mode 100644 index 000000000..a6ad3d2b2 --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkDefinitionTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Joe Grandja + */ +public class JwkDefinitionTest { + + @Test + public void constructorWhenArgumentsPassedThenAttributesAreCorrectlySet() throws Exception { + String keyId = "key-id-1"; + JwkDefinition.KeyType keyType = JwkDefinition.KeyType.RSA; + JwkDefinition.PublicKeyUse publicKeyUse = JwkDefinition.PublicKeyUse.SIG; + JwkDefinition.CryptoAlgorithm algorithm = JwkDefinition.CryptoAlgorithm.RS512; + + JwkDefinition jwkDefinition = new JwkDefinition(keyId, keyType, publicKeyUse, algorithm) { }; + + assertEquals(keyId, jwkDefinition.getKeyId()); + assertEquals(keyType, jwkDefinition.getKeyType()); + assertEquals(publicKeyUse, jwkDefinition.getPublicKeyUse()); + assertEquals(algorithm, jwkDefinition.getAlgorithm()); + } + + @Test + public void cryptoAlgorithmWhenAttributesAccessedThenCorrectValuesReturned() { + assertEquals("RS256", JwkDefinition.CryptoAlgorithm.RS256.headerParamValue()); + assertEquals("SHA256withRSA", JwkDefinition.CryptoAlgorithm.RS256.standardName()); + assertEquals("RS384", JwkDefinition.CryptoAlgorithm.RS384.headerParamValue()); + assertEquals("SHA384withRSA", JwkDefinition.CryptoAlgorithm.RS384.standardName()); + assertEquals("RS512", JwkDefinition.CryptoAlgorithm.RS512.headerParamValue()); + assertEquals("SHA512withRSA", JwkDefinition.CryptoAlgorithm.RS512.standardName()); + } +} \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverterTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverterTest.java index 834f9e559..233385aeb 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverterTest.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkSetConverterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.KEYS; /** - * @author jgrandja + * @author Joe Grandja */ public class JwkSetConverterTest { private final JwkSetConverter converter = new JwkSetConverter(); diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStoreTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStoreTest.java index 34353d37d..28ea3b528 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStoreTest.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkTokenStoreTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,73 +15,139 @@ */ package org.springframework.security.oauth2.provider.token.store.jwk; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.spy; + /** - * @author jgrandja + * @author Joe Grandja */ +@RunWith(PowerMockRunner.class) +@PrepareForTest(JwkTokenStore.class) public class JwkTokenStoreTest { private JwkTokenStore jwkTokenStore = new JwkTokenStore("https://identity.server1.io/token_keys"); @Rule public ExpectedException thrown = ExpectedException.none(); - @Before - public void setUp() throws Exception { - this.thrown.expect(JwkException.class); - this.thrown.expectMessage("This operation is not supported."); + @Test + public void readAuthenticationUsingOAuth2AccessTokenWhenCalledThenDelegateCalled() throws Exception { + JwkTokenStore spy = spy(this.jwkTokenStore); + JwtTokenStore delegate = mock(JwtTokenStore.class); + when(delegate.readAuthentication(any(OAuth2AccessToken.class))).thenReturn(null); + + Field field = ReflectionUtils.findField(spy.getClass(), "delegate"); + field.setAccessible(true); + ReflectionUtils.setField(field, spy, delegate); + + spy.readAuthentication(mock(OAuth2AccessToken.class)); + verify(delegate).readAuthentication(any(OAuth2AccessToken.class)); + } + + @Test + public void readAuthenticationUsingAccessTokenStringWhenCalledThenDelegateCalled() throws Exception { + JwkTokenStore spy = spy(this.jwkTokenStore); + JwtTokenStore delegate = mock(JwtTokenStore.class); + when(delegate.readAuthentication(anyString())).thenReturn(null); + + Field field = ReflectionUtils.findField(spy.getClass(), "delegate"); + field.setAccessible(true); + ReflectionUtils.setField(field, spy, delegate); + + spy.readAuthentication(anyString()); + verify(delegate).readAuthentication(anyString()); + } + + @Test + public void readAccessTokenWhenCalledThenDelegateCalled() throws Exception { + JwkTokenStore spy = spy(this.jwkTokenStore); + JwtTokenStore delegate = mock(JwtTokenStore.class); + when(delegate.readAccessToken(anyString())).thenReturn(null); + + Field field = ReflectionUtils.findField(spy.getClass(), "delegate"); + field.setAccessible(true); + ReflectionUtils.setField(field, spy, delegate); + + spy.readAccessToken(anyString()); + verify(delegate).readAccessToken(anyString()); } @Test public void storeAccessTokenWhenCalledThenThrowJwkException() throws Exception { + this.setUpExpectedJwkException(); this.jwkTokenStore.storeAccessToken(null, null); } @Test public void removeAccessTokenWhenCalledThenThrowJwkException() throws Exception { + this.setUpExpectedJwkException(); this.jwkTokenStore.removeAccessToken(null); } @Test public void storeRefreshTokenWhenCalledThenThrowJwkException() throws Exception { + this.setUpExpectedJwkException(); this.jwkTokenStore.storeRefreshToken(null, null); } @Test public void readRefreshTokenWhenCalledThenThrowJwkException() throws Exception { + this.setUpExpectedJwkException(); this.jwkTokenStore.readRefreshToken(null); } @Test public void readAuthenticationForRefreshTokenWhenCalledThenThrowJwkException() throws Exception { + this.setUpExpectedJwkException(); this.jwkTokenStore.readAuthenticationForRefreshToken(null); } @Test public void removeRefreshTokenWhenCalledThenThrowJwkException() throws Exception { + this.setUpExpectedJwkException(); this.jwkTokenStore.removeRefreshToken(null); } @Test public void removeAccessTokenUsingRefreshTokenWhenCalledThenThrowJwkException() throws Exception { + this.setUpExpectedJwkException(); this.jwkTokenStore.removeAccessTokenUsingRefreshToken(null); } @Test public void getAccessTokenWhenCalledThenThrowJwkException() throws Exception { + this.setUpExpectedJwkException(); this.jwkTokenStore.getAccessToken(null); } @Test public void findTokensByClientIdAndUserNameWhenCalledThenThrowJwkException() throws Exception { + this.setUpExpectedJwkException(); this.jwkTokenStore.findTokensByClientIdAndUserName(null, null); } @Test public void findTokensByClientIdWhenCalledThenThrowJwkException() throws Exception { + this.setUpExpectedJwkException(); this.jwkTokenStore.findTokensByClientId(null); } + + private void setUpExpectedJwkException() { + this.thrown.expect(JwkException.class); + this.thrown.expectMessage("This operation is not supported."); + } } \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverterTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverterTest.java index a27db63f8..3c830838e 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverterTest.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwkVerifyingJwtAccessTokenConverterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import static org.springframework.security.oauth2.provider.token.store.jwk.JwtTestUtil.createJwtHeader; /** - * @author jgrandja + * @author Joe Grandja */ public class JwkVerifyingJwtAccessTokenConverterTest { @@ -58,7 +58,7 @@ public void decodeWhenKeyIdHeaderInvalidThenThrowJwkException() throws Exception this.thrown.expectMessage("Invalid JOSE Header kid (invalid-key-id)"); JwkDefinition jwkDefinition = this.createRSAJwkDefinition("key-id-1", JwkDefinition.CryptoAlgorithm.RS256); JwkDefinitionSource jwkDefinitionSource = mock(JwkDefinitionSource.class); - when(jwkDefinitionSource.getDefinitionRefreshIfNecessary("key-id-1")).thenReturn(jwkDefinition); + when(jwkDefinitionSource.getDefinitionLoadIfNecessary("key-id-1")).thenReturn(jwkDefinition); JwkVerifyingJwtAccessTokenConverter accessTokenConverter = new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource); String jwt = createJwt(createJwtHeader("invalid-key-id", JwkDefinition.CryptoAlgorithm.RS256)); @@ -71,7 +71,7 @@ public void decodeWhenAlgorithmHeaderMissingThenThrowJwkException() throws Excep this.thrown.expectMessage("Invalid JWT/JWS: alg is a required JOSE Header"); JwkDefinition jwkDefinition = this.createRSAJwkDefinition("key-id-1", JwkDefinition.CryptoAlgorithm.RS256); JwkDefinitionSource jwkDefinitionSource = mock(JwkDefinitionSource.class); - when(jwkDefinitionSource.getDefinitionRefreshIfNecessary("key-id-1")).thenReturn(jwkDefinition); + when(jwkDefinitionSource.getDefinitionLoadIfNecessary("key-id-1")).thenReturn(jwkDefinition); JwkVerifyingJwtAccessTokenConverter accessTokenConverter = new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource); String jwt = createJwt(createJwtHeader("key-id-1", null)); @@ -85,7 +85,7 @@ public void decodeWhenAlgorithmHeaderDoesNotMatchJwkAlgorithmThenThrowJwkExcepti "does not match algorithm associated to JWK with kid (key-id-1)"); JwkDefinition jwkDefinition = this.createRSAJwkDefinition("key-id-1", JwkDefinition.CryptoAlgorithm.RS256); JwkDefinitionSource jwkDefinitionSource = mock(JwkDefinitionSource.class); - when(jwkDefinitionSource.getDefinitionRefreshIfNecessary("key-id-1")).thenReturn(jwkDefinition); + when(jwkDefinitionSource.getDefinitionLoadIfNecessary("key-id-1")).thenReturn(jwkDefinition); JwkVerifyingJwtAccessTokenConverter accessTokenConverter = new JwkVerifyingJwtAccessTokenConverter(jwkDefinitionSource); String jwt = createJwt(createJwtHeader("key-id-1", JwkDefinition.CryptoAlgorithm.RS512)); @@ -104,6 +104,6 @@ private JwkDefinition createRSAJwkDefinition(JwkDefinition.KeyType keyType, String modulus, String exponent) { - return new RSAJwkDefinition(keyId, publicKeyUse, algorithm, modulus, exponent); + return new RsaJwkDefinition(keyId, publicKeyUse, algorithm, modulus, exponent); } } \ No newline at end of file diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverterTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverterTest.java index f652e61c7..a7689d5c3 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverterTest.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtHeaderConverterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import static org.springframework.security.oauth2.provider.token.store.jwk.JwtTestUtil.createJwt; /** - * @author jgrandja + * @author Joe Grandja */ public class JwtHeaderConverterTest { private final JwtHeaderConverter converter = new JwtHeaderConverter(); diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtTestUtil.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtTestUtil.java index b8df0e87b..5aab5d5e9 100644 --- a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtTestUtil.java +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/JwtTestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/RsaJwkDefinitionTest.java b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/RsaJwkDefinitionTest.java new file mode 100644 index 000000000..72ca34d3c --- /dev/null +++ b/spring-security-oauth2/src/test/java/org/springframework/security/oauth2/provider/token/store/jwk/RsaJwkDefinitionTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.oauth2.provider.token.store.jwk; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Joe Grandja + */ +public class RsaJwkDefinitionTest { + + @Test + public void constructorWhenArgumentsPassedThenAttributesAreCorrectlySet() throws Exception { + String keyId = "key-id-1"; + JwkDefinition.PublicKeyUse publicKeyUse = JwkDefinition.PublicKeyUse.ENC; + JwkDefinition.CryptoAlgorithm algorithm = JwkDefinition.CryptoAlgorithm.RS384; + String modulus = "AMh-pGAj9vX2gwFDyrXot1f2YfHgh8h0Qx6w9IqLL"; + String exponent = "AQAB"; + + RsaJwkDefinition rsaJwkDefinition = new RsaJwkDefinition( + keyId, publicKeyUse, algorithm, modulus, exponent); + + assertEquals(keyId, rsaJwkDefinition.getKeyId()); + assertEquals(JwkDefinition.KeyType.RSA, rsaJwkDefinition.getKeyType()); + assertEquals(publicKeyUse, rsaJwkDefinition.getPublicKeyUse()); + assertEquals(algorithm, rsaJwkDefinition.getAlgorithm()); + assertEquals(modulus, rsaJwkDefinition.getModulus()); + assertEquals(exponent, rsaJwkDefinition.getExponent()); + } +} \ No newline at end of file From f6c1d3f931754fa0dd9a82544e867c4e35e4b487 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 1 Mar 2017 19:59:41 -0500 Subject: [PATCH 14/15] Release version 2.1.0.RELEASE --- pom.xml | 2 +- samples/oauth/sparklr/pom.xml | 2 +- samples/oauth/tonr/pom.xml | 2 +- samples/oauth2/sparklr/pom.xml | 2 +- samples/oauth2/tonr/pom.xml | 2 +- samples/pom.xml | 2 +- spring-security-oauth/pom.xml | 2 +- spring-security-oauth2/pom.xml | 2 +- tests/annotation/approval/pom.xml | 2 +- tests/annotation/client/pom.xml | 2 +- tests/annotation/common/pom.xml | 2 +- tests/annotation/custom-authentication/pom.xml | 2 +- tests/annotation/custom-grant/pom.xml | 2 +- tests/annotation/form/pom.xml | 2 +- tests/annotation/jaxb/pom.xml | 2 +- tests/annotation/jdbc/pom.xml | 2 +- tests/annotation/jpa/pom.xml | 2 +- tests/annotation/jwt/pom.xml | 2 +- tests/annotation/mappings/pom.xml | 2 +- tests/annotation/multi/pom.xml | 2 +- tests/annotation/pom.xml | 4 ++-- tests/annotation/resource/pom.xml | 2 +- tests/annotation/ssl/pom.xml | 2 +- tests/annotation/vanilla/pom.xml | 2 +- tests/pom.xml | 2 +- tests/xml/approval/pom.xml | 2 +- tests/xml/client/pom.xml | 2 +- tests/xml/common/pom.xml | 2 +- tests/xml/form/pom.xml | 2 +- tests/xml/jdbc/pom.xml | 2 +- tests/xml/jwt/pom.xml | 2 +- tests/xml/mappings/pom.xml | 2 +- tests/xml/pom.xml | 4 ++-- tests/xml/vanilla/pom.xml | 2 +- 34 files changed, 36 insertions(+), 36 deletions(-) diff --git a/pom.xml b/pom.xml index 3d5e79f56..bd0477f35 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ OAuth for Spring Security Parent Project for OAuth Support for Spring Security pom - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE http://static.springframework.org/spring-security/oauth diff --git a/samples/oauth/sparklr/pom.xml b/samples/oauth/sparklr/pom.xml index 4cc3debd2..91e000ecd 100644 --- a/samples/oauth/sparklr/pom.xml +++ b/samples/oauth/sparklr/pom.xml @@ -5,7 +5,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE ../../.. diff --git a/samples/oauth/tonr/pom.xml b/samples/oauth/tonr/pom.xml index 570abf329..6d48cf227 100644 --- a/samples/oauth/tonr/pom.xml +++ b/samples/oauth/tonr/pom.xml @@ -5,7 +5,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE ../../.. diff --git a/samples/oauth2/sparklr/pom.xml b/samples/oauth2/sparklr/pom.xml index fa285f8af..cca907196 100644 --- a/samples/oauth2/sparklr/pom.xml +++ b/samples/oauth2/sparklr/pom.xml @@ -5,7 +5,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE ../../.. diff --git a/samples/oauth2/tonr/pom.xml b/samples/oauth2/tonr/pom.xml index 1131e00b1..5c5de38d2 100644 --- a/samples/oauth2/tonr/pom.xml +++ b/samples/oauth2/tonr/pom.xml @@ -6,7 +6,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE ../../.. diff --git a/samples/pom.xml b/samples/pom.xml index 2c56b940a..2ff069739 100755 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE spring-security-oauth-samples diff --git a/spring-security-oauth/pom.xml b/spring-security-oauth/pom.xml index 7dd4a4c1d..aa4bbe97a 100644 --- a/spring-security-oauth/pom.xml +++ b/spring-security-oauth/pom.xml @@ -4,7 +4,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE spring-security-oauth diff --git a/spring-security-oauth2/pom.xml b/spring-security-oauth2/pom.xml index 4949b5ba2..cd9cd7c0e 100644 --- a/spring-security-oauth2/pom.xml +++ b/spring-security-oauth2/pom.xml @@ -5,7 +5,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE spring-security-oauth2 diff --git a/tests/annotation/approval/pom.xml b/tests/annotation/approval/pom.xml index 2dc67ad4d..02a45f1a3 100644 --- a/tests/annotation/approval/pom.xml +++ b/tests/annotation/approval/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/client/pom.xml b/tests/annotation/client/pom.xml index 555854c6d..8854f5fab 100644 --- a/tests/annotation/client/pom.xml +++ b/tests/annotation/client/pom.xml @@ -11,7 +11,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/common/pom.xml b/tests/annotation/common/pom.xml index ec1d24e93..f67e73cd5 100644 --- a/tests/annotation/common/pom.xml +++ b/tests/annotation/common/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/custom-authentication/pom.xml b/tests/annotation/custom-authentication/pom.xml index f2f8cb481..d524766ee 100644 --- a/tests/annotation/custom-authentication/pom.xml +++ b/tests/annotation/custom-authentication/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/custom-grant/pom.xml b/tests/annotation/custom-grant/pom.xml index 3ef3ac2de..1f1c65f98 100644 --- a/tests/annotation/custom-grant/pom.xml +++ b/tests/annotation/custom-grant/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/form/pom.xml b/tests/annotation/form/pom.xml index 311156648..c6baa174e 100644 --- a/tests/annotation/form/pom.xml +++ b/tests/annotation/form/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/jaxb/pom.xml b/tests/annotation/jaxb/pom.xml index 05fc8f4cd..5d2bc2cd1 100644 --- a/tests/annotation/jaxb/pom.xml +++ b/tests/annotation/jaxb/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/jdbc/pom.xml b/tests/annotation/jdbc/pom.xml index b38274a78..51c136659 100644 --- a/tests/annotation/jdbc/pom.xml +++ b/tests/annotation/jdbc/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/jpa/pom.xml b/tests/annotation/jpa/pom.xml index b82319fc0..a7ba7a4ac 100644 --- a/tests/annotation/jpa/pom.xml +++ b/tests/annotation/jpa/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/jwt/pom.xml b/tests/annotation/jwt/pom.xml index 6d92b9026..e2364ddee 100644 --- a/tests/annotation/jwt/pom.xml +++ b/tests/annotation/jwt/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/mappings/pom.xml b/tests/annotation/mappings/pom.xml index 8346ed914..b2b1a2765 100644 --- a/tests/annotation/mappings/pom.xml +++ b/tests/annotation/mappings/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/multi/pom.xml b/tests/annotation/multi/pom.xml index e2634b7c1..25cee2368 100644 --- a/tests/annotation/multi/pom.xml +++ b/tests/annotation/multi/pom.xml @@ -9,7 +9,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/pom.xml b/tests/annotation/pom.xml index 6f139dded..ebb378cf7 100644 --- a/tests/annotation/pom.xml +++ b/tests/annotation/pom.xml @@ -4,7 +4,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE pom @@ -39,7 +39,7 @@ org.springframework.security.oauth spring-security-oauth2 - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE jackson-mapper-asl diff --git a/tests/annotation/resource/pom.xml b/tests/annotation/resource/pom.xml index 6d3b500f1..bbe5e3288 100644 --- a/tests/annotation/resource/pom.xml +++ b/tests/annotation/resource/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/ssl/pom.xml b/tests/annotation/ssl/pom.xml index 13dbe9cd3..3c2ae4c67 100644 --- a/tests/annotation/ssl/pom.xml +++ b/tests/annotation/ssl/pom.xml @@ -11,7 +11,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/annotation/vanilla/pom.xml b/tests/annotation/vanilla/pom.xml index 590597180..99dd26ae0 100644 --- a/tests/annotation/vanilla/pom.xml +++ b/tests/annotation/vanilla/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/pom.xml b/tests/pom.xml index 4d58bbcb0..770460930 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -4,7 +4,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE spring-security-oauth-tests diff --git a/tests/xml/approval/pom.xml b/tests/xml/approval/pom.xml index f66b6a0f6..9c209c065 100644 --- a/tests/xml/approval/pom.xml +++ b/tests/xml/approval/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/xml/client/pom.xml b/tests/xml/client/pom.xml index 349d602a2..b604409eb 100644 --- a/tests/xml/client/pom.xml +++ b/tests/xml/client/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/xml/common/pom.xml b/tests/xml/common/pom.xml index 654689b20..f4b5da6e3 100644 --- a/tests/xml/common/pom.xml +++ b/tests/xml/common/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/xml/form/pom.xml b/tests/xml/form/pom.xml index e6302c055..83f7a4481 100644 --- a/tests/xml/form/pom.xml +++ b/tests/xml/form/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/xml/jdbc/pom.xml b/tests/xml/jdbc/pom.xml index e777afb3d..bd3ef53f1 100644 --- a/tests/xml/jdbc/pom.xml +++ b/tests/xml/jdbc/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/xml/jwt/pom.xml b/tests/xml/jwt/pom.xml index 965689999..17fe67982 100644 --- a/tests/xml/jwt/pom.xml +++ b/tests/xml/jwt/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/xml/mappings/pom.xml b/tests/xml/mappings/pom.xml index 84d9a32d9..9297aebc1 100644 --- a/tests/xml/mappings/pom.xml +++ b/tests/xml/mappings/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE diff --git a/tests/xml/pom.xml b/tests/xml/pom.xml index 421c0c48a..21fa83142 100644 --- a/tests/xml/pom.xml +++ b/tests/xml/pom.xml @@ -4,7 +4,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE pom @@ -33,7 +33,7 @@ org.springframework.security.oauth spring-security-oauth2 - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE jackson-mapper-asl diff --git a/tests/xml/vanilla/pom.xml b/tests/xml/vanilla/pom.xml index f64a2d1f2..3f3ffe999 100644 --- a/tests/xml/vanilla/pom.xml +++ b/tests/xml/vanilla/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.0.13.BUILD-SNAPSHOT + 2.1.0.RELEASE From 1cb82dd21e6aea0b906b6192a9c87be68472f265 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Thu, 2 Mar 2017 17:46:55 -0500 Subject: [PATCH 15/15] Next development version --- pom.xml | 2 +- samples/oauth/sparklr/pom.xml | 2 +- samples/oauth/tonr/pom.xml | 2 +- samples/oauth2/sparklr/pom.xml | 2 +- samples/oauth2/tonr/pom.xml | 2 +- samples/pom.xml | 2 +- spring-security-oauth/pom.xml | 2 +- spring-security-oauth2/pom.xml | 2 +- tests/annotation/approval/pom.xml | 2 +- tests/annotation/client/pom.xml | 2 +- tests/annotation/common/pom.xml | 2 +- tests/annotation/custom-authentication/pom.xml | 2 +- tests/annotation/custom-grant/pom.xml | 2 +- tests/annotation/form/pom.xml | 2 +- tests/annotation/jaxb/pom.xml | 2 +- tests/annotation/jdbc/pom.xml | 2 +- tests/annotation/jpa/pom.xml | 2 +- tests/annotation/jwt/pom.xml | 2 +- tests/annotation/mappings/pom.xml | 2 +- tests/annotation/multi/pom.xml | 2 +- tests/annotation/pom.xml | 4 ++-- tests/annotation/resource/pom.xml | 2 +- tests/annotation/ssl/pom.xml | 2 +- tests/annotation/vanilla/pom.xml | 2 +- tests/pom.xml | 2 +- tests/xml/approval/pom.xml | 2 +- tests/xml/client/pom.xml | 2 +- tests/xml/common/pom.xml | 2 +- tests/xml/form/pom.xml | 2 +- tests/xml/jdbc/pom.xml | 2 +- tests/xml/jwt/pom.xml | 2 +- tests/xml/mappings/pom.xml | 2 +- tests/xml/pom.xml | 4 ++-- tests/xml/vanilla/pom.xml | 2 +- 34 files changed, 36 insertions(+), 36 deletions(-) diff --git a/pom.xml b/pom.xml index bd0477f35..b05c742dc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ OAuth for Spring Security Parent Project for OAuth Support for Spring Security pom - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT http://static.springframework.org/spring-security/oauth diff --git a/samples/oauth/sparklr/pom.xml b/samples/oauth/sparklr/pom.xml index 91e000ecd..d5985dc6d 100644 --- a/samples/oauth/sparklr/pom.xml +++ b/samples/oauth/sparklr/pom.xml @@ -5,7 +5,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT ../../.. diff --git a/samples/oauth/tonr/pom.xml b/samples/oauth/tonr/pom.xml index 6d48cf227..283337497 100644 --- a/samples/oauth/tonr/pom.xml +++ b/samples/oauth/tonr/pom.xml @@ -5,7 +5,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT ../../.. diff --git a/samples/oauth2/sparklr/pom.xml b/samples/oauth2/sparklr/pom.xml index cca907196..1f9013dd3 100644 --- a/samples/oauth2/sparklr/pom.xml +++ b/samples/oauth2/sparklr/pom.xml @@ -5,7 +5,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT ../../.. diff --git a/samples/oauth2/tonr/pom.xml b/samples/oauth2/tonr/pom.xml index 5c5de38d2..50334a463 100644 --- a/samples/oauth2/tonr/pom.xml +++ b/samples/oauth2/tonr/pom.xml @@ -6,7 +6,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT ../../.. diff --git a/samples/pom.xml b/samples/pom.xml index 2ff069739..96c07851c 100755 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT spring-security-oauth-samples diff --git a/spring-security-oauth/pom.xml b/spring-security-oauth/pom.xml index aa4bbe97a..587b63fac 100644 --- a/spring-security-oauth/pom.xml +++ b/spring-security-oauth/pom.xml @@ -4,7 +4,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT spring-security-oauth diff --git a/spring-security-oauth2/pom.xml b/spring-security-oauth2/pom.xml index cd9cd7c0e..a850142f0 100644 --- a/spring-security-oauth2/pom.xml +++ b/spring-security-oauth2/pom.xml @@ -5,7 +5,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT spring-security-oauth2 diff --git a/tests/annotation/approval/pom.xml b/tests/annotation/approval/pom.xml index 02a45f1a3..d1110a48a 100644 --- a/tests/annotation/approval/pom.xml +++ b/tests/annotation/approval/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/client/pom.xml b/tests/annotation/client/pom.xml index 8854f5fab..d2aebd623 100644 --- a/tests/annotation/client/pom.xml +++ b/tests/annotation/client/pom.xml @@ -11,7 +11,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/common/pom.xml b/tests/annotation/common/pom.xml index f67e73cd5..dc24d1048 100644 --- a/tests/annotation/common/pom.xml +++ b/tests/annotation/common/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/custom-authentication/pom.xml b/tests/annotation/custom-authentication/pom.xml index d524766ee..38884d6b4 100644 --- a/tests/annotation/custom-authentication/pom.xml +++ b/tests/annotation/custom-authentication/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/custom-grant/pom.xml b/tests/annotation/custom-grant/pom.xml index 1f1c65f98..648084bea 100644 --- a/tests/annotation/custom-grant/pom.xml +++ b/tests/annotation/custom-grant/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/form/pom.xml b/tests/annotation/form/pom.xml index c6baa174e..0170024a6 100644 --- a/tests/annotation/form/pom.xml +++ b/tests/annotation/form/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/jaxb/pom.xml b/tests/annotation/jaxb/pom.xml index 5d2bc2cd1..6b76b5c08 100644 --- a/tests/annotation/jaxb/pom.xml +++ b/tests/annotation/jaxb/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/jdbc/pom.xml b/tests/annotation/jdbc/pom.xml index 51c136659..2035871b8 100644 --- a/tests/annotation/jdbc/pom.xml +++ b/tests/annotation/jdbc/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/jpa/pom.xml b/tests/annotation/jpa/pom.xml index a7ba7a4ac..e973072a1 100644 --- a/tests/annotation/jpa/pom.xml +++ b/tests/annotation/jpa/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/jwt/pom.xml b/tests/annotation/jwt/pom.xml index e2364ddee..46c3a4609 100644 --- a/tests/annotation/jwt/pom.xml +++ b/tests/annotation/jwt/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/mappings/pom.xml b/tests/annotation/mappings/pom.xml index b2b1a2765..eff643328 100644 --- a/tests/annotation/mappings/pom.xml +++ b/tests/annotation/mappings/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/multi/pom.xml b/tests/annotation/multi/pom.xml index 25cee2368..554119314 100644 --- a/tests/annotation/multi/pom.xml +++ b/tests/annotation/multi/pom.xml @@ -9,7 +9,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/pom.xml b/tests/annotation/pom.xml index ebb378cf7..065a54434 100644 --- a/tests/annotation/pom.xml +++ b/tests/annotation/pom.xml @@ -4,7 +4,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT pom @@ -39,7 +39,7 @@ org.springframework.security.oauth spring-security-oauth2 - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT jackson-mapper-asl diff --git a/tests/annotation/resource/pom.xml b/tests/annotation/resource/pom.xml index bbe5e3288..df34736a2 100644 --- a/tests/annotation/resource/pom.xml +++ b/tests/annotation/resource/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/ssl/pom.xml b/tests/annotation/ssl/pom.xml index 3c2ae4c67..9a6a8a01a 100644 --- a/tests/annotation/ssl/pom.xml +++ b/tests/annotation/ssl/pom.xml @@ -11,7 +11,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/annotation/vanilla/pom.xml b/tests/annotation/vanilla/pom.xml index 99dd26ae0..7b31b952a 100644 --- a/tests/annotation/vanilla/pom.xml +++ b/tests/annotation/vanilla/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/pom.xml b/tests/pom.xml index 770460930..234e3141c 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -4,7 +4,7 @@ org.springframework.security.oauth spring-security-oauth-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT spring-security-oauth-tests diff --git a/tests/xml/approval/pom.xml b/tests/xml/approval/pom.xml index 9c209c065..c8569a5d2 100644 --- a/tests/xml/approval/pom.xml +++ b/tests/xml/approval/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/xml/client/pom.xml b/tests/xml/client/pom.xml index b604409eb..049f30793 100644 --- a/tests/xml/client/pom.xml +++ b/tests/xml/client/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/xml/common/pom.xml b/tests/xml/common/pom.xml index f4b5da6e3..4d15848f3 100644 --- a/tests/xml/common/pom.xml +++ b/tests/xml/common/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/xml/form/pom.xml b/tests/xml/form/pom.xml index 83f7a4481..37d5a50f7 100644 --- a/tests/xml/form/pom.xml +++ b/tests/xml/form/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/xml/jdbc/pom.xml b/tests/xml/jdbc/pom.xml index bd3ef53f1..1c130362a 100644 --- a/tests/xml/jdbc/pom.xml +++ b/tests/xml/jdbc/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/xml/jwt/pom.xml b/tests/xml/jwt/pom.xml index 17fe67982..0275e803f 100644 --- a/tests/xml/jwt/pom.xml +++ b/tests/xml/jwt/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/xml/mappings/pom.xml b/tests/xml/mappings/pom.xml index 9297aebc1..f55d557c9 100644 --- a/tests/xml/mappings/pom.xml +++ b/tests/xml/mappings/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT diff --git a/tests/xml/pom.xml b/tests/xml/pom.xml index 21fa83142..887dca74e 100644 --- a/tests/xml/pom.xml +++ b/tests/xml/pom.xml @@ -4,7 +4,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT pom @@ -33,7 +33,7 @@ org.springframework.security.oauth spring-security-oauth2 - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT jackson-mapper-asl diff --git a/tests/xml/vanilla/pom.xml b/tests/xml/vanilla/pom.xml index 3f3ffe999..abe8c9fa1 100644 --- a/tests/xml/vanilla/pom.xml +++ b/tests/xml/vanilla/pom.xml @@ -10,7 +10,7 @@ org.demo spring-oauth2-tests-xml-parent - 2.1.0.RELEASE + 2.1.1.BUILD-SNAPSHOT