Add OAuth2TokenCustomizer

Closes gh-199
This commit is contained in:
Joe Grandja
2021-01-18 09:31:06 -05:00
parent 3f310eec00
commit adf96b4e25
25 changed files with 1126 additions and 279 deletions

View File

@@ -18,7 +18,9 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.function.BiConsumer;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
@@ -33,19 +35,29 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.TokenType;
@@ -53,7 +65,9 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.test.web.servlet.MockMvc;
@@ -90,13 +104,16 @@ public class OAuth2AuthorizationCodeGrantTests {
// https://tools.ietf.org/html/rfc7636#appendix-B
private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM";
private static final String AUTHORITIES_CLAIM = "authorities";
private static RegisteredClientRepository registeredClientRepository;
private static OAuth2AuthorizationService authorizationService;
private static JWKSource<SecurityContext> jwkSource;
private static NimbusJwsEncoder jwtEncoder;
private static BiConsumer<JoseHeader.Builder, JwtClaimsSet.Builder> jwtCustomizer;
private static NimbusJwtDecoder jwtDecoder;
private static ProviderSettings providerSettings;
private static HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();
@Rule
public final SpringTestRule spring = new SpringTestRule();
@@ -111,8 +128,7 @@ public class OAuth2AuthorizationCodeGrantTests {
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
jwtEncoder = new NimbusJwsEncoder(jwkSource);
jwtCustomizer = mock(BiConsumer.class);
jwtEncoder.setJwtCustomizer(jwtCustomizer);
jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build();
providerSettings = new ProviderSettings()
.authorizationEndpoint("/test/authorize")
.tokenEndpoint("/test/token");
@@ -186,8 +202,17 @@ public class OAuth2AuthorizationCodeGrantTests {
eq(TokenType.AUTHORIZATION_CODE)))
.thenReturn(authorization);
assertTokenRequestReturnsAccessTokenResponse(
OAuth2AccessTokenResponse accessTokenResponse = assertTokenRequestReturnsAccessTokenResponse(
registeredClient, authorization, OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI);
// Assert user authorities was propagated as claim in JWT
Jwt jwt = jwtDecoder.decode(accessTokenResponse.getAccessToken().getTokenValue());
List<String> authoritiesClaim = jwt.getClaim(AUTHORITIES_CLAIM);
Authentication principal = authorization.getAttribute(OAuth2AuthorizationAttributeNames.PRINCIPAL);
Set<String> userAuthorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities);
}
@Test
@@ -208,10 +233,10 @@ public class OAuth2AuthorizationCodeGrantTests {
registeredClient, authorization, providerSettings.tokenEndpoint());
}
private void assertTokenRequestReturnsAccessTokenResponse(RegisteredClient registeredClient,
private OAuth2AccessTokenResponse assertTokenRequestReturnsAccessTokenResponse(RegisteredClient registeredClient,
OAuth2Authorization authorization, String tokenEndpointUri) throws Exception {
this.mvc.perform(post(tokenEndpointUri)
MvcResult mvcResult = this.mvc.perform(post(tokenEndpointUri)
.params(getTokenRequestParameters(registeredClient, authorization))
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
registeredClient.getClientId(), registeredClient.getClientSecret())))
@@ -222,13 +247,19 @@ public class OAuth2AuthorizationCodeGrantTests {
.andExpect(jsonPath("$.token_type").isNotEmpty())
.andExpect(jsonPath("$.expires_in").isNotEmpty())
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
.andExpect(jsonPath("$.scope").isNotEmpty());
.andExpect(jsonPath("$.scope").isNotEmpty())
.andReturn();
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
verify(authorizationService).findByToken(
eq(authorization.getTokens().getToken(OAuth2AuthorizationCode.class).getTokenValue()),
eq(TokenType.AUTHORIZATION_CODE));
verify(authorizationService).save(any());
MockHttpServletResponse servletResponse = mvcResult.getResponse();
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
}
@Test
@@ -295,8 +326,6 @@ public class OAuth2AuthorizationCodeGrantTests {
.params(getTokenRequestParameters(registeredClient, authorization))
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
registeredClient.getClientId(), registeredClient.getClientSecret())));
verify(jwtCustomizer).accept(any(JoseHeader.Builder.class), any(JwtClaimsSet.Builder.class));
}
private static MultiValueMap<String, String> getAuthorizationRequestParameters(RegisteredClient registeredClient) {
@@ -345,6 +374,20 @@ public class OAuth2AuthorizationCodeGrantTests {
JWKSource<SecurityContext> jwkSource() {
return jwkSource;
}
@Bean
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType()) &&
TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
Authentication principal = context.getPrincipal();
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
context.getClaims().claim(AUTHORITIES_CLAIM, authorities);
}
};
}
}
@EnableWebSecurity

View File

@@ -41,6 +41,8 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@@ -65,6 +67,7 @@ public class OAuth2ClientCredentialsGrantTests {
private static RegisteredClientRepository registeredClientRepository;
private static OAuth2AuthorizationService authorizationService;
private static JWKSource<SecurityContext> jwkSource;
private static OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
@Rule
public final SpringTestRule spring = new SpringTestRule();
@@ -78,10 +81,13 @@ public class OAuth2ClientCredentialsGrantTests {
authorizationService = mock(OAuth2AuthorizationService.class);
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
jwtCustomizer = mock(OAuth2TokenCustomizer.class);
}
@SuppressWarnings("unchecked")
@Before
public void setup() {
reset(jwtCustomizer);
reset(registeredClientRepository);
reset(authorizationService);
}
@@ -115,6 +121,7 @@ public class OAuth2ClientCredentialsGrantTests {
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.scope").value("scope1 scope2"));
verify(jwtCustomizer).customize(any());
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
verify(authorizationService).save(any());
}
@@ -136,6 +143,7 @@ public class OAuth2ClientCredentialsGrantTests {
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.scope").value("scope1 scope2"));
verify(jwtCustomizer).customize(any());
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
verify(authorizationService).save(any());
}
@@ -166,5 +174,10 @@ public class OAuth2ClientCredentialsGrantTests {
JWKSource<SecurityContext> jwkSource() {
return jwkSource;
}
@Bean
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return jwtCustomizer;
}
}
}

View File

@@ -18,6 +18,9 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
@@ -31,24 +34,40 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -68,9 +87,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @since 0.0.3
*/
public class OAuth2RefreshTokenGrantTests {
private static final String AUTHORITIES_CLAIM = "authorities";
private static RegisteredClientRepository registeredClientRepository;
private static OAuth2AuthorizationService authorizationService;
private static JWKSource<SecurityContext> jwkSource;
private static NimbusJwtDecoder jwtDecoder;
private static HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();
@Rule
public final SpringTestRule spring = new SpringTestRule();
@@ -84,6 +107,7 @@ public class OAuth2RefreshTokenGrantTests {
authorizationService = mock(OAuth2AuthorizationService.class);
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build();
}
@Before
@@ -106,7 +130,7 @@ public class OAuth2RefreshTokenGrantTests {
eq(TokenType.REFRESH_TOKEN)))
.thenReturn(authorization);
this.mvc.perform(post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI)
MvcResult mvcResult = this.mvc.perform(post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI)
.params(getRefreshTokenRequestParameters(authorization))
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
registeredClient.getClientId(), registeredClient.getClientSecret())))
@@ -117,7 +141,8 @@ public class OAuth2RefreshTokenGrantTests {
.andExpect(jsonPath("$.token_type").isNotEmpty())
.andExpect(jsonPath("$.expires_in").isNotEmpty())
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
.andExpect(jsonPath("$.scope").isNotEmpty());
.andExpect(jsonPath("$.scope").isNotEmpty())
.andReturn();
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
verify(authorizationService).findByToken(
@@ -125,6 +150,20 @@ public class OAuth2RefreshTokenGrantTests {
eq(TokenType.REFRESH_TOKEN));
verify(authorizationService).save(any());
MockHttpServletResponse servletResponse = mvcResult.getResponse();
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter.read(
OAuth2AccessTokenResponse.class, httpResponse);
// Assert user authorities was propagated as claim in JWT
Jwt jwt = jwtDecoder.decode(accessTokenResponse.getAccessToken().getTokenValue());
List<String> authoritiesClaim = jwt.getClaim(AUTHORITIES_CLAIM);
Authentication principal = authorization.getAttribute(OAuth2AuthorizationAttributeNames.PRINCIPAL);
Set<String> userAuthorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities);
}
private static MultiValueMap<String, String> getRefreshTokenRequestParameters(OAuth2Authorization authorization) {
@@ -160,5 +199,18 @@ public class OAuth2RefreshTokenGrantTests {
JWKSource<SecurityContext> jwkSource() {
return jwkSource;
}
@Bean
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
if (AuthorizationGrantType.REFRESH_TOKEN.equals(context.getAuthorizationGrantType())) {
Authentication principal = context.getPrincipal();
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
context.getClaims().claim(AUTHORITIES_CLAIM, authorities);
}
};
}
}
}

View File

@@ -18,6 +18,9 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
@@ -32,15 +35,28 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -48,7 +64,9 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.test.web.servlet.MockMvc;
@@ -80,10 +98,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Daniel Garnier-Moiroux
*/
public class OidcTests {
private static final String issuerUrl = "https://example.com/issuer1";
private static final String ISSUER_URL = "https://example.com/issuer1";
private static final String AUTHORITIES_CLAIM = "authorities";
private static RegisteredClientRepository registeredClientRepository;
private static OAuth2AuthorizationService authorizationService;
private static JWKSource<SecurityContext> jwkSource;
private static NimbusJwtDecoder jwtDecoder;
private static HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();
@Rule
public final SpringTestRule spring = new SpringTestRule();
@@ -97,6 +119,7 @@ public class OidcTests {
authorizationService = mock(OAuth2AuthorizationService.class);
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build();
}
@Before
@@ -111,7 +134,7 @@ public class OidcTests {
this.mvc.perform(get(OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("issuer").value(issuerUrl));
.andExpect(jsonPath("issuer").value(ISSUER_URL));
}
@Test
@@ -148,7 +171,7 @@ public class OidcTests {
MvcResult mvcResult = this.mvc.perform(get(OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI)
.params(getAuthorizationRequestParameters(registeredClient))
.with(user("user")))
.with(user("user").roles("A", "B")))
.andExpect(status().is3xxRedirection())
.andReturn();
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://example.com\\?code=.{15,}&state=state");
@@ -164,7 +187,7 @@ public class OidcTests {
eq(TokenType.AUTHORIZATION_CODE)))
.thenReturn(authorization);
this.mvc.perform(post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI)
mvcResult = this.mvc.perform(post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI)
.params(getTokenRequestParameters(registeredClient, authorization))
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
registeredClient.getClientId(), registeredClient.getClientSecret())))
@@ -176,13 +199,28 @@ public class OidcTests {
.andExpect(jsonPath("$.expires_in").isNotEmpty())
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
.andExpect(jsonPath("$.scope").isNotEmpty())
.andExpect(jsonPath("$.id_token").isNotEmpty());
.andExpect(jsonPath("$.id_token").isNotEmpty())
.andReturn();
verify(registeredClientRepository, times(2)).findByClientId(eq(registeredClient.getClientId()));
verify(authorizationService).findByToken(
eq(authorization.getTokens().getToken(OAuth2AuthorizationCode.class).getTokenValue()),
eq(TokenType.AUTHORIZATION_CODE));
verify(authorizationService, times(2)).save(any());
MockHttpServletResponse servletResponse = mvcResult.getResponse();
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
// Assert user authorities was propagated as claim in ID Token
Jwt idToken = jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
List<String> authoritiesClaim = idToken.getClaim(AUTHORITIES_CLAIM);
Authentication principal = authorization.getAttribute(OAuth2AuthorizationAttributeNames.PRINCIPAL);
Set<String> userAuthorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities);
}
private static MultiValueMap<String, String> getAuthorizationRequestParameters(RegisteredClient registeredClient) {
@@ -231,6 +269,19 @@ public class OidcTests {
JWKSource<SecurityContext> jwkSource() {
return jwkSource;
}
@Bean
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {
Authentication principal = context.getPrincipal();
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
context.getClaims().claim(AUTHORITIES_CLAIM, authorities);
}
};
}
}
@EnableWebSecurity
@@ -239,7 +290,7 @@ public class OidcTests {
@Bean
ProviderSettings providerSettings() {
return new ProviderSettings().issuer(issuerUrl);
return new ProviderSettings().issuer(ISSUER_URL);
}
}

View File

