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 + * + * + * *

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();