Support configurable JwtDecoder for IdToken verification
Fixes gh-5717
This commit is contained in:
parent
be23ab8114
commit
8f4f52edb9
|
@ -16,6 +16,7 @@
|
||||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||||
|
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
@ -55,6 +56,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
|
||||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
|
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||||
|
@ -488,6 +490,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||||
|
|
||||||
OidcAuthorizationCodeAuthenticationProvider oidcAuthorizationCodeAuthenticationProvider =
|
OidcAuthorizationCodeAuthenticationProvider oidcAuthorizationCodeAuthenticationProvider =
|
||||||
new OidcAuthorizationCodeAuthenticationProvider(accessTokenResponseClient, oidcUserService);
|
new OidcAuthorizationCodeAuthenticationProvider(accessTokenResponseClient, oidcUserService);
|
||||||
|
JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = this.getJwtDecoderFactoryBean();
|
||||||
|
if (jwtDecoderFactory != null) {
|
||||||
|
oidcAuthorizationCodeAuthenticationProvider.setJwtDecoderFactory(jwtDecoderFactory);
|
||||||
|
}
|
||||||
if (userAuthoritiesMapper != null) {
|
if (userAuthoritiesMapper != null) {
|
||||||
oidcAuthorizationCodeAuthenticationProvider.setAuthoritiesMapper(userAuthoritiesMapper);
|
oidcAuthorizationCodeAuthenticationProvider.setAuthoritiesMapper(userAuthoritiesMapper);
|
||||||
}
|
}
|
||||||
|
@ -541,6 +547,19 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||||
return new AntPathRequestMatcher(loginProcessingUrl);
|
return new AntPathRequestMatcher(loginProcessingUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private JwtDecoderFactory<ClientRegistration> getJwtDecoderFactoryBean() {
|
||||||
|
ResolvableType type = ResolvableType.forClassWithGenerics(JwtDecoderFactory.class, ClientRegistration.class);
|
||||||
|
String[] names = this.getBuilder().getSharedObject(ApplicationContext.class).getBeanNamesForType(type);
|
||||||
|
if (names.length > 1) {
|
||||||
|
throw new NoUniqueBeanDefinitionException(type, names);
|
||||||
|
}
|
||||||
|
if (names.length == 1) {
|
||||||
|
return (JwtDecoderFactory<ClientRegistration>) this.getBuilder().getSharedObject(ApplicationContext.class).getBean(names[0]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private GrantedAuthoritiesMapper getGrantedAuthoritiesMapper() {
|
private GrantedAuthoritiesMapper getGrantedAuthoritiesMapper() {
|
||||||
GrantedAuthoritiesMapper grantedAuthoritiesMapper =
|
GrantedAuthoritiesMapper grantedAuthoritiesMapper =
|
||||||
this.getBuilder().getSharedObject(GrantedAuthoritiesMapper.class);
|
this.getBuilder().getSharedObject(GrantedAuthoritiesMapper.class);
|
||||||
|
|
|
@ -16,28 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.security.config.web.server;
|
package org.springframework.security.config.web.server;
|
||||||
|
|
||||||
import static org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint.DelegateEntry;
|
|
||||||
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.match;
|
|
||||||
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.notMatch;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.security.interfaces.RSAPublicKey;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
import reactor.util.context.Context;
|
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
|
@ -55,6 +33,8 @@ import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
|
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
|
||||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
||||||
|
@ -80,6 +60,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
|
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
|
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
|
||||||
|
@ -92,6 +73,7 @@ import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
|
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.server.WebFilterExchange;
|
import org.springframework.security.web.server.WebFilterExchange;
|
||||||
|
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
|
||||||
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
|
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
|
||||||
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
|
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
|
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
|
||||||
|
@ -159,9 +141,27 @@ import org.springframework.web.cors.reactive.DefaultCorsProcessor;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.WebFilter;
|
import org.springframework.web.server.WebFilter;
|
||||||
import org.springframework.web.server.WebFilterChain;
|
import org.springframework.web.server.WebFilterChain;
|
||||||
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
|
import reactor.core.publisher.Mono;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import reactor.util.context.Context;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint.DelegateEntry;
|
||||||
|
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.match;
|
||||||
|
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.notMatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link ServerHttpSecurity} is similar to Spring Security's {@code HttpSecurity} but for WebFlux.
|
* A {@link ServerHttpSecurity} is similar to Spring Security's {@code HttpSecurity} but for WebFlux.
|
||||||
|
@ -618,7 +618,14 @@ public class ServerHttpSecurity {
|
||||||
boolean oidcAuthenticationProviderEnabled = ClassUtils.isPresent(
|
boolean oidcAuthenticationProviderEnabled = ClassUtils.isPresent(
|
||||||
"org.springframework.security.oauth2.jwt.JwtDecoder", this.getClass().getClassLoader());
|
"org.springframework.security.oauth2.jwt.JwtDecoder", this.getClass().getClassLoader());
|
||||||
if (oidcAuthenticationProviderEnabled) {
|
if (oidcAuthenticationProviderEnabled) {
|
||||||
OidcAuthorizationCodeReactiveAuthenticationManager oidc = new OidcAuthorizationCodeReactiveAuthenticationManager(client, getOidcUserService());
|
OidcAuthorizationCodeReactiveAuthenticationManager oidc =
|
||||||
|
new OidcAuthorizationCodeReactiveAuthenticationManager(client, getOidcUserService());
|
||||||
|
ResolvableType type = ResolvableType.forClassWithGenerics(
|
||||||
|
ReactiveJwtDecoderFactory.class, ClientRegistration.class);
|
||||||
|
ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = getBeanOrNull(type);
|
||||||
|
if (jwtDecoderFactory != null) {
|
||||||
|
oidc.setJwtDecoderFactory(jwtDecoderFactory);
|
||||||
|
}
|
||||||
result = new DelegatingReactiveAuthenticationManager(oidc, result);
|
result = new DelegatingReactiveAuthenticationManager(oidc, result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -19,11 +19,12 @@ import org.apache.http.HttpHeaders;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.beans.PropertyAccessorFactory;
|
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.mock.web.MockFilterChain;
|
import org.springframework.mock.web.MockFilterChain;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
@ -50,7 +51,6 @@ import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||||
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
|
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
|
||||||
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
|
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
|
||||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
|
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
|
||||||
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
@ -66,6 +66,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
||||||
import org.springframework.security.web.FilterChainProxy;
|
import org.springframework.security.web.FilterChainProxy;
|
||||||
import org.springframework.security.web.context.HttpRequestResponseHolder;
|
import org.springframework.security.web.context.HttpRequestResponseHolder;
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
|
@ -81,6 +82,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
@ -369,8 +371,7 @@ public class OAuth2LoginConfigurerTests {
|
||||||
@Test
|
@Test
|
||||||
public void oidcLogin() throws Exception {
|
public void oidcLogin() throws Exception {
|
||||||
// setup application context
|
// setup application context
|
||||||
loadConfig(OAuth2LoginConfig.class);
|
loadConfig(OAuth2LoginConfig.class, JwtDecoderFactoryConfig.class);
|
||||||
registerJwtDecoder();
|
|
||||||
|
|
||||||
// setup authorization request
|
// setup authorization request
|
||||||
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
|
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
|
||||||
|
@ -396,8 +397,7 @@ public class OAuth2LoginConfigurerTests {
|
||||||
@Test
|
@Test
|
||||||
public void oidcLoginCustomWithConfigurer() throws Exception {
|
public void oidcLoginCustomWithConfigurer() throws Exception {
|
||||||
// setup application context
|
// setup application context
|
||||||
loadConfig(OAuth2LoginConfigCustomWithConfigurer.class);
|
loadConfig(OAuth2LoginConfigCustomWithConfigurer.class, JwtDecoderFactoryConfig.class);
|
||||||
registerJwtDecoder();
|
|
||||||
|
|
||||||
// setup authorization request
|
// setup authorization request
|
||||||
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
|
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
|
||||||
|
@ -423,8 +423,7 @@ public class OAuth2LoginConfigurerTests {
|
||||||
@Test
|
@Test
|
||||||
public void oidcLoginCustomWithBeanRegistration() throws Exception {
|
public void oidcLoginCustomWithBeanRegistration() throws Exception {
|
||||||
// setup application context
|
// setup application context
|
||||||
loadConfig(OAuth2LoginConfigCustomWithBeanRegistration.class);
|
loadConfig(OAuth2LoginConfigCustomWithBeanRegistration.class, JwtDecoderFactoryConfig.class);
|
||||||
registerJwtDecoder();
|
|
||||||
|
|
||||||
// setup authorization request
|
// setup authorization request
|
||||||
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
|
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
|
||||||
|
@ -447,6 +446,15 @@ public class OAuth2LoginConfigurerTests {
|
||||||
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OIDC_USER");
|
assertThat(authentication.getAuthorities()).last().hasToString("ROLE_OIDC_USER");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void oidcLoginCustomWithNoUniqueJwtDecoderFactory() {
|
||||||
|
assertThatThrownBy(() -> loadConfig(OAuth2LoginConfig.class, NoUniqueJwtDecoderFactoryConfig.class))
|
||||||
|
.hasRootCauseInstanceOf(NoUniqueBeanDefinitionException.class)
|
||||||
|
.hasMessageContaining("No qualifying bean of type " +
|
||||||
|
"'org.springframework.security.oauth2.jwt.JwtDecoderFactory<org.springframework.security.oauth2.client.registration.ClientRegistration>' " +
|
||||||
|
"available: expected single matching bean but found 2: jwtDecoderFactory1,jwtDecoderFactory2");
|
||||||
|
}
|
||||||
|
|
||||||
private void loadConfig(Class<?>... configs) {
|
private void loadConfig(Class<?>... configs) {
|
||||||
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
|
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
|
||||||
applicationContext.register(configs);
|
applicationContext.register(configs);
|
||||||
|
@ -455,25 +463,6 @@ public class OAuth2LoginConfigurerTests {
|
||||||
this.context = applicationContext;
|
this.context = applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerJwtDecoder() {
|
|
||||||
JwtDecoder decoder = token -> {
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
|
||||||
claims.put(IdTokenClaimNames.SUB, "sub123");
|
|
||||||
claims.put(IdTokenClaimNames.ISS, "http://localhost/iss");
|
|
||||||
claims.put(IdTokenClaimNames.AUD, Arrays.asList("clientId", "a", "u", "d"));
|
|
||||||
claims.put(IdTokenClaimNames.AZP, "clientId");
|
|
||||||
return new Jwt("token123", Instant.now(), Instant.now().plusSeconds(3600),
|
|
||||||
Collections.singletonMap("header1", "value1"), claims);
|
|
||||||
};
|
|
||||||
this.springSecurityFilterChain.getFilters("/login/oauth2/code/google").stream()
|
|
||||||
.filter(OAuth2LoginAuthenticationFilter.class::isInstance)
|
|
||||||
.findFirst()
|
|
||||||
.ifPresent(filter -> PropertyAccessorFactory.forDirectFieldAccess(filter)
|
|
||||||
.setPropertyValue(
|
|
||||||
"authenticationManager.providers[2].jwtDecoders['google']",
|
|
||||||
decoder));
|
|
||||||
}
|
|
||||||
|
|
||||||
private OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(String... scopes) {
|
private OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(String... scopes) {
|
||||||
return this.createOAuth2AuthorizationRequest(GOOGLE_CLIENT_REGISTRATION, scopes);
|
return this.createOAuth2AuthorizationRequest(GOOGLE_CLIENT_REGISTRATION, scopes);
|
||||||
}
|
}
|
||||||
|
@ -632,6 +621,42 @@ public class OAuth2LoginConfigurerTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class JwtDecoderFactoryConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
JwtDecoderFactory<ClientRegistration> jwtDecoderFactory() {
|
||||||
|
return clientRegistration -> getJwtDecoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JwtDecoder getJwtDecoder() {
|
||||||
|
return token -> {
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
claims.put(IdTokenClaimNames.SUB, "sub123");
|
||||||
|
claims.put(IdTokenClaimNames.ISS, "http://localhost/iss");
|
||||||
|
claims.put(IdTokenClaimNames.AUD, Arrays.asList("clientId", "a", "u", "d"));
|
||||||
|
claims.put(IdTokenClaimNames.AZP, "clientId");
|
||||||
|
return new Jwt("token123", Instant.now(), Instant.now().plusSeconds(3600),
|
||||||
|
Collections.singletonMap("header1", "value1"), claims);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class NoUniqueJwtDecoderFactoryConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
JwtDecoderFactory<ClientRegistration> jwtDecoderFactory1() {
|
||||||
|
return clientRegistration -> JwtDecoderFactoryConfig.getJwtDecoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
JwtDecoderFactory<ClientRegistration> jwtDecoderFactory2() {
|
||||||
|
return clientRegistration -> JwtDecoderFactoryConfig.getJwtDecoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> createOauth2AccessTokenResponseClient() {
|
private static OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> createOauth2AccessTokenResponseClient() {
|
||||||
return request -> {
|
return request -> {
|
||||||
Map<String, Object> additionalParameters = new HashMap<>();
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
|
|
@ -16,12 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.security.config.web.server;
|
package org.springframework.security.config.web.server;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
@ -34,15 +28,29 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFlux
|
||||||
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
|
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
|
||||||
import org.springframework.security.config.test.SpringTestRule;
|
import org.springframework.security.config.test.SpringTestRule;
|
||||||
import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder;
|
import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
|
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||||
|
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||||
|
import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager;
|
||||||
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
|
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges;
|
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers;
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.security.oauth2.core.user.TestOAuth2Users;
|
import org.springframework.security.oauth2.core.user.TestOAuth2Users;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
import org.springframework.security.web.server.WebFilterChainProxy;
|
import org.springframework.security.web.server.WebFilterChainProxy;
|
||||||
|
@ -51,9 +59,17 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.WebFilter;
|
import org.springframework.web.server.WebFilter;
|
||||||
import org.springframework.web.server.WebFilterChain;
|
import org.springframework.web.server.WebFilterChain;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
|
@ -72,6 +88,12 @@ public class OAuth2LoginTests {
|
||||||
.clientSecret("secret")
|
.clientSecret("secret")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
private static ClientRegistration google = CommonOAuth2Provider.GOOGLE
|
||||||
|
.getBuilder("google")
|
||||||
|
.clientId("client")
|
||||||
|
.clientSecret("secret")
|
||||||
|
.build();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void defaultLoginPageWithMultipleClientRegistrationsThenLinks() {
|
public void defaultLoginPageWithMultipleClientRegistrationsThenLinks() {
|
||||||
this.spring.register(OAuth2LoginWithMulitpleClientRegistrations.class).autowire();
|
this.spring.register(OAuth2LoginWithMulitpleClientRegistrations.class).autowire();
|
||||||
|
@ -97,11 +119,6 @@ public class OAuth2LoginTests {
|
||||||
static class OAuth2LoginWithMulitpleClientRegistrations {
|
static class OAuth2LoginWithMulitpleClientRegistrations {
|
||||||
@Bean
|
@Bean
|
||||||
InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
|
InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
|
||||||
ClientRegistration google = CommonOAuth2Provider.GOOGLE
|
|
||||||
.getBuilder("google")
|
|
||||||
.clientId("client")
|
|
||||||
.clientSecret("secret")
|
|
||||||
.build();
|
|
||||||
return new InMemoryReactiveClientRegistrationRepository(github, google);
|
return new InMemoryReactiveClientRegistrationRepository(github, google);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,6 +199,107 @@ public class OAuth2LoginTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void oauth2LoginWhenCustomJwtDecoderFactoryThenUsed() {
|
||||||
|
this.spring.register(OAuth2LoginWithMulitpleClientRegistrations.class,
|
||||||
|
OAuth2LoginWithJwtDecoderFactoryBeanConfig.class).autowire();
|
||||||
|
|
||||||
|
WebTestClient webTestClient = WebTestClientBuilder
|
||||||
|
.bindToWebFilters(this.springSecurity)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
OAuth2LoginWithJwtDecoderFactoryBeanConfig config = this.spring.getContext()
|
||||||
|
.getBean(OAuth2LoginWithJwtDecoderFactoryBeanConfig.class);
|
||||||
|
|
||||||
|
OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success();
|
||||||
|
OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid");
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken token = new OAuth2AuthorizationCodeAuthenticationToken(google, exchange, accessToken);
|
||||||
|
|
||||||
|
ServerAuthenticationConverter converter = config.authenticationConverter;
|
||||||
|
when(converter.convert(any())).thenReturn(Mono.just(token));
|
||||||
|
|
||||||
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token");
|
||||||
|
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
||||||
|
.tokenType(accessToken.getTokenType())
|
||||||
|
.scopes(accessToken.getScopes())
|
||||||
|
.additionalParameters(additionalParameters)
|
||||||
|
.build();
|
||||||
|
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> tokenResponseClient = config.tokenResponseClient;
|
||||||
|
when(tokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse));
|
||||||
|
|
||||||
|
OidcUser user = TestOidcUsers.create();
|
||||||
|
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = config.userService;
|
||||||
|
when(userService.loadUser(any())).thenReturn(Mono.just(user));
|
||||||
|
|
||||||
|
webTestClient.get()
|
||||||
|
.uri("/login/oauth2/code/google")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().is3xxRedirection();
|
||||||
|
|
||||||
|
verify(config.jwtDecoderFactory).createDecoder(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class OAuth2LoginWithJwtDecoderFactoryBeanConfig {
|
||||||
|
|
||||||
|
ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class);
|
||||||
|
|
||||||
|
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> tokenResponseClient =
|
||||||
|
mock(ReactiveOAuth2AccessTokenResponseClient.class);
|
||||||
|
|
||||||
|
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
|
||||||
|
|
||||||
|
ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = spy(new JwtDecoderFactory());
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
|
||||||
|
// @formatter:off
|
||||||
|
http
|
||||||
|
.authorizeExchange()
|
||||||
|
.anyExchange().authenticated()
|
||||||
|
.and()
|
||||||
|
.oauth2Login()
|
||||||
|
.authenticationConverter(authenticationConverter)
|
||||||
|
.authenticationManager(authenticationManager());
|
||||||
|
return http.build();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReactiveAuthenticationManager authenticationManager() {
|
||||||
|
OidcAuthorizationCodeReactiveAuthenticationManager oidc =
|
||||||
|
new OidcAuthorizationCodeReactiveAuthenticationManager(tokenResponseClient, userService);
|
||||||
|
oidc.setJwtDecoderFactory(jwtDecoderFactory());
|
||||||
|
return oidc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory() {
|
||||||
|
return jwtDecoderFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class JwtDecoderFactory implements ReactiveJwtDecoderFactory<ClientRegistration> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
||||||
|
return getJwtDecoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReactiveJwtDecoder getJwtDecoder() {
|
||||||
|
return token -> {
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
claims.put(IdTokenClaimNames.SUB, "subject");
|
||||||
|
claims.put(IdTokenClaimNames.ISS, "http://localhost/issuer");
|
||||||
|
claims.put(IdTokenClaimNames.AUD, Collections.singletonList("client"));
|
||||||
|
claims.put(IdTokenClaimNames.AZP, "client");
|
||||||
|
Jwt jwt = new Jwt("id-token", Instant.now(), Instant.now().plusSeconds(3600),
|
||||||
|
Collections.singletonMap("header1", "value1"), claims);
|
||||||
|
return Mono.just(jwt);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static class GitHubWebFilter implements WebFilter {
|
static class GitHubWebFilter implements WebFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,10 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
@ -43,10 +39,15 @@ import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
|
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,7 +81,7 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
||||||
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
|
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
|
||||||
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||||
private final OAuth2UserService<OidcUserRequest, OidcUser> userService;
|
private final OAuth2UserService<OidcUserRequest, OidcUser> userService;
|
||||||
private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new DefaultJwtDecoderFactory();
|
||||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,6 +175,18 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
||||||
return authenticationResult;
|
return authenticationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link JwtDecoderFactory} used for {@link OidcIdToken} signature verification.
|
||||||
|
* The factory returns a {@link JwtDecoder} associated to the provided {@link ClientRegistration}.
|
||||||
|
*
|
||||||
|
* @since 5.2
|
||||||
|
* @param jwtDecoderFactory the {@link JwtDecoderFactory} used for {@link OidcIdToken} signature verification
|
||||||
|
*/
|
||||||
|
public final void setJwtDecoderFactory(JwtDecoderFactory<ClientRegistration> jwtDecoderFactory) {
|
||||||
|
Assert.notNull(jwtDecoderFactory, "jwtDecoderFactory cannot be null");
|
||||||
|
this.jwtDecoderFactory = jwtDecoderFactory;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link GrantedAuthoritiesMapper} used for mapping {@link OidcUser#getAuthorities()}}
|
* Sets the {@link GrantedAuthoritiesMapper} used for mapping {@link OidcUser#getAuthorities()}}
|
||||||
* to a new set of authorities which will be associated to the {@link OAuth2LoginAuthenticationToken}.
|
* to a new set of authorities which will be associated to the {@link OAuth2LoginAuthenticationToken}.
|
||||||
|
@ -191,30 +204,32 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
||||||
}
|
}
|
||||||
|
|
||||||
private OidcIdToken createOidcToken(ClientRegistration clientRegistration, OAuth2AccessTokenResponse accessTokenResponse) {
|
private OidcIdToken createOidcToken(ClientRegistration clientRegistration, OAuth2AccessTokenResponse accessTokenResponse) {
|
||||||
JwtDecoder jwtDecoder = getJwtDecoder(clientRegistration);
|
JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
|
||||||
Jwt jwt = jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(
|
Jwt jwt = jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
|
||||||
OidcParameterNames.ID_TOKEN));
|
|
||||||
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
|
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
|
||||||
OidcTokenValidator.validateIdToken(idToken, clientRegistration);
|
OidcTokenValidator.validateIdToken(idToken, clientRegistration);
|
||||||
return idToken;
|
return idToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JwtDecoder getJwtDecoder(ClientRegistration clientRegistration) {
|
private static class DefaultJwtDecoderFactory implements JwtDecoderFactory<ClientRegistration> {
|
||||||
JwtDecoder jwtDecoder = this.jwtDecoders.get(clientRegistration.getRegistrationId());
|
private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
||||||
if (jwtDecoder == null) {
|
|
||||||
|
@Override
|
||||||
|
public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
||||||
|
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
||||||
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
|
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(
|
OAuth2Error oauth2Error = new OAuth2Error(
|
||||||
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||||
"Failed to find a Signature Verifier for Client Registration: '" +
|
"Failed to find a Signature Verifier for Client Registration: '" +
|
||||||
clientRegistration.getRegistrationId() + "'. Check to ensure you have configured the JwkSet URI.",
|
clientRegistration.getRegistrationId() +
|
||||||
|
"'. Check to ensure you have configured the JwkSet URI.",
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
}
|
}
|
||||||
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
|
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
|
||||||
jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(jwkSetUri).build());
|
return new NimbusJwtDecoder(withJwkSetUri(jwkSetUri).build());
|
||||||
this.jwtDecoders.put(clientRegistration.getRegistrationId(), jwtDecoder);
|
});
|
||||||
}
|
}
|
||||||
return jwtDecoder;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
@ -46,7 +47,6 @@ import reactor.core.publisher.Mono;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of an {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth 2.0 Login,
|
* An implementation of an {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth 2.0 Login,
|
||||||
|
@ -86,7 +86,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
||||||
|
|
||||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
||||||
|
|
||||||
private Function<ClientRegistration, ReactiveJwtDecoder> decoderFactory = new DefaultDecoderFactory();
|
private ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new DefaultJwtDecoderFactory();
|
||||||
|
|
||||||
public OidcAuthorizationCodeReactiveAuthenticationManager(
|
public OidcAuthorizationCodeReactiveAuthenticationManager(
|
||||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
||||||
|
@ -143,13 +143,15 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a way to customize the {@link ReactiveJwtDecoder} given a {@link ClientRegistration}
|
* Sets the {@link ReactiveJwtDecoderFactory} used for {@link OidcIdToken} signature verification.
|
||||||
* @param decoderFactory the {@link Function} used to create {@link ReactiveJwtDecoder} instance. Cannot be null.
|
* The factory returns a {@link ReactiveJwtDecoder} associated to the provided {@link ClientRegistration}.
|
||||||
|
*
|
||||||
|
* @since 5.2
|
||||||
|
* @param jwtDecoderFactory the {@link ReactiveJwtDecoderFactory} used for {@link OidcIdToken} signature verification
|
||||||
*/
|
*/
|
||||||
void setDecoderFactory(
|
public final void setJwtDecoderFactory(ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory) {
|
||||||
Function<ClientRegistration, ReactiveJwtDecoder> decoderFactory) {
|
Assert.notNull(jwtDecoderFactory, "jwtDecoderFactory cannot be null");
|
||||||
Assert.notNull(decoderFactory, "decoderFactory cannot be null");
|
this.jwtDecoderFactory = jwtDecoderFactory;
|
||||||
this.decoderFactory = decoderFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<OAuth2LoginAuthenticationToken> authenticationResult(OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication, OAuth2AccessTokenResponse accessTokenResponse) {
|
private Mono<OAuth2LoginAuthenticationToken> authenticationResult(OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication, OAuth2AccessTokenResponse accessTokenResponse) {
|
||||||
|
@ -183,33 +185,31 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<OidcIdToken> createOidcToken(ClientRegistration clientRegistration, OAuth2AccessTokenResponse accessTokenResponse) {
|
private Mono<OidcIdToken> createOidcToken(ClientRegistration clientRegistration, OAuth2AccessTokenResponse accessTokenResponse) {
|
||||||
ReactiveJwtDecoder jwtDecoder = this.decoderFactory.apply(clientRegistration);
|
ReactiveJwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
|
||||||
String rawIdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
|
String rawIdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
|
||||||
return jwtDecoder.decode(rawIdToken)
|
return jwtDecoder.decode(rawIdToken)
|
||||||
.map(jwt -> new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims()))
|
.map(jwt -> new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims()))
|
||||||
.doOnNext(idToken -> OidcTokenValidator.validateIdToken(idToken, clientRegistration));
|
.doOnNext(idToken -> OidcTokenValidator.validateIdToken(idToken, clientRegistration));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DefaultDecoderFactory implements Function<ClientRegistration, ReactiveJwtDecoder> {
|
private static class DefaultJwtDecoderFactory implements ReactiveJwtDecoderFactory<ClientRegistration> {
|
||||||
private final Map<String, ReactiveJwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
private final Map<String, ReactiveJwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReactiveJwtDecoder apply(ClientRegistration clientRegistration) {
|
public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
||||||
ReactiveJwtDecoder jwtDecoder = this.jwtDecoders.get(clientRegistration.getRegistrationId());
|
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
||||||
if (jwtDecoder == null) {
|
|
||||||
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
|
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(
|
OAuth2Error oauth2Error = new OAuth2Error(
|
||||||
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||||
"Failed to find a Signature Verifier for Client Registration: '" +
|
"Failed to find a Signature Verifier for Client Registration: '" +
|
||||||
clientRegistration.getRegistrationId() + "'. Check to ensure you have configured the JwkSet URI.",
|
clientRegistration.getRegistrationId() +
|
||||||
|
"'. Check to ensure you have configured the JwkSet URI.",
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
}
|
}
|
||||||
jwtDecoder = new NimbusReactiveJwtDecoder(clientRegistration.getProviderDetails().getJwkSetUri());
|
return new NimbusReactiveJwtDecoder(clientRegistration.getProviderDetails().getJwkSetUri());
|
||||||
this.jwtDecoders.put(clientRegistration.getRegistrationId(), jwtDecoder);
|
});
|
||||||
}
|
|
||||||
return jwtDecoder;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,22 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
|
@ -52,13 +42,19 @@ import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
import static org.mockito.ArgumentMatchers.anyCollection;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.springframework.security.oauth2.client.registration.TestClientRegistrations.clientRegistration;
|
import static org.springframework.security.oauth2.client.registration.TestClientRegistrations.clientRegistration;
|
||||||
|
@ -112,6 +108,12 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
||||||
new OidcAuthorizationCodeAuthenticationProvider(this.accessTokenResponseClient, null);
|
new OidcAuthorizationCodeAuthenticationProvider(this.accessTokenResponseClient, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setJwtDecoderFactoryWhenNullThenThrowIllegalArgumentException() {
|
||||||
|
this.exception.expect(IllegalArgumentException.class);
|
||||||
|
this.authenticationProvider.setJwtDecoderFactory(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setAuthoritiesMapperWhenAuthoritiesMapperIsNullThenThrowIllegalArgumentException() {
|
public void setAuthoritiesMapperWhenAuthoritiesMapperIsNullThenThrowIllegalArgumentException() {
|
||||||
this.exception.expect(IllegalArgumentException.class);
|
this.exception.expect(IllegalArgumentException.class);
|
||||||
|
@ -428,8 +430,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
||||||
|
|
||||||
JwtDecoder jwtDecoder = mock(JwtDecoder.class);
|
JwtDecoder jwtDecoder = mock(JwtDecoder.class);
|
||||||
when(jwtDecoder.decode(anyString())).thenReturn(idToken);
|
when(jwtDecoder.decode(anyString())).thenReturn(idToken);
|
||||||
ReflectionTestUtils.setField(this.authenticationProvider,
|
this.authenticationProvider.setJwtDecoderFactory(registration -> jwtDecoder);
|
||||||
"jwtDecoders", Collections.singletonMap("registration-id", jwtDecoder));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private OAuth2AccessTokenResponse accessTokenSuccessResponse() {
|
private OAuth2AccessTokenResponse accessTokenSuccessResponse() {
|
||||||
|
|
|
@ -53,9 +53,7 @@ import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.*;
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ -105,6 +103,12 @@ public class OidcAuthorizationCodeReactiveAuthenticationManagerTests {
|
||||||
.isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setJwtDecoderFactoryWhenNullThenIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> this.manager.setJwtDecoderFactory(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenNoSubscriptionThenDoesNothing() {
|
public void authenticateWhenNoSubscriptionThenDoesNothing() {
|
||||||
// we didn't do anything because it should cause a ClassCastException (as verified below)
|
// we didn't do anything because it should cause a ClassCastException (as verified below)
|
||||||
|
@ -157,7 +161,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManagerTests {
|
||||||
when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse));
|
when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse));
|
||||||
when(this.userService.loadUser(any())).thenReturn(Mono.empty());
|
when(this.userService.loadUser(any())).thenReturn(Mono.empty());
|
||||||
when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken));
|
when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken));
|
||||||
this.manager.setDecoderFactory(c -> this.jwtDecoder);
|
this.manager.setJwtDecoderFactory(c -> this.jwtDecoder);
|
||||||
assertThat(this.manager.authenticate(loginToken()).block()).isNull();
|
assertThat(this.manager.authenticate(loginToken()).block()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +184,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManagerTests {
|
||||||
DefaultOidcUser user = new DefaultOidcUser(AuthorityUtils.createAuthorityList("ROLE_USER"), this.idToken);
|
DefaultOidcUser user = new DefaultOidcUser(AuthorityUtils.createAuthorityList("ROLE_USER"), this.idToken);
|
||||||
when(this.userService.loadUser(any())).thenReturn(Mono.just(user));
|
when(this.userService.loadUser(any())).thenReturn(Mono.just(user));
|
||||||
when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken));
|
when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken));
|
||||||
this.manager.setDecoderFactory(c -> this.jwtDecoder);
|
this.manager.setJwtDecoderFactory(c -> this.jwtDecoder);
|
||||||
|
|
||||||
OAuth2LoginAuthenticationToken result = (OAuth2LoginAuthenticationToken) this.manager.authenticate(loginToken()).block();
|
OAuth2LoginAuthenticationToken result = (OAuth2LoginAuthenticationToken) this.manager.authenticate(loginToken()).block();
|
||||||
|
|
||||||
|
@ -209,7 +213,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManagerTests {
|
||||||
DefaultOidcUser user = new DefaultOidcUser(AuthorityUtils.createAuthorityList("ROLE_USER"), this.idToken);
|
DefaultOidcUser user = new DefaultOidcUser(AuthorityUtils.createAuthorityList("ROLE_USER"), this.idToken);
|
||||||
when(this.userService.loadUser(any())).thenReturn(Mono.just(user));
|
when(this.userService.loadUser(any())).thenReturn(Mono.just(user));
|
||||||
when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken));
|
when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken));
|
||||||
this.manager.setDecoderFactory(c -> this.jwtDecoder);
|
this.manager.setJwtDecoderFactory(c -> this.jwtDecoder);
|
||||||
|
|
||||||
OAuth2LoginAuthenticationToken result = (OAuth2LoginAuthenticationToken) this.manager.authenticate(loginToken()).block();
|
OAuth2LoginAuthenticationToken result = (OAuth2LoginAuthenticationToken) this.manager.authenticate(loginToken()).block();
|
||||||
|
|
||||||
|
@ -245,7 +249,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManagerTests {
|
||||||
ArgumentCaptor<OidcUserRequest> userRequestArgCaptor = ArgumentCaptor.forClass(OidcUserRequest.class);
|
ArgumentCaptor<OidcUserRequest> userRequestArgCaptor = ArgumentCaptor.forClass(OidcUserRequest.class);
|
||||||
when(this.userService.loadUser(userRequestArgCaptor.capture())).thenReturn(Mono.just(user));
|
when(this.userService.loadUser(userRequestArgCaptor.capture())).thenReturn(Mono.just(user));
|
||||||
when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken));
|
when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken));
|
||||||
this.manager.setDecoderFactory(c -> this.jwtDecoder);
|
this.manager.setJwtDecoderFactory(c -> this.jwtDecoder);
|
||||||
|
|
||||||
this.manager.authenticate(loginToken()).block();
|
this.manager.authenticate(loginToken()).block();
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ public class TestOAuth2AuthorizationRequests {
|
||||||
return OAuth2AuthorizationRequest.authorizationCode()
|
return OAuth2AuthorizationRequest.authorizationCode()
|
||||||
.authorizationUri("https://example.com/login/oauth/authorize")
|
.authorizationUri("https://example.com/login/oauth/authorize")
|
||||||
.clientId(clientId)
|
.clientId(clientId)
|
||||||
|
.scope("openid")
|
||||||
.redirectUri("https://example.com/authorize/oauth2/code/registration-id")
|
.redirectUri("https://example.com/authorize/oauth2/code/registration-id")
|
||||||
.state("state")
|
.state("state")
|
||||||
.additionalParameters(additionalParameters);
|
.additionalParameters(additionalParameters);
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.core.oidc.user;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Joe Grandja
|
||||||
|
*/
|
||||||
|
public class TestOidcUsers {
|
||||||
|
|
||||||
|
public static DefaultOidcUser create() {
|
||||||
|
List<GrantedAuthority> roles = AuthorityUtils.createAuthorityList("ROLE_USER");
|
||||||
|
return new DefaultOidcUser(roles, idToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OidcIdToken idToken() {
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
claims.put(IdTokenClaimNames.SUB, "subject");
|
||||||
|
claims.put(IdTokenClaimNames.ISS, "http://localhost/issuer");
|
||||||
|
claims.put(IdTokenClaimNames.AUD, Collections.singletonList("client"));
|
||||||
|
claims.put(IdTokenClaimNames.AZP, "client");
|
||||||
|
return new OidcIdToken("id-token", Instant.now(), Instant.now().plusSeconds(3600), claims);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.jwt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for {@link JwtDecoder}(s).
|
||||||
|
* This factory should be supplied with a type that provides
|
||||||
|
* contextual information used to create a specific {@code JwtDecoder}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.2
|
||||||
|
* @see JwtDecoder
|
||||||
|
*
|
||||||
|
* @param <C> The type that provides contextual information used to create a specific {@code JwtDecoder}.
|
||||||
|
*/
|
||||||
|
public interface JwtDecoderFactory<C> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code JwtDecoder} using the supplied "contextual" type.
|
||||||
|
*
|
||||||
|
* @param context the type that provides contextual information
|
||||||
|
* @return a {@link JwtDecoder}
|
||||||
|
*/
|
||||||
|
JwtDecoder createDecoder(C context);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.jwt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for {@link ReactiveJwtDecoder}(s).
|
||||||
|
* This factory should be supplied with a type that provides
|
||||||
|
* contextual information used to create a specific {@code ReactiveJwtDecoder}.
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.2
|
||||||
|
* @see ReactiveJwtDecoder
|
||||||
|
*
|
||||||
|
* @param <C> The type that provides contextual information used to create a specific {@code ReactiveJwtDecoder}.
|
||||||
|
*/
|
||||||
|
public interface ReactiveJwtDecoderFactory<C> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code ReactiveJwtDecoder} using the supplied "contextual" type.
|
||||||
|
*
|
||||||
|
* @param context the type that provides contextual information
|
||||||
|
* @return a {@link ReactiveJwtDecoder}
|
||||||
|
*/
|
||||||
|
ReactiveJwtDecoder createDecoder(C context);
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue