1
0
mirror of synced 2026-08-05 17:57:15 +00:00

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:
Josh Cummings
2018-08-02 17:48:34 -06:00
committed by Rob Winch
parent 938dbbf424
commit d610f31425
7 changed files with 313 additions and 127 deletions
@@ -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;