diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java
index d03662615e..e75914fa4b 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java
@@ -19,6 +19,8 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -26,8 +28,10 @@ import org.springframework.security.config.annotation.web.configurers.CsrfConfig
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
@@ -71,6 +75,15 @@ import org.springframework.util.Assert;
*
*
*
+ * Also with {@link #jwt()} consider
+ *
+ *
+ * -
+ * customizing the conversion from a {@link Jwt} to an {@link org.springframework.security.core.Authentication} with
+ * {@link JwtConfigurer#jwtAuthenticationConverter(Converter)}
+ *
+ *
+ *
* Security Filters
*
* The following {@code Filter}s are populated when {@link #jwt()} is configured:
@@ -182,9 +195,12 @@ public final class OAuth2ResourceServerConfigurer jwtAuthenticationConverter =
+ this.jwtConfigurer.getJwtAuthenticationConverter();
JwtAuthenticationProvider provider =
new JwtAuthenticationProvider(decoder);
+ provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
provider = postProcess(provider);
http.authenticationProvider(provider);
@@ -195,6 +211,9 @@ public final class OAuth2ResourceServerConfigurer jwtAuthenticationConverter =
+ new JwtAuthenticationConverter();
+
JwtConfigurer(ApplicationContext context) {
this.context = context;
}
@@ -209,10 +228,21 @@ public final class OAuth2ResourceServerConfigurer jwtAuthenticationConverter) {
+
+ this.jwtAuthenticationConverter = jwtAuthenticationConverter;
+ return this;
+ }
+
public OAuth2ResourceServerConfigurer and() {
return OAuth2ResourceServerConfigurer.this;
}
+ Converter getJwtAuthenticationConverter() {
+ return this.jwtAuthenticationConverter;
+ }
+
JwtDecoder getJwtDecoder() {
if ( this.decoder == null ) {
return this.context.getBean(JwtDecoder.class);
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java
index d592f0664c..e94c8ebb12 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java
@@ -24,6 +24,7 @@ import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
+import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
@@ -47,12 +48,14 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -62,6 +65,7 @@ import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
@@ -71,8 +75,9 @@ import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtException;
-import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
@@ -102,6 +107,7 @@ import static org.hamcrest.core.StringStartsWith.startsWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
@@ -125,6 +131,8 @@ public class OAuth2ResourceServerConfigurerTests {
private static final Map JWT_CLAIMS = Collections.singletonMap(JwtClaimNames.SUB, JWT_SUBJECT);
private static final Jwt JWT = new Jwt(JWT_TOKEN, Instant.MIN, Instant.MAX, JWT_HEADERS, JWT_CLAIMS);
private static final String JWK_SET_URI = "https://mock.org";
+ private static final JwtAuthenticationToken JWT_AUTHENTICATION_TOKEN =
+ new JwtAuthenticationToken(JWT, Collections.emptyList());
@Autowired(required = false)
MockMvc mvc;
@@ -898,6 +906,45 @@ public class OAuth2ResourceServerConfigurerTests {
.andExpect(invalidTokenHeader("Jwt expired at"));
}
+ // -- converter
+
+ @Test
+ public void requestWhenJwtAuthenticationConverterConfiguredOnDslThenIsUsed()
+ throws Exception {
+
+ this.spring.register(JwtDecoderConfig.class, JwtAuthenticationConverterConfiguredOnDsl.class,
+ BasicController.class).autowire();
+
+ Converter jwtAuthenticationConverter =
+ this.spring.getContext().getBean(JwtAuthenticationConverterConfiguredOnDsl.class)
+ .getJwtAuthenticationConverter();
+ when(jwtAuthenticationConverter.convert(JWT)).thenReturn(JWT_AUTHENTICATION_TOKEN);
+
+ JwtDecoder jwtDecoder = this.spring.getContext().getBean(JwtDecoder.class);
+ when(jwtDecoder.decode(anyString())).thenReturn(JWT);
+
+ this.mvc.perform(get("/")
+ .with(bearerToken(JWT_TOKEN)))
+ .andExpect(status().isOk());
+
+ verify(jwtAuthenticationConverter).convert(JWT);
+ }
+
+ @Test
+ public void requestWhenJwtAuthenticationConverterCustomizedAuthoritiesThenThoseAuthoritiesArePropagated()
+ throws Exception {
+
+ this.spring.register(JwtDecoderConfig.class, CustomAuthorityMappingConfig.class, BasicController.class)
+ .autowire();
+
+ JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
+ when(decoder.decode(JWT_TOKEN)).thenReturn(JWT);
+
+ this.mvc.perform(get("/requires-read-scope")
+ .with(bearerToken(JWT_TOKEN)))
+ .andExpect(status().isOk());
+ }
+
// -- In combination with other authentication providers
@Test
@@ -1139,6 +1186,59 @@ public class OAuth2ResourceServerConfigurerTests {
}
}
+ @EnableWebSecurity
+ static class JwtAuthenticationConverterConfiguredOnDsl extends WebSecurityConfigurerAdapter {
+ private final Converter jwtAuthenticationConverter = mock(Converter.class);
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+
+ http
+ .authorizeRequests()
+ .anyRequest().authenticated()
+ .and()
+ .oauth2()
+ .resourceServer()
+ .jwt()
+ .jwtAuthenticationConverter(getJwtAuthenticationConverter());
+
+ // @formatter:on
+ }
+
+ Converter getJwtAuthenticationConverter() {
+ return this.jwtAuthenticationConverter;
+ }
+ }
+
+ @EnableWebSecurity
+ static class CustomAuthorityMappingConfig extends WebSecurityConfigurerAdapter {
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+
+ http
+ .authorizeRequests()
+ .antMatchers("/requires-read-scope").access("hasAuthority('message:read')")
+ .and()
+ .oauth2()
+ .resourceServer()
+ .jwt()
+ .jwtAuthenticationConverter(getJwtAuthenticationConverter());
+
+ // @formatter:on
+ }
+
+ Converter getJwtAuthenticationConverter() {
+ return new JwtAuthenticationConverter() {
+ @Override
+ protected Collection extractAuthorities(Jwt jwt) {
+ return Collections.singletonList(new SimpleGrantedAuthority("message:read"));
+ }
+ };
+ }
+ }
+
@EnableWebSecurity
static class BasicAndResourceServerConfig extends WebSecurityConfigurerAdapter {
@Value("${mock.jwk-set-uri:https://example.org}") String uri;
diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java
similarity index 80%
rename from oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtConverter.java
rename to oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java
index 0ade72982f..0daae1e0c6 100644
--- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtConverter.java
+++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java
@@ -16,36 +16,41 @@
package org.springframework.security.oauth2.server.resource.authentication;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.util.StringUtils;
-
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Collectors;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.util.StringUtils;
+
/**
* @author Rob Winch
+ * @author Josh Cummings
* @since 5.1
*/
-class JwtConverter {
+public class JwtAuthenticationConverter implements Converter {
private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
private static final Collection WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES =
Arrays.asList("scope", "scp");
- JwtAuthenticationToken convert(Jwt jwt) {
- Collection authorities =
- this.getScopes(jwt)
+ public final AbstractAuthenticationToken convert(Jwt jwt) {
+ Collection authorities = extractAuthorities(jwt);
+ return new JwtAuthenticationToken(jwt, authorities);
+ }
+
+ protected Collection extractAuthorities(Jwt jwt) {
+ return this.getScopes(jwt)
.stream()
.map(authority -> SCOPE_AUTHORITY_PREFIX + authority)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
-
- return new JwtAuthenticationToken(jwt, authorities);
}
private Collection getScopes(Jwt jwt) {
diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.java
index fafb111422..909133738b 100644
--- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.java
+++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.java
@@ -17,7 +17,9 @@ package org.springframework.security.oauth2.server.resource.authentication;
import java.util.Collection;
+import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -59,7 +61,7 @@ import org.springframework.util.Assert;
public final class JwtAuthenticationProvider implements AuthenticationProvider {
private final JwtDecoder jwtDecoder;
- private final JwtConverter jwtConverter = new JwtConverter();
+ private Converter jwtAuthenticationConverter = new JwtAuthenticationConverter();
private static final OAuth2Error DEFAULT_INVALID_TOKEN =
invalidToken("An error occurred while attempting to decode the Jwt: Invalid token");
@@ -91,7 +93,7 @@ public final class JwtAuthenticationProvider implements AuthenticationProvider {
throw new OAuth2AuthenticationException(invalidToken, invalidToken.getDescription(), failed);
}
- JwtAuthenticationToken token = this.jwtConverter.convert(jwt);
+ AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt);
token.setDetails(bearer.getDetails());
return token;
@@ -105,6 +107,13 @@ public final class JwtAuthenticationProvider implements AuthenticationProvider {
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
}
+ public void setJwtAuthenticationConverter(
+ Converter jwtAuthenticationConverter) {
+
+ Assert.notNull(jwtAuthenticationConverter, "jwtAuthenticationConverter cannot be null");
+ this.jwtAuthenticationConverter = jwtAuthenticationConverter;
+ }
+
private static OAuth2Error invalidToken(String message) {
try {
return new BearerTokenError(
diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtReactiveAuthenticationManager.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtReactiveAuthenticationManager.java
index 84b40cbe17..2bec5bdf6f 100644
--- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtReactiveAuthenticationManager.java
+++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtReactiveAuthenticationManager.java
@@ -36,7 +36,7 @@ import reactor.core.publisher.Mono;
* @since 5.1
*/
public class JwtReactiveAuthenticationManager implements ReactiveAuthenticationManager {
- private final JwtConverter jwtConverter = new JwtConverter();
+ private final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
private final ReactiveJwtDecoder jwtDecoder;
@@ -52,7 +52,7 @@ public class JwtReactiveAuthenticationManager implements ReactiveAuthenticationM
.cast(BearerTokenAuthenticationToken.class)
.map(BearerTokenAuthenticationToken::getToken)
.flatMap(this.jwtDecoder::decode)
- .map(this.jwtConverter::convert)
+ .map(this.jwtAuthenticationConverter::convert)
.cast(Authentication.class)
.onErrorMap(JwtException.class, this::onError);
}
diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java
new file mode 100644
index 0000000000..5c64974a54
--- /dev/null
+++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2002-2018 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.server.resource.authentication;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.assertj.core.util.Maps;
+import org.junit.Test;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link JwtAuthenticationConverter}
+ *
+ * @author Josh Cummings
+ */
+public class JwtAuthenticationConverterTests {
+ JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
+
+ @Test
+ public void convertWhenTokenHasScopeAttributeThenTranslatedToAuthorities() {
+ Jwt jwt = this.jwt(Collections.singletonMap("scope", "message:read message:write"));
+
+ AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
+ Collection authorities = authentication.getAuthorities();
+
+ assertThat(authorities).containsExactly(
+ new SimpleGrantedAuthority("SCOPE_message:read"),
+ new SimpleGrantedAuthority("SCOPE_message:write"));
+ }
+
+ @Test
+ public void convertWhenTokenHasEmptyScopeAttributeThenTranslatedToNoAuthorities() {
+ Jwt jwt = this.jwt(Collections.singletonMap("scope", ""));
+
+ AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
+
+ Collection authorities = authentication.getAuthorities();
+
+ assertThat(authorities).containsExactly();
+ }
+
+ @Test
+ public void convertWhenTokenHasScpAttributeThenTranslatedToAuthorities() {
+ Jwt jwt = this.jwt(Collections.singletonMap("scp", Arrays.asList("message:read", "message:write")));
+
+ AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
+
+ Collection authorities = authentication.getAuthorities();
+
+ assertThat(authorities).containsExactly(
+ new SimpleGrantedAuthority("SCOPE_message:read"),
+ new SimpleGrantedAuthority("SCOPE_message:write"));
+ }
+
+ @Test
+ public void convertWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities() {
+ Jwt jwt = this.jwt(Maps.newHashMap("scp", Arrays.asList()));
+
+ AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
+
+ Collection authorities = authentication.getAuthorities();
+
+ assertThat(authorities).containsExactly();
+ }
+
+ @Test
+ public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() {
+ Map claims = new HashMap<>();
+ claims.put("scp", Arrays.asList("message:read", "message:write"));
+ claims.put("scope", "missive:read missive:write");
+ Jwt jwt = this.jwt(claims);
+
+ AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
+
+ Collection authorities = authentication.getAuthorities();
+
+ assertThat(authorities).containsExactly(
+ new SimpleGrantedAuthority("SCOPE_missive:read"),
+ new SimpleGrantedAuthority("SCOPE_missive:write"));
+ }
+
+ @Test
+ public void convertWhenTokenHasEmptyScopeAndNonEmptyScpThenScopeAttributeIsTranslatedToNoAuthorities() {
+ Map claims = new HashMap<>();
+ claims.put("scp", Arrays.asList("message:read", "message:write"));
+ claims.put("scope", "");
+ Jwt jwt = this.jwt(claims);
+
+ AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt);
+
+ Collection authorities = authentication.getAuthorities();
+
+ assertThat(authorities).containsExactly();
+ }
+
+ private Jwt jwt(Map claims) {
+ Map headers = new HashMap<>();
+ headers.put("alg", JwsAlgorithms.RS256);
+
+ return new Jwt("token", Instant.now(), Instant.now().plusSeconds(3600), headers, claims);
+ }
+}
diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java
index 51e898a5ea..eb4908dc5b 100644
--- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java
+++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProviderTests.java
@@ -16,21 +16,18 @@
package org.springframework.security.oauth2.server.resource.authentication;
import java.time.Instant;
-import java.util.Arrays;
-import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
-import org.assertj.core.util.Maps;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
import org.springframework.security.oauth2.jwt.Jwt;
@@ -41,6 +38,7 @@ import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
@@ -50,6 +48,9 @@ import static org.mockito.Mockito.when;
*/
@RunWith(MockitoJUnitRunner.class)
public class JwtAuthenticationProviderTests {
+ @Mock
+ Converter jwtAuthenticationConverter;
+
@Mock
JwtDecoder jwtDecoder;
@@ -59,6 +60,7 @@ public class JwtAuthenticationProviderTests {
public void setup() {
this.provider =
new JwtAuthenticationProvider(this.jwtDecoder);
+ this.provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
}
@Test
@@ -70,6 +72,7 @@ public class JwtAuthenticationProviderTests {
Jwt jwt = this.jwt(claims);
when(this.jwtDecoder.decode("token")).thenReturn(jwt);
+ when(this.jwtAuthenticationConverter.convert(jwt)).thenReturn(new JwtAuthenticationToken(jwt));
JwtAuthenticationToken authentication =
(JwtAuthenticationToken) this.provider.authenticate(token);
@@ -88,112 +91,6 @@ public class JwtAuthenticationProviderTests {
.matches(errorCode(BearerTokenErrorCodes.INVALID_TOKEN));
}
- @Test
- public void authenticateWhenTokenHasScopeAttributeThenTranslatedToAuthorities() {
- BearerTokenAuthenticationToken token = this.authentication();
-
- Jwt jwt = this.jwt(Maps.newHashMap("scope", "message:read message:write"));
-
- when(this.jwtDecoder.decode(token.getToken())).thenReturn(jwt);
-
- JwtAuthenticationToken authentication =
- (JwtAuthenticationToken) this.provider.authenticate(token);
-
- Collection authorities = authentication.getAuthorities();
-
- assertThat(authorities).containsExactly(
- new SimpleGrantedAuthority("SCOPE_message:read"),
- new SimpleGrantedAuthority("SCOPE_message:write"));
- }
-
- @Test
- public void authenticateWhenTokenHasEmptyScopeAttributeThenTranslatedToNoAuthorities() {
- BearerTokenAuthenticationToken token = this.authentication();
-
- Jwt jwt = this.jwt(Maps.newHashMap("scope", ""));
-
- when(this.jwtDecoder.decode(token.getToken())).thenReturn(jwt);
-
- JwtAuthenticationToken authentication =
- (JwtAuthenticationToken) this.provider.authenticate(token);
-
- Collection authorities = authentication.getAuthorities();
-
- assertThat(authorities).containsExactly();
- }
-
- @Test
- public void authenticateWhenTokenHasScpAttributeThenTranslatedToAuthorities() {
- BearerTokenAuthenticationToken token = this.authentication();
-
- Jwt jwt = this.jwt(Maps.newHashMap("scp", Arrays.asList("message:read", "message:write")));
-
- when(this.jwtDecoder.decode(token.getToken())).thenReturn(jwt);
-
- JwtAuthenticationToken authentication =
- (JwtAuthenticationToken) this.provider.authenticate(token);
-
- Collection authorities = authentication.getAuthorities();
-
- assertThat(authorities).containsExactly(
- new SimpleGrantedAuthority("SCOPE_message:read"),
- new SimpleGrantedAuthority("SCOPE_message:write"));
- }
-
- @Test
- public void authenticateWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities() {
- BearerTokenAuthenticationToken token = this.authentication();
-
- Jwt jwt = this.jwt(Maps.newHashMap("scp", Arrays.asList()));
-
- when(this.jwtDecoder.decode(token.getToken())).thenReturn(jwt);
-
- JwtAuthenticationToken authentication =
- (JwtAuthenticationToken) this.provider.authenticate(token);
-
- Collection authorities = authentication.getAuthorities();
-
- assertThat(authorities).containsExactly();
- }
-
- @Test
- public void authenticateWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() {
- BearerTokenAuthenticationToken token = this.authentication();
-
- Map claims = Maps.newHashMap("scp", Arrays.asList("message:read", "message:write"));
- claims.put("scope", "missive:read missive:write");
- Jwt jwt = this.jwt(claims);
-
- when(this.jwtDecoder.decode(token.getToken())).thenReturn(jwt);
-
- JwtAuthenticationToken authentication =
- (JwtAuthenticationToken) this.provider.authenticate(token);
-
- Collection authorities = authentication.getAuthorities();
-
- assertThat(authorities).containsExactly(
- new SimpleGrantedAuthority("SCOPE_missive:read"),
- new SimpleGrantedAuthority("SCOPE_missive:write"));
- }
-
- @Test
- public void authenticateWhenTokenHasEmptyScopeAndNonEmptyScpThenScopeAttributeIsTranslatedToNoAuthorities() {
- BearerTokenAuthenticationToken token = this.authentication();
-
- Map claims = Maps.newHashMap("scp", Arrays.asList("message:read", "message:write"));
- claims.put("scope", "");
- Jwt jwt = this.jwt(claims);
-
- when(this.jwtDecoder.decode(token.getToken())).thenReturn(jwt);
-
- JwtAuthenticationToken authentication =
- (JwtAuthenticationToken) this.provider.authenticate(token);
-
- Collection authorities = authentication.getAuthorities();
-
- assertThat(authorities).containsExactly();
- }
-
@Test
public void authenticateWhenDecoderThrowsIncompatibleErrorMessageThenWrapsWithGenericOne() {
BearerTokenAuthenticationToken token = this.authentication();
@@ -207,6 +104,23 @@ public class JwtAuthenticationProviderTests {
"An error occurred while attempting to decode the Jwt: Invalid token");
}
+ @Test
+ public void authenticateWhenConverterReturnsAuthenticationThenProviderPropagatesIt() {
+ BearerTokenAuthenticationToken token = this.authentication();
+ Object details = mock(Object.class);
+ token.setDetails(details);
+
+ Jwt jwt = this.jwt(Collections.singletonMap("some", "value"));
+ JwtAuthenticationToken authentication = new JwtAuthenticationToken(jwt);
+
+ when(this.jwtDecoder.decode(token.getToken())).thenReturn(jwt);
+ when(this.jwtAuthenticationConverter.convert(jwt)).thenReturn(authentication);
+
+ assertThat(this.provider.authenticate(token))
+ .isEqualTo(authentication)
+ .hasFieldOrPropertyWithValue("details", details);
+ }
+
@Test
public void supportsWhenBearerTokenAuthenticationTokenThenReturnsTrue() {
assertThat(this.provider.supports(BearerTokenAuthenticationToken.class)).isTrue();