@@ -21,7 +21,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import com.nimbusds.jose.KeySourceException;
import com.nimbusds.jose.jwk.ECKey;
@@ -49,7 +48,6 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link NimbusJwsEncoder}.
@@ -77,12 +75,6 @@ public class NimbusJwsEncoderTests {
.withMessage("jwkSource cannot be null");
}
@Test
public void setJwtCustomizerWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.jwsEncoder.setJwtCustomizer(null))
.withMessage("jwtCustomizer cannot be null");
}
@Test
public void encodeWhenHeadersNullThenThrowIllegalArgumentException() {
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
@@ -99,22 +91,6 @@ public class NimbusJwsEncoderTests {
.withMessage("claims cannot be null");
}
@Test
public void encodeWhenCustomizerSetThenCalled() {
RSAKey rsaJwk = TestJwks.DEFAULT_RSA_JWK;
this.jwkList.add(rsaJwk);
BiConsumer<JoseHeader.Builder, JwtClaimsSet.Builder> jwtCustomizer = mock(BiConsumer.class);
this.jwsEncoder.setJwtCustomizer(jwtCustomizer);
JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
this.jwsEncoder.encode(joseHeader, jwtClaimsSet);
verify(jwtCustomizer).accept(any(JoseHeader.Builder.class), any(JwtClaimsSet.Builder.class));
}
@Test
public void encodeWhenJwkSelectFailedThenThrowJwtEncodingException() throws Exception {
this.jwkSource = mock(JWKSource.class);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2021 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,6 +15,12 @@
*/
package org.springframework.security.oauth2.server.authorization;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Map;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
@@ -24,11 +30,6 @@ import org.springframework.security.oauth2.server.authorization.client.TestRegis
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Map;
/**
* @author Joe Grandja
* @author Daniel Garnier-Moiroux
@@ -63,6 +64,8 @@ public class TestOAuth2Authorizations {
.principalName("principal")
.tokens(OAuth2Tokens.builder().token(authorizationCode).accessToken(accessToken).refreshToken(refreshToken).build())
.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST, authorizationRequest)
.attribute(OAuth2AuthorizationAttributeNames.PRINCIPAL,
new TestingAuthenticationToken("principal", null, "ROLE_A", "ROLE_B"))
.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES, authorizationRequest.getScopes());
}
}

View File

@@ -15,10 +15,17 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@@ -28,11 +35,12 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
@@ -41,14 +49,10 @@ import org.springframework.security.oauth2.server.authorization.TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenMetadata;
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
@@ -69,6 +73,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
private static final String AUTHORIZATION_CODE = "code";
private OAuth2AuthorizationService authorizationService;
private JwtEncoder jwtEncoder;
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
private OAuth2AuthorizationCodeAuthenticationProvider authenticationProvider;
@Before
@@ -77,6 +82,8 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
this.jwtEncoder = mock(JwtEncoder.class);
this.authenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(
this.authorizationService, this.jwtEncoder);
this.jwtCustomizer = mock(OAuth2TokenCustomizer.class);
this.authenticationProvider.setJwtCustomizer(this.jwtCustomizer);
}
@Test
@@ -93,6 +100,13 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
.hasMessage("jwtEncoder cannot be null");
}
@Test
public void setJwtCustomizerWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.authenticationProvider.setJwtCustomizer(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("jwtCustomizer cannot be null");
}
@Test
public void supportsWhenTypeOAuth2AuthorizationCodeAuthenticationTokenThenReturnTrue() {
assertThat(this.authenticationProvider.supports(OAuth2AuthorizationCodeAuthenticationToken.class)).isTrue();
@@ -225,6 +239,18 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
ArgumentCaptor<JwtEncodingContext> jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class);
verify(this.jwtCustomizer).customize(jwtEncodingContextCaptor.capture());
JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue();
assertThat(jwtEncodingContext.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(jwtEncodingContext.<Authentication>getPrincipal()).isEqualTo(authorization.getAttribute(OAuth2AuthorizationAttributeNames.PRINCIPAL));
assertThat(jwtEncodingContext.getAuthorization()).isEqualTo(authorization);
assertThat(jwtEncodingContext.getTokenType()).isEqualTo(TokenType.ACCESS_TOKEN);
assertThat(jwtEncodingContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(jwtEncodingContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication);
assertThat(jwtEncodingContext.getHeaders()).isNotNull();
assertThat(jwtEncodingContext.getClaims()).isNotNull();
ArgumentCaptor<JwtClaimsSet> jwtClaimsSetCaptor = ArgumentCaptor.forClass(JwtClaimsSet.class);
verify(this.jwtEncoder).encode(any(), jwtClaimsSetCaptor.capture());
JwtClaimsSet jwtClaimsSet = jwtClaimsSetCaptor.getValue();
@@ -264,6 +290,29 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
ArgumentCaptor<JwtEncodingContext> jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class);
verify(this.jwtCustomizer, times(2)).customize(jwtEncodingContextCaptor.capture());
// Access Token context
JwtEncodingContext accessTokenContext = jwtEncodingContextCaptor.getAllValues().get(0);
assertThat(accessTokenContext.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(accessTokenContext.<Authentication>getPrincipal()).isEqualTo(authorization.getAttribute(OAuth2AuthorizationAttributeNames.PRINCIPAL));
assertThat(accessTokenContext.getAuthorization()).isEqualTo(authorization);
assertThat(accessTokenContext.getTokenType()).isEqualTo(TokenType.ACCESS_TOKEN);
assertThat(accessTokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(accessTokenContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication);
assertThat(accessTokenContext.getHeaders()).isNotNull();
assertThat(accessTokenContext.getClaims()).isNotNull();
// ID Token context
JwtEncodingContext idTokenContext = jwtEncodingContextCaptor.getAllValues().get(1);
assertThat(idTokenContext.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(idTokenContext.<Authentication>getPrincipal()).isEqualTo(authorization.getAttribute(OAuth2AuthorizationAttributeNames.PRINCIPAL));
assertThat(idTokenContext.getAuthorization()).isEqualTo(authorization);
assertThat(idTokenContext.getTokenType().getValue()).isEqualTo(OidcParameterNames.ID_TOKEN);
assertThat(idTokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(idTokenContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication);
assertThat(idTokenContext.getHeaders()).isNotNull();
assertThat(idTokenContext.getClaims()).isNotNull();
verify(this.jwtEncoder, times(2)).encode(any(), any()); // Access token and ID Token
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);

View File

@@ -15,28 +15,34 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
@@ -53,6 +59,7 @@ import static org.mockito.Mockito.when;
public class OAuth2ClientCredentialsAuthenticationProviderTests {
private OAuth2AuthorizationService authorizationService;
private JwtEncoder jwtEncoder;
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
private OAuth2ClientCredentialsAuthenticationProvider authenticationProvider;
@Before
@@ -61,6 +68,8 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
this.jwtEncoder = mock(JwtEncoder.class);
this.authenticationProvider = new OAuth2ClientCredentialsAuthenticationProvider(
this.authorizationService, this.jwtEncoder);
this.jwtCustomizer = mock(OAuth2TokenCustomizer.class);
this.authenticationProvider.setJwtCustomizer(this.jwtCustomizer);
}
@Test
@@ -77,6 +86,13 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
.hasMessage("jwtEncoder cannot be null");
}
@Test
public void setJwtCustomizerWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.authenticationProvider.setJwtCustomizer(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("jwtCustomizer cannot be null");
}
@Test
public void supportsWhenSupportedAuthenticationThenTrue() {
assertThat(this.authenticationProvider.supports(OAuth2ClientCredentialsAuthenticationToken.class)).isTrue();
@@ -152,7 +168,7 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
OAuth2ClientCredentialsAuthenticationToken authentication =
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScope);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt(requestedScope));
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -165,11 +181,23 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt(registeredClient.getScopes()));
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
ArgumentCaptor<JwtEncodingContext> jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class);
verify(this.jwtCustomizer).customize(jwtEncodingContextCaptor.capture());
JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue();
assertThat(jwtEncodingContext.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(jwtEncodingContext.<Authentication>getPrincipal()).isEqualTo(clientPrincipal);
assertThat(jwtEncodingContext.getAuthorization()).isNull();
assertThat(jwtEncodingContext.getTokenType()).isEqualTo(TokenType.ACCESS_TOKEN);
assertThat(jwtEncodingContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
assertThat(jwtEncodingContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication);
assertThat(jwtEncodingContext.getHeaders()).isNotNull();
assertThat(jwtEncodingContext.getClaims()).isNotNull();
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(this.authorizationService).save(authorizationCaptor.capture());
OAuth2Authorization authorization = authorizationCaptor.getValue();
@@ -182,13 +210,14 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
assertThat(accessTokenAuthentication.getAccessToken()).isEqualTo(authorization.getTokens().getAccessToken());
}
private static Jwt createJwt() {
private static Jwt createJwt(Set<String> scope) {
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
return Jwt.withTokenValue("token")
.header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName())
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.claim(OAuth2ParameterNames.SCOPE, scope)
.build();
}
}

View File

@@ -15,20 +15,30 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
@@ -36,14 +46,10 @@ import org.springframework.security.oauth2.server.authorization.TestOAuth2Author
import org.springframework.security.oauth2.server.authorization.TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenMetadata;
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashSet;
import java.util.Set;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -56,25 +62,24 @@ import static org.mockito.Mockito.when;
* Tests for {@link OAuth2RefreshTokenAuthenticationProvider}.
*
* @author Alexey Nesterov
* @author Joe Grandja
* @since 0.0.3
*/
public class OAuth2RefreshTokenAuthenticationProviderTests {
private OAuth2AuthorizationService authorizationService;
private JwtEncoder jwtEncoder;
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
private OAuth2RefreshTokenAuthenticationProvider authenticationProvider;
@Before
public void setUp() {
this.authorizationService = mock(OAuth2AuthorizationService.class);
this.jwtEncoder = mock(JwtEncoder.class);
Jwt jwt = Jwt.withTokenValue("refreshed-access-token")
.header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName())
.issuedAt(Instant.now())
.expiresAt(Instant.now().plus(1, ChronoUnit.HOURS))
.build();
when(this.jwtEncoder.encode(any(), any())).thenReturn(jwt);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt(Collections.singleton("scope1")));
this.authenticationProvider = new OAuth2RefreshTokenAuthenticationProvider(
this.authorizationService, this.jwtEncoder);
this.jwtCustomizer = mock(OAuth2TokenCustomizer.class);
this.authenticationProvider.setJwtCustomizer(this.jwtCustomizer);
}
@Test
@@ -93,6 +98,13 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
.isEqualTo("jwtEncoder cannot be null");
}
@Test
public void setJwtCustomizerWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.authenticationProvider.setJwtCustomizer(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("jwtCustomizer cannot be null");
}
@Test
public void supportsWhenSupportedAuthenticationThenTrue() {
assertThat(this.authenticationProvider.supports(OAuth2RefreshTokenAuthenticationToken.class)).isTrue();
@@ -119,6 +131,18 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
ArgumentCaptor<JwtEncodingContext> jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class);
verify(this.jwtCustomizer).customize(jwtEncodingContextCaptor.capture());
JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue();
assertThat(jwtEncodingContext.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(jwtEncodingContext.<Authentication>getPrincipal()).isEqualTo(authorization.getAttribute(OAuth2AuthorizationAttributeNames.PRINCIPAL));
assertThat(jwtEncodingContext.getAuthorization()).isEqualTo(authorization);
assertThat(jwtEncodingContext.getTokenType()).isEqualTo(TokenType.ACCESS_TOKEN);
assertThat(jwtEncodingContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN);
assertThat(jwtEncodingContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication);
assertThat(jwtEncodingContext.getHeaders()).isNotNull();
assertThat(jwtEncodingContext.getClaims()).isNotNull();
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(this.authorizationService).save(authorizationCaptor.capture());
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
@@ -340,4 +364,15 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
.extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
}
private static Jwt createJwt(Set<String> scope) {
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
return Jwt.withTokenValue("refreshed-access-token")
.header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName())
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.claim(OAuth2ParameterNames.SCOPE, scope)
.build();
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2020-2021 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
*
* https://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.server.authorization.token;
import org.junit.Test;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.TestJoseHeaders;
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link JwtEncodingContext}.
*
* @author Joe Grandja
*/
public class JwtEncodingContextTests {
@Test
public void withWhenHeadersNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JwtEncodingContext.with(null, TestJwtClaimsSets.jwtClaimsSet()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("headersBuilder cannot be null");
}
@Test
public void withWhenClaimsNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JwtEncodingContext.with(TestJoseHeaders.joseHeader(), null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("claimsBuilder cannot be null");
}
@Test
public void setWhenValueNullThenThrowIllegalArgumentException() {
JwtEncodingContext.Builder builder = JwtEncodingContext
.with(TestJoseHeaders.joseHeader(), TestJwtClaimsSets.jwtClaimsSet());
assertThatThrownBy(() -> builder.registeredClient(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> builder.principal(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> builder.authorization(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> builder.tokenType(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> builder.authorizationGrantType(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> builder.authorizationGrant(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> builder.put(null, ""))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
public void buildWhenAllValuesProvidedThenAllValuesAreSet() {
JoseHeader.Builder headers = TestJoseHeaders.joseHeader();
JwtClaimsSet.Builder claims = TestJwtClaimsSets.jwtClaimsSet();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "password");
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build();
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
OAuth2AuthorizationCodeAuthenticationToken authorizationGrant =
new OAuth2AuthorizationCodeAuthenticationToken(
"code", clientPrincipal, authorizationRequest.getRedirectUri(), null);
JwtEncodingContext context = JwtEncodingContext.with(headers, claims)
.registeredClient(registeredClient)
.principal(principal)
.authorization(authorization)
.tokenType(TokenType.ACCESS_TOKEN)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrant(authorizationGrant)
.put("custom-key-1", "custom-value-1")
.context(ctx -> ctx.put("custom-key-2", "custom-value-2"))
.build();
assertThat(context.getHeaders()).isEqualTo(headers);
assertThat(context.getClaims()).isEqualTo(claims);
assertThat(context.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(context.<Authentication>getPrincipal()).isEqualTo(principal);
assertThat(context.getAuthorization()).isEqualTo(authorization);
assertThat(context.getTokenType()).isEqualTo(TokenType.ACCESS_TOKEN);
assertThat(context.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(context.<Authentication>getAuthorizationGrant()).isEqualTo(authorizationGrant);
assertThat(context.<String>get("custom-key-1")).isEqualTo("custom-value-1");
assertThat(context.<String>get("custom-key-2")).isEqualTo("custom-value-2");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2021 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,15 +15,25 @@
*/
package org.springframework.security.oauth2.server.authorization.web;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.function.Consumer;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@@ -44,13 +54,6 @@ import org.springframework.security.oauth2.server.authorization.client.TestRegis
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
import org.springframework.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
@@ -464,6 +467,8 @@ public class OAuth2AuthorizationEndpointFilterTests {
OAuth2Authorization authorization = authorizationCaptor.getValue();
assertThat(authorization.getRegisteredClientId()).isEqualTo(registeredClient.getId());
assertThat(authorization.getPrincipalName()).isEqualTo(this.authentication.getPrincipal().toString());
assertThat(authorization.<Authentication>getAttribute(OAuth2AuthorizationAttributeNames.PRINCIPAL))
.isEqualTo(this.authentication);
OAuth2AuthorizationCode authorizationCode = authorization.getTokens().getToken(OAuth2AuthorizationCode.class);
assertThat(authorizationCode).isNotNull();
@@ -511,6 +516,8 @@ public class OAuth2AuthorizationEndpointFilterTests {
OAuth2Authorization authorization = authorizationCaptor.getValue();
assertThat(authorization.getRegisteredClientId()).isEqualTo(registeredClient.getId());
assertThat(authorization.getPrincipalName()).isEqualTo(this.authentication.getPrincipal().toString());
assertThat(authorization.<Authentication>getAttribute(OAuth2AuthorizationAttributeNames.PRINCIPAL))
.isEqualTo(this.authentication);
OAuth2AuthorizationCode authorizationCode = authorization.getTokens().getToken(OAuth2AuthorizationCode.class);
assertThat(authorizationCode).isNotNull();
@@ -556,6 +563,8 @@ public class OAuth2AuthorizationEndpointFilterTests {
OAuth2Authorization authorization = authorizationCaptor.getValue();
assertThat(authorization.getRegisteredClientId()).isEqualTo(registeredClient.getId());
assertThat(authorization.getPrincipalName()).isEqualTo(this.authentication.getPrincipal().toString());
assertThat(authorization.<Authentication>getAttribute(OAuth2AuthorizationAttributeNames.PRINCIPAL))
.isEqualTo(this.authentication);
String state = authorization.getAttribute(OAuth2AuthorizationAttributeNames.STATE);
assertThat(state).isNotNull();