Jwt -> Authentication Conversion
Exposes ability to specify a strategy for converting Jwt into an Authentication, specifically in JwtAuthenticationProvider. Fixes: gh-5629
This commit is contained in:
parent
938dbbf424
commit
d610f31425
|
@ -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;
|
|||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* Also with {@link #jwt()} consider
|
||||
*
|
||||
* <ul>
|
||||
* <li>
|
||||
* customizing the conversion from a {@link Jwt} to an {@link org.springframework.security.core.Authentication} with
|
||||
* {@link JwtConfigurer#jwtAuthenticationConverter(Converter)}
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Security Filters</h2>
|
||||
*
|
||||
* The following {@code Filter}s are populated when {@link #jwt()} is configured:
|
||||
|
@ -182,9 +195,12 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||
}
|
||||
|
||||
JwtDecoder decoder = this.jwtConfigurer.getJwtDecoder();
|
||||
Converter<Jwt, ? extends AbstractAuthenticationToken> 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<H extends HttpSecurityBuilder<
|
|||
|
||||
private JwtDecoder decoder;
|
||||
|
||||
private Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter =
|
||||
new JwtAuthenticationConverter();
|
||||
|
||||
JwtConfigurer(ApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
@ -209,10 +228,21 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||
return this;
|
||||
}
|
||||
|
||||
public JwtConfigurer jwtAuthenticationConverter
|
||||
(Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter) {
|
||||
|
||||
this.jwtAuthenticationConverter = jwtAuthenticationConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OAuth2ResourceServerConfigurer<H> and() {
|
||||
return OAuth2ResourceServerConfigurer.this;
|
||||
}
|
||||
|
||||
Converter<Jwt, ? extends AbstractAuthenticationToken> getJwtAuthenticationConverter() {
|
||||
return this.jwtAuthenticationConverter;
|
||||
}
|
||||
|
||||
JwtDecoder getJwtDecoder() {
|
||||
if ( this.decoder == null ) {
|
||||
return this.context.getBean(JwtDecoder.class);
|
||||
|
|
|
@ -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<String, Object> 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<Jwt, JwtAuthenticationToken> 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<Jwt, JwtAuthenticationToken> 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<Jwt, JwtAuthenticationToken> 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<Jwt, AbstractAuthenticationToken> getJwtAuthenticationConverter() {
|
||||
return new JwtAuthenticationConverter() {
|
||||
@Override
|
||||
protected Collection<GrantedAuthority> 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;
|
||||
|
|
|
@ -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<Jwt, AbstractAuthenticationToken> {
|
||||
private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
|
||||
|
||||
private static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES =
|
||||
Arrays.asList("scope", "scp");
|
||||
|
||||
|
||||
JwtAuthenticationToken convert(Jwt jwt) {
|
||||
Collection<GrantedAuthority> authorities =
|
||||
this.getScopes(jwt)
|
||||
public final AbstractAuthenticationToken convert(Jwt jwt) {
|
||||
Collection<GrantedAuthority> authorities = extractAuthorities(jwt);
|
||||
return new JwtAuthenticationToken(jwt, authorities);
|
||||
}
|
||||
|
||||
protected Collection<GrantedAuthority> 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<String> getScopes(Jwt jwt) {
|
|
@ -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<Jwt, ? extends AbstractAuthenticationToken> 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<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter) {
|
||||
|
||||
Assert.notNull(jwtAuthenticationConverter, "jwtAuthenticationConverter cannot be null");
|
||||
this.jwtAuthenticationConverter = jwtAuthenticationConverter;
|
||||
}
|
||||
|
||||
private static OAuth2Error invalidToken(String message) {
|
||||
try {
|
||||
return new BearerTokenError(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<GrantedAuthority> 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<GrantedAuthority> 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<GrantedAuthority> 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<GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
assertThat(authorities).containsExactly();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() {
|
||||
Map<String, Object> 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<GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
assertThat(authorities).containsExactly(
|
||||
new SimpleGrantedAuthority("SCOPE_missive:read"),
|
||||
new SimpleGrantedAuthority("SCOPE_missive:write"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenTokenHasEmptyScopeAndNonEmptyScpThenScopeAttributeIsTranslatedToNoAuthorities() {
|
||||
Map<String, Object> 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<GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
assertThat(authorities).containsExactly();
|
||||
}
|
||||
|
||||
private Jwt jwt(Map<String, Object> claims) {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put("alg", JwsAlgorithms.RS256);
|
||||
|
||||
return new Jwt("token", Instant.now(), Instant.now().plusSeconds(3600), headers, claims);
|
||||
}
|
||||
}
|
|
@ -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<Jwt, JwtAuthenticationToken> 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<GrantedAuthority> 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<GrantedAuthority> 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<GrantedAuthority> 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<GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
assertThat(authorities).containsExactly();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenTokenHasBothScopeAndScpThenScopeAttributeIsTranslatedToAuthorities() {
|
||||
BearerTokenAuthenticationToken token = this.authentication();
|
||||
|
||||
Map<String, Object> 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<GrantedAuthority> 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<String, Object> 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<GrantedAuthority> 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();
|
||||
|
|
Loading…
Reference in New Issue