Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ae7ceef70 | |||
| b3c7f0a79f | |||
| 4df4f9a63d | |||
| 9d25c2d2f8 | |||
| 94b116c8c8 | |||
| 3c6f08ede0 | |||
| 9a94234dae | |||
| 017044bf93 | |||
| ae99097723 | |||
| acb7e63cf7 | |||
| 0256439a50 | |||
| c8f72a1b87 | |||
| 332e8ce07a | |||
| 7aea459535 | |||
| c91389ff8b | |||
| 6de345b972 | |||
| 19f08cbedb | |||
| f82e435aaf | |||
| a44225d334 | |||
| 5decfb1ece | |||
| a24d67375b | |||
| 3a46ba8a85 | |||
| a4851095df | |||
| 633e5b85b4 | |||
| bfa5830e3d | |||
| e43fab518c | |||
| c9f676739b | |||
| b7ce65b284 | |||
| 67d561b5f7 | |||
| 4dd2b1dfe1 | |||
| aa28a0b453 | |||
| 8145cb557b | |||
| ef4109358a | |||
| 6d6fd09665 |
+1
-1
@@ -47,7 +47,7 @@ public class ServiceAuthenticationDetailsSource implements
|
|||||||
// ===================================================================================================
|
// ===================================================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an implementation that uses the specified ServiceProperites and the default
|
* Creates an implementation that uses the specified ServiceProperties and the default
|
||||||
* CAS artifactParameterName.
|
* CAS artifactParameterName.
|
||||||
*
|
*
|
||||||
* @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
|
* @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
|
||||||
|
|||||||
+1
-1
@@ -723,7 +723,7 @@ public final class HttpSecurity extends
|
|||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @return the {@link ServletApiConfigurer} for further customizations
|
* @return the {@link CsrfConfigurer} for further customizations
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
|
public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
|
||||||
|
|||||||
+1
-1
@@ -171,7 +171,7 @@ public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecur
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the {@link AuthenticationSuccessHandler} to be used. The default is
|
* Specifies the {@link AuthenticationSuccessHandler} to be used. The default is
|
||||||
* {@link SavedRequestAwareAuthenticationSuccessHandler} with no additional properites
|
* {@link SavedRequestAwareAuthenticationSuccessHandler} with no additional properties
|
||||||
* set.
|
* set.
|
||||||
*
|
*
|
||||||
* @param successHandler the {@link AuthenticationSuccessHandler}.
|
* @param successHandler the {@link AuthenticationSuccessHandler}.
|
||||||
|
|||||||
+19
-19
@@ -160,6 +160,25 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(H http) throws Exception {
|
public void init(H http) throws Exception {
|
||||||
|
if ( this.jwtConfigurer == null ) {
|
||||||
|
throw new IllegalStateException("Jwt is the only supported format for bearer tokens " +
|
||||||
|
"in Spring Security and no Jwt configuration was found. Make sure to specify " +
|
||||||
|
"a jwk set uri by doing http.oauth2ResourceServer().jwt().jwkSetUri(uri), or wire a " +
|
||||||
|
"JwtDecoder instance by doing http.oauth2ResourceServer().jwt().decoder(decoder), or " +
|
||||||
|
"expose a JwtDecoder instance as a bean and do http.oauth2ResourceServer().jwt().");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
registerDefaultAccessDeniedHandler(http);
|
registerDefaultAccessDeniedHandler(http);
|
||||||
registerDefaultEntryPoint(http);
|
registerDefaultEntryPoint(http);
|
||||||
registerDefaultCsrfOverride(http);
|
registerDefaultCsrfOverride(http);
|
||||||
@@ -179,25 +198,6 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||||||
filter = postProcess(filter);
|
filter = postProcess(filter);
|
||||||
|
|
||||||
http.addFilter(filter);
|
http.addFilter(filter);
|
||||||
|
|
||||||
if ( this.jwtConfigurer == null ) {
|
|
||||||
throw new IllegalStateException("Jwt is the only supported format for bearer tokens " +
|
|
||||||
"in Spring Security and no Jwt configuration was found. Make sure to specify " +
|
|
||||||
"a jwk set uri by doing http.oauth2ResourceServer().jwt().jwkSetUri(uri), or wire a " +
|
|
||||||
"JwtDecoder instance by doing http.oauth2ResourceServer().jwt().decoder(decoder), or " +
|
|
||||||
"expose a JwtDecoder instance as a bean and do http.oauth2ResourceServer().jwt().");
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class JwtConfigurer {
|
public class JwtConfigurer {
|
||||||
|
|||||||
+1
-1
@@ -208,7 +208,7 @@ public class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionP
|
|||||||
pc.registerBeanComponent(new BeanComponentDefinition(
|
pc.registerBeanComponent(new BeanComponentDefinition(
|
||||||
expressionHandler, expressionHandlerRef));
|
expressionHandler, expressionHandlerRef));
|
||||||
logger.info("Expressions were enabled for method security but no SecurityExpressionHandler was configured. "
|
logger.info("Expressions were enabled for method security but no SecurityExpressionHandler was configured. "
|
||||||
+ "All hasPermision() expressions will evaluate to false.");
|
+ "All hasPermission() expressions will evaluate to false.");
|
||||||
}
|
}
|
||||||
|
|
||||||
BeanDefinitionBuilder expressionPreAdviceBldr = BeanDefinitionBuilder
|
BeanDefinitionBuilder expressionPreAdviceBldr = BeanDefinitionBuilder
|
||||||
|
|||||||
+46
-10
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -621,20 +621,56 @@ public class ServerHttpSecurity {
|
|||||||
authenticationFilter.setAuthenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/login?error"));
|
authenticationFilter.setAuthenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/login?error"));
|
||||||
authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
|
authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
|
||||||
|
|
||||||
MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(
|
setDefaultEntryPoints(http);
|
||||||
MediaType.TEXT_HTML);
|
|
||||||
htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
|
|
||||||
Map<String, String> urlToText = http.oauth2Login.getLinks();
|
|
||||||
if (urlToText.size() == 1) {
|
|
||||||
http.defaultEntryPoints.add(new DelegateEntry(htmlMatcher, new RedirectServerAuthenticationEntryPoint(urlToText.keySet().iterator().next())));
|
|
||||||
} else {
|
|
||||||
http.defaultEntryPoints.add(new DelegateEntry(htmlMatcher, new RedirectServerAuthenticationEntryPoint("/login")));
|
|
||||||
}
|
|
||||||
|
|
||||||
http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
|
http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
|
||||||
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
|
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setDefaultEntryPoints(ServerHttpSecurity http) {
|
||||||
|
String defaultLoginPage = "/login";
|
||||||
|
Map<String, String> urlToText = http.oauth2Login.getLinks();
|
||||||
|
String providerLoginPage = null;
|
||||||
|
if (urlToText.size() == 1) {
|
||||||
|
providerLoginPage = urlToText.keySet().iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(
|
||||||
|
MediaType.APPLICATION_XHTML_XML, new MediaType("image", "*"),
|
||||||
|
MediaType.TEXT_HTML, MediaType.TEXT_PLAIN);
|
||||||
|
htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
|
||||||
|
|
||||||
|
ServerWebExchangeMatcher xhrMatcher = exchange -> {
|
||||||
|
if (exchange.getRequest().getHeaders().getOrDefault("X-Requested-With", Collections.emptyList()).contains("XMLHttpRequest")) {
|
||||||
|
return ServerWebExchangeMatcher.MatchResult.match();
|
||||||
|
}
|
||||||
|
return ServerWebExchangeMatcher.MatchResult.notMatch();
|
||||||
|
};
|
||||||
|
ServerWebExchangeMatcher notXhrMatcher = new NegatedServerWebExchangeMatcher(xhrMatcher);
|
||||||
|
|
||||||
|
ServerWebExchangeMatcher defaultEntryPointMatcher = new AndServerWebExchangeMatcher(
|
||||||
|
notXhrMatcher, htmlMatcher);
|
||||||
|
|
||||||
|
if (providerLoginPage != null) {
|
||||||
|
ServerWebExchangeMatcher loginPageMatcher = new PathPatternParserServerWebExchangeMatcher(defaultLoginPage);
|
||||||
|
ServerWebExchangeMatcher faviconMatcher = new PathPatternParserServerWebExchangeMatcher("/favicon.ico");
|
||||||
|
ServerWebExchangeMatcher defaultLoginPageMatcher = new AndServerWebExchangeMatcher(
|
||||||
|
new OrServerWebExchangeMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
|
||||||
|
|
||||||
|
ServerWebExchangeMatcher matcher = new AndServerWebExchangeMatcher(
|
||||||
|
notXhrMatcher, new NegatedServerWebExchangeMatcher(defaultLoginPageMatcher));
|
||||||
|
RedirectServerAuthenticationEntryPoint entryPoint =
|
||||||
|
new RedirectServerAuthenticationEntryPoint(providerLoginPage);
|
||||||
|
entryPoint.setRequestCache(http.requestCache.requestCache);
|
||||||
|
http.defaultEntryPoints.add(new DelegateEntry(matcher, entryPoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
RedirectServerAuthenticationEntryPoint defaultEntryPoint =
|
||||||
|
new RedirectServerAuthenticationEntryPoint(defaultLoginPage);
|
||||||
|
defaultEntryPoint.setRequestCache(http.requestCache.requestCache);
|
||||||
|
http.defaultEntryPoints.add(new DelegateEntry(defaultEntryPointMatcher, defaultEntryPoint));
|
||||||
|
}
|
||||||
|
|
||||||
private ServerWebExchangeMatcher createAttemptAuthenticationRequestMatcher() {
|
private ServerWebExchangeMatcher createAttemptAuthenticationRequestMatcher() {
|
||||||
return new PathPatternParserServerWebExchangeMatcher("/login/oauth2/code/{registrationId}");
|
return new PathPatternParserServerWebExchangeMatcher("/login/oauth2/code/{registrationId}");
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-1
@@ -298,6 +298,18 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||||||
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer"));
|
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-8031
|
||||||
|
@Test
|
||||||
|
public void getWhenAnonymousDisabledThenAllows() throws Exception {
|
||||||
|
this.spring.register(JwtDecoderConfig.class, AnonymousDisabledConfig.class).autowire();
|
||||||
|
JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
|
||||||
|
when(decoder.decode(anyString())).thenReturn(JWT);
|
||||||
|
|
||||||
|
this.mvc.perform(get("/authenticated")
|
||||||
|
.with(bearerToken("token")))
|
||||||
|
.andExpect(status().isNotFound());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getWhenUsingDefaultsWithNoBearerTokenThenUnauthorized()
|
public void getWhenUsingDefaultsWithNoBearerTokenThenUnauthorized()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@@ -652,7 +664,8 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getBearerTokenResolverWhenDuplicateResolverBeansThenWiringException() {
|
public void getBearerTokenResolverWhenDuplicateResolverBeansThenWiringException() {
|
||||||
assertThatCode(() -> this.spring.register(MultipleBearerTokenResolverBeansConfig.class).autowire())
|
assertThatCode(() -> this.spring
|
||||||
|
.register(JwtDecoderConfig.class, MultipleBearerTokenResolverBeansConfig.class).autowire())
|
||||||
.isInstanceOf(BeanCreationException.class)
|
.isInstanceOf(BeanCreationException.class)
|
||||||
.hasRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
|
.hasRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
|
||||||
}
|
}
|
||||||
@@ -1097,6 +1110,22 @@ public class OAuth2ResourceServerConfigurerTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
static class AnonymousDisabledConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
http
|
||||||
|
.authorizeRequests()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.and()
|
||||||
|
.anonymous().disable()
|
||||||
|
.oauth2ResourceServer()
|
||||||
|
.jwt();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
static class MethodSecurityConfig extends WebSecurityConfigurerAdapter {
|
static class MethodSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|||||||
+31
-1
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -26,8 +26,10 @@ import org.junit.Rule;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||||
@@ -61,10 +63,12 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
|
|||||||
import org.springframework.security.web.server.WebFilterChainProxy;
|
import org.springframework.security.web.server.WebFilterChainProxy;
|
||||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
|
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||||
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.web.server.WebHandler;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
@@ -79,6 +83,8 @@ public class OAuth2LoginTests {
|
|||||||
@Rule
|
@Rule
|
||||||
public final SpringTestRule spring = new SpringTestRule();
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
|
|
||||||
|
private WebTestClient client;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private WebFilterChainProxy springSecurity;
|
private WebFilterChainProxy springSecurity;
|
||||||
|
|
||||||
@@ -94,6 +100,14 @@ public class OAuth2LoginTests {
|
|||||||
.clientSecret("secret")
|
.clientSecret("secret")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setApplicationContext(ApplicationContext context) {
|
||||||
|
if (context.getBeanNamesForType(WebHandler.class).length > 0) {
|
||||||
|
this.client = WebTestClient.bindToApplicationContext(context)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void defaultLoginPageWithMultipleClientRegistrationsThenLinks() {
|
public void defaultLoginPageWithMultipleClientRegistrationsThenLinks() {
|
||||||
this.spring.register(OAuth2LoginWithMulitpleClientRegistrations.class).autowire();
|
this.spring.register(OAuth2LoginWithMulitpleClientRegistrations.class).autowire();
|
||||||
@@ -140,6 +154,22 @@ public class OAuth2LoginTests {
|
|||||||
assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize");
|
assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-8118
|
||||||
|
@Test
|
||||||
|
public void defaultLoginPageWithSingleClientRegistrationAndXhrRequestThenDoesNotRedirectForAuthorization() {
|
||||||
|
this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, WebFluxConfig.class).autowire();
|
||||||
|
|
||||||
|
this.client.get()
|
||||||
|
.uri("/")
|
||||||
|
.header("X-Requested-With", "XMLHttpRequest")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().is3xxRedirection()
|
||||||
|
.expectHeader().valueEquals(HttpHeaders.LOCATION, "/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableWebFlux
|
||||||
|
static class WebFluxConfig { }
|
||||||
|
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
static class OAuth2LoginWithSingleClientRegistrations {
|
static class OAuth2LoginWithSingleClientRegistrations {
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
-2
@@ -17,7 +17,6 @@ package org.springframework.security.access.expression;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.security.access.PermissionEvaluator;
|
import org.springframework.security.access.PermissionEvaluator;
|
||||||
@@ -158,7 +157,6 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
|
|||||||
|
|
||||||
private Set<String> getAuthoritySet() {
|
private Set<String> getAuthoritySet() {
|
||||||
if (roles == null) {
|
if (roles == null) {
|
||||||
roles = new HashSet<>();
|
|
||||||
Collection<? extends GrantedAuthority> userAuthorities = authentication
|
Collection<? extends GrantedAuthority> userAuthorities = authentication
|
||||||
.getAuthorities();
|
.getAuthorities();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -129,7 +129,7 @@ import org.springframework.util.Assert;
|
|||||||
* </property>
|
* </property>
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* A configuration note: The JaasAuthenticationProvider uses the security properites
|
* A configuration note: The JaasAuthenticationProvider uses the security properties
|
||||||
* "login.config.url.X" to configure jaas. If you would like to customize the way Jaas
|
* "login.config.url.X" to configure jaas. If you would like to customize the way Jaas
|
||||||
* gets configured, create a subclass of this and override the
|
* gets configured, create a subclass of this and override the
|
||||||
* {@link #configureJaas(Resource)} method.
|
* {@link #configureJaas(Resource)} method.
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ public class Encryptors {
|
|||||||
* not be shared
|
* not be shared
|
||||||
* @param salt a hex-encoded, random, site-global salt value to use to generate the
|
* @param salt a hex-encoded, random, site-global salt value to use to generate the
|
||||||
* key
|
* key
|
||||||
*
|
|
||||||
* @see #standard(CharSequence, CharSequence) which uses the slightly weaker CBC mode
|
|
||||||
* (instead of GCM)
|
|
||||||
*/
|
*/
|
||||||
public static BytesEncryptor stronger(CharSequence password, CharSequence salt) {
|
public static BytesEncryptor stronger(CharSequence password, CharSequence salt) {
|
||||||
return new AesBytesEncryptor(password.toString(), salt,
|
return new AesBytesEncryptor(password.toString(), salt,
|
||||||
@@ -55,11 +52,19 @@ public class Encryptors {
|
|||||||
* provided salt is expected to be hex-encoded; it should be random and at least 8
|
* provided salt is expected to be hex-encoded; it should be random and at least 8
|
||||||
* bytes in length. Also applies a random 16 byte initialization vector to ensure each
|
* bytes in length. Also applies a random 16 byte initialization vector to ensure each
|
||||||
* encrypted message will be unique. Requires Java 6.
|
* encrypted message will be unique. Requires Java 6.
|
||||||
|
* NOTE: This mode is not
|
||||||
|
* <a href="https://en.wikipedia.org/wiki/Authenticated_encryption">authenticated</a>
|
||||||
|
* and does not provide any guarantees about the authenticity of the data.
|
||||||
|
* For a more secure alternative, users should prefer
|
||||||
|
* {@link #stronger(CharSequence, CharSequence)}.
|
||||||
*
|
*
|
||||||
* @param password the password used to generate the encryptor's secret key; should
|
* @param password the password used to generate the encryptor's secret key; should
|
||||||
* not be shared
|
* not be shared
|
||||||
* @param salt a hex-encoded, random, site-global salt value to use to generate the
|
* @param salt a hex-encoded, random, site-global salt value to use to generate the
|
||||||
* key
|
* key
|
||||||
|
*
|
||||||
|
* @see #stronger(CharSequence, CharSequence) which uses the significatly more secure
|
||||||
|
* GCM (instead of CBC)
|
||||||
*/
|
*/
|
||||||
public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
|
public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
|
||||||
return new AesBytesEncryptor(password.toString(), salt,
|
return new AesBytesEncryptor(password.toString(), salt,
|
||||||
|
|||||||
@@ -17,14 +17,16 @@ Encryptors are thread-safe.
|
|||||||
|
|
||||||
[[spring-security-crypto-encryption-bytes]]
|
[[spring-security-crypto-encryption-bytes]]
|
||||||
==== BytesEncryptor
|
==== BytesEncryptor
|
||||||
Use the Encryptors.standard factory method to construct a "standard" BytesEncryptor:
|
Use the `Encryptors.stronger` factory method to construct a BytesEncryptor:
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
Encryptors.standard("password", "salt");
|
Encryptors.stronger("password", "salt");
|
||||||
----
|
----
|
||||||
|
|
||||||
The "standard" encryption method is 256-bit AES using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2).
|
The "stronger" encryption method creates an encryptor using 256 bit AES encryption with
|
||||||
|
Galois Counter Mode (GCM).
|
||||||
|
It derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2).
|
||||||
This method requires Java 6.
|
This method requires Java 6.
|
||||||
The password used to generate the SecretKey should be kept in a secure place and not be shared.
|
The password used to generate the SecretKey should be kept in a secure place and not be shared.
|
||||||
The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised.
|
The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised.
|
||||||
@@ -38,6 +40,11 @@ Such a salt may be generated using a KeyGenerator:
|
|||||||
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
|
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
|
||||||
----
|
----
|
||||||
|
|
||||||
|
Users may also use the `standard` encryption method, which is 256-bit AES in Cipher Block Chaining (CBC) Mode.
|
||||||
|
This mode is not https://en.wikipedia.org/wiki/Authenticated_encryption[authenticated] and does not provide any
|
||||||
|
guarantees about the authenticity of the data.
|
||||||
|
For a more secure alternative, users should prefer `Encryptors.stronger`.
|
||||||
|
|
||||||
[[spring-security-crypto-encryption-text]]
|
[[spring-security-crypto-encryption-text]]
|
||||||
==== TextEncryptor
|
==== TextEncryptor
|
||||||
Use the Encryptors.text factory method to construct a standard TextEncryptor:
|
Use the Encryptors.text factory method to construct a standard TextEncryptor:
|
||||||
|
|||||||
+3
-3
@@ -1,3 +1,3 @@
|
|||||||
gaeVersion=1.9.71
|
gaeVersion=1.9.79
|
||||||
springBootVersion=2.1.12.RELEASE
|
springBootVersion=2.1.13.RELEASE
|
||||||
version=5.1.8.RELEASE
|
version=5.1.9.RELEASE
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
if (!project.hasProperty('reactorVersion')) {
|
if (!project.hasProperty('reactorVersion')) {
|
||||||
ext.reactorVersion = 'Californium-SR15'
|
ext.reactorVersion = 'Californium-SR17'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.hasProperty('springVersion')) {
|
if (!project.hasProperty('springVersion')) {
|
||||||
ext.springVersion = '5.1.13.RELEASE'
|
ext.springVersion = '5.1.14.RELEASE'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.hasProperty('springDataVersion')) {
|
if (!project.hasProperty('springDataVersion')) {
|
||||||
ext.springDataVersion = 'Lovelace-SR15'
|
ext.springDataVersion = 'Lovelace-SR16'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyManagement {
|
dependencyManagement {
|
||||||
@@ -18,16 +18,16 @@ dependencyManagement {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
dependency 'cglib:cglib-nodep:3.2.12'
|
dependency 'cglib:cglib-nodep:3.2.12'
|
||||||
dependency 'com.squareup.okhttp3:mockwebserver:3.12.8'
|
dependency 'com.squareup.okhttp3:mockwebserver:3.12.10'
|
||||||
dependency 'opensymphony:sitemesh:2.4.2'
|
dependency 'opensymphony:sitemesh:2.4.2'
|
||||||
dependency 'org.gebish:geb-spock:0.10.0'
|
dependency 'org.gebish:geb-spock:0.10.0'
|
||||||
dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
|
dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
|
||||||
dependency 'org.powermock:powermock-api-mockito2:2.0.5'
|
dependency 'org.powermock:powermock-api-mockito2:2.0.6'
|
||||||
dependency 'org.powermock:powermock-api-support:2.0.5'
|
dependency 'org.powermock:powermock-api-support:2.0.6'
|
||||||
dependency 'org.powermock:powermock-core:2.0.5'
|
dependency 'org.powermock:powermock-core:2.0.6'
|
||||||
dependency 'org.powermock:powermock-module-junit4-common:2.0.5'
|
dependency 'org.powermock:powermock-module-junit4-common:2.0.6'
|
||||||
dependency 'org.powermock:powermock-module-junit4:2.0.5'
|
dependency 'org.powermock:powermock-module-junit4:2.0.6'
|
||||||
dependency 'org.powermock:powermock-reflect:2.0.5'
|
dependency 'org.powermock:powermock-reflect:2.0.6'
|
||||||
dependency 'org.python:jython:2.5.3'
|
dependency 'org.python:jython:2.5.3'
|
||||||
dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
|
dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
|
||||||
dependency 'org.spockframework:spock-spring:1.0-groovy-2.4'
|
dependency 'org.spockframework:spock-spring:1.0-groovy-2.4'
|
||||||
@@ -56,11 +56,11 @@ dependencyManagement {
|
|||||||
dependency 'com.nimbusds:lang-tag:1.4.3'
|
dependency 'com.nimbusds:lang-tag:1.4.3'
|
||||||
dependency 'com.nimbusds:nimbus-jose-jwt:6.0.2'
|
dependency 'com.nimbusds:nimbus-jose-jwt:6.0.2'
|
||||||
dependency 'com.nimbusds:oauth2-oidc-sdk:6.0'
|
dependency 'com.nimbusds:oauth2-oidc-sdk:6.0'
|
||||||
dependency 'com.squareup.okhttp3:okhttp:3.12.8'
|
dependency 'com.squareup.okhttp3:okhttp:3.12.10'
|
||||||
dependency 'com.squareup.okio:okio:1.13.0'
|
dependency 'com.squareup.okio:okio:1.13.0'
|
||||||
dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1'
|
dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1'
|
||||||
dependency 'com.sun.xml.bind:jaxb-impl:2.3.2'
|
dependency 'com.sun.xml.bind:jaxb-impl:2.3.2'
|
||||||
dependency 'com.unboundid:unboundid-ldapsdk:4.0.11'
|
dependency 'com.unboundid:unboundid-ldapsdk:4.0.14'
|
||||||
dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1'
|
dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1'
|
||||||
dependency 'commons-cli:commons-cli:1.4'
|
dependency 'commons-cli:commons-cli:1.4'
|
||||||
dependency 'commons-codec:commons-codec:1.11'
|
dependency 'commons-codec:commons-codec:1.11'
|
||||||
@@ -127,7 +127,7 @@ dependencyManagement {
|
|||||||
dependency 'org.apache.directory.shared:shared-cursor:0.9.15'
|
dependency 'org.apache.directory.shared:shared-cursor:0.9.15'
|
||||||
dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15'
|
dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15'
|
||||||
dependency 'org.apache.directory.shared:shared-ldap:0.9.15'
|
dependency 'org.apache.directory.shared:shared-ldap:0.9.15'
|
||||||
dependency 'org.apache.httpcomponents:httpclient:4.5.11'
|
dependency 'org.apache.httpcomponents:httpclient:4.5.12'
|
||||||
dependency 'org.apache.httpcomponents:httpcore:4.4.8'
|
dependency 'org.apache.httpcomponents:httpcore:4.4.8'
|
||||||
dependency 'org.apache.httpcomponents:httpmime:4.5.3'
|
dependency 'org.apache.httpcomponents:httpmime:4.5.3'
|
||||||
dependency 'org.apache.mina:mina-core:2.0.0-M6'
|
dependency 'org.apache.mina:mina-core:2.0.0-M6'
|
||||||
@@ -147,21 +147,21 @@ dependencyManagement {
|
|||||||
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
|
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
|
||||||
dependency 'org.bouncycastle:bcpkix-jdk15on:1.64'
|
dependency 'org.bouncycastle:bcpkix-jdk15on:1.64'
|
||||||
dependency 'org.bouncycastle:bcprov-jdk15on:1.58'
|
dependency 'org.bouncycastle:bcprov-jdk15on:1.58'
|
||||||
dependency 'org.codehaus.groovy:groovy-all:2.4.17'
|
dependency 'org.codehaus.groovy:groovy-all:2.4.19'
|
||||||
dependency 'org.codehaus.groovy:groovy-json:2.4.17'
|
dependency 'org.codehaus.groovy:groovy-json:2.4.19'
|
||||||
dependency 'org.codehaus.groovy:groovy:2.4.14'
|
dependency 'org.codehaus.groovy:groovy:2.4.19'
|
||||||
dependency 'org.eclipse.jdt:ecj:3.12.3'
|
dependency 'org.eclipse.jdt:ecj:3.12.3'
|
||||||
dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty:jetty-client:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty:jetty-client:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty:jetty-http:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty:jetty-http:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty:jetty-io:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty:jetty-io:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty:jetty-security:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty:jetty-security:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty:jetty-server:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty:jetty-server:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty:jetty-servlet:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty:jetty-servlet:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty:jetty-util:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty:jetty-util:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty:jetty-xml:9.4.19.v20190610'
|
dependency 'org.eclipse.jetty:jetty-xml:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.persistence:javax.persistence:2.2.1'
|
dependency 'org.eclipse.persistence:javax.persistence:2.2.1'
|
||||||
dependency 'org.gebish:geb-ast:0.10.0'
|
dependency 'org.gebish:geb-ast:0.10.0'
|
||||||
dependency 'org.gebish:geb-core:0.10.0'
|
dependency 'org.gebish:geb-core:0.10.0'
|
||||||
@@ -170,9 +170,9 @@ dependencyManagement {
|
|||||||
dependency 'org.hamcrest:hamcrest-core:1.3'
|
dependency 'org.hamcrest:hamcrest-core:1.3'
|
||||||
dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final'
|
dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final'
|
||||||
dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final'
|
dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final'
|
||||||
dependency 'org.hibernate:hibernate-core:5.2.17.Final'
|
dependency 'org.hibernate:hibernate-core:5.2.18.Final'
|
||||||
dependency 'org.hibernate:hibernate-entitymanager:5.3.15.Final'
|
dependency 'org.hibernate:hibernate-entitymanager:5.3.15.Final'
|
||||||
dependency 'org.hibernate:hibernate-validator:6.0.18.Final'
|
dependency 'org.hibernate:hibernate-validator:6.0.19.Final'
|
||||||
dependency 'org.hsqldb:hsqldb:2.4.1'
|
dependency 'org.hsqldb:hsqldb:2.4.1'
|
||||||
dependency 'org.jasig.cas.client:cas-client-core:3.5.1'
|
dependency 'org.jasig.cas.client:cas-client-core:3.5.1'
|
||||||
dependency 'org.javassist:javassist:3.22.0-CR2'
|
dependency 'org.javassist:javassist:3.22.0-CR2'
|
||||||
@@ -183,7 +183,7 @@ dependencyManagement {
|
|||||||
dependency 'org.objenesis:objenesis:2.6'
|
dependency 'org.objenesis:objenesis:2.6'
|
||||||
dependency 'org.openid4java:openid4java-nodeps:0.9.6'
|
dependency 'org.openid4java:openid4java-nodeps:0.9.6'
|
||||||
dependency 'org.ow2.asm:asm:6.2.1'
|
dependency 'org.ow2.asm:asm:6.2.1'
|
||||||
dependency 'org.reactivestreams:reactive-streams:1.0.1'
|
dependency 'org.reactivestreams:reactive-streams:1.0.3'
|
||||||
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.3'
|
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.33.3'
|
||||||
dependency 'org.seleniumhq.selenium:selenium-api:3.141.59'
|
dependency 'org.seleniumhq.selenium:selenium-api:3.141.59'
|
||||||
dependency 'org.seleniumhq.selenium:selenium-java:3.141.59'
|
dependency 'org.seleniumhq.selenium:selenium-java:3.141.59'
|
||||||
|
|||||||
+3
-2
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -73,7 +73,8 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
|
|||||||
authorizationCodeAuthentication.getClientRegistration(),
|
authorizationCodeAuthentication.getClientRegistration(),
|
||||||
authorizationCodeAuthentication.getAuthorizationExchange(),
|
authorizationCodeAuthentication.getAuthorizationExchange(),
|
||||||
accessTokenResponse.getAccessToken(),
|
accessTokenResponse.getAccessToken(),
|
||||||
accessTokenResponse.getRefreshToken());
|
accessTokenResponse.getRefreshToken(),
|
||||||
|
accessTokenResponse.getAdditionalParameters());
|
||||||
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
|
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
|
||||||
|
|
||||||
return authenticationResult;
|
return authenticationResult;
|
||||||
|
|||||||
+17
-23
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -28,7 +28,6 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
|||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ import java.util.Map;
|
|||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
|
||||||
*/
|
*/
|
||||||
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
|
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
|
||||||
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
private final OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider;
|
||||||
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
|
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
|
||||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
||||||
|
|
||||||
@@ -74,59 +73,54 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
|
|||||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
||||||
OAuth2UserService<OAuth2UserRequest, OAuth2User> userService) {
|
OAuth2UserService<OAuth2UserRequest, OAuth2User> userService) {
|
||||||
|
|
||||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
|
||||||
Assert.notNull(userService, "userService cannot be null");
|
Assert.notNull(userService, "userService cannot be null");
|
||||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
this.authorizationCodeAuthenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(accessTokenResponseClient);
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
OAuth2LoginAuthenticationToken authorizationCodeAuthentication =
|
OAuth2LoginAuthenticationToken loginAuthenticationToken =
|
||||||
(OAuth2LoginAuthenticationToken) authentication;
|
(OAuth2LoginAuthenticationToken) authentication;
|
||||||
|
|
||||||
// Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
// Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
|
||||||
// scope
|
// scope
|
||||||
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
|
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
|
||||||
if (authorizationCodeAuthentication.getAuthorizationExchange()
|
if (loginAuthenticationToken.getAuthorizationExchange()
|
||||||
.getAuthorizationRequest().getScopes().contains("openid")) {
|
.getAuthorizationRequest().getScopes().contains("openid")) {
|
||||||
// This is an OpenID Connect Authentication Request so return null
|
// This is an OpenID Connect Authentication Request so return null
|
||||||
// and let OidcAuthorizationCodeAuthenticationProvider handle it instead
|
// and let OidcAuthorizationCodeAuthenticationProvider handle it instead
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2AccessTokenResponse accessTokenResponse;
|
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken;
|
||||||
try {
|
try {
|
||||||
OAuth2AuthorizationExchangeValidator.validate(
|
authorizationCodeAuthenticationToken = (OAuth2AuthorizationCodeAuthenticationToken) this.authorizationCodeAuthenticationProvider
|
||||||
authorizationCodeAuthentication.getAuthorizationExchange());
|
.authenticate(new OAuth2AuthorizationCodeAuthenticationToken(
|
||||||
|
loginAuthenticationToken.getClientRegistration(),
|
||||||
accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
|
loginAuthenticationToken.getAuthorizationExchange()));
|
||||||
new OAuth2AuthorizationCodeGrantRequest(
|
|
||||||
authorizationCodeAuthentication.getClientRegistration(),
|
|
||||||
authorizationCodeAuthentication.getAuthorizationExchange()));
|
|
||||||
|
|
||||||
} catch (OAuth2AuthorizationException ex) {
|
} catch (OAuth2AuthorizationException ex) {
|
||||||
OAuth2Error oauth2Error = ex.getError();
|
OAuth2Error oauth2Error = ex.getError();
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
|
OAuth2AccessToken accessToken = authorizationCodeAuthenticationToken.getAccessToken();
|
||||||
Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters();
|
Map<String, Object> additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters();
|
||||||
|
|
||||||
OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
|
OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
|
||||||
authorizationCodeAuthentication.getClientRegistration(), accessToken, additionalParameters));
|
loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters));
|
||||||
|
|
||||||
Collection<? extends GrantedAuthority> mappedAuthorities =
|
Collection<? extends GrantedAuthority> mappedAuthorities =
|
||||||
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
|
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
|
||||||
|
|
||||||
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
|
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
|
||||||
authorizationCodeAuthentication.getClientRegistration(),
|
loginAuthenticationToken.getClientRegistration(),
|
||||||
authorizationCodeAuthentication.getAuthorizationExchange(),
|
loginAuthenticationToken.getAuthorizationExchange(),
|
||||||
oauth2User,
|
oauth2User,
|
||||||
mappedAuthorities,
|
mappedAuthorities,
|
||||||
accessToken,
|
accessToken,
|
||||||
accessTokenResponse.getRefreshToken());
|
authorizationCodeAuthenticationToken.getRefreshToken());
|
||||||
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
|
authenticationResult.setDetails(loginAuthenticationToken.getDetails());
|
||||||
|
|
||||||
return authenticationResult;
|
return authenticationResult;
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-15
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -41,6 +41,7 @@ import org.springframework.util.Assert;
|
|||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
import org.springframework.web.util.UriComponents;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
@@ -48,6 +49,11 @@ import javax.servlet.ServletException;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@code Filter} for the OAuth 2.0 Authorization Code Grant,
|
* A {@code Filter} for the OAuth 2.0 Authorization Code Grant,
|
||||||
@@ -132,24 +138,39 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
|
|||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
|
|
||||||
if (this.shouldProcessAuthorizationResponse(request)) {
|
if (matchesAuthorizationResponse(request)) {
|
||||||
this.processAuthorizationResponse(request, response);
|
processAuthorizationResponse(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldProcessAuthorizationResponse(HttpServletRequest request) {
|
private boolean matchesAuthorizationResponse(HttpServletRequest request) {
|
||||||
|
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
|
||||||
|
if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.loadAuthorizationRequest(request);
|
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.loadAuthorizationRequest(request);
|
||||||
if (authorizationRequest == null) {
|
if (authorizationRequest == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String requestUrl = UrlUtils.buildFullRequestUrl(request.getScheme(), request.getServerName(),
|
|
||||||
request.getServerPort(), request.getRequestURI(), null);
|
// Compare redirect_uri
|
||||||
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
|
UriComponents requestUri = UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request)).build();
|
||||||
if (requestUrl.equals(authorizationRequest.getRedirectUri()) &&
|
UriComponents redirectUri = UriComponentsBuilder.fromUriString(authorizationRequest.getRedirectUri()).build();
|
||||||
OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
|
Set<Map.Entry<String, List<String>>> requestUriParameters = new LinkedHashSet<>(requestUri.getQueryParams().entrySet());
|
||||||
|
Set<Map.Entry<String, List<String>>> redirectUriParameters = new LinkedHashSet<>(redirectUri.getQueryParams().entrySet());
|
||||||
|
// Remove the additional request parameters (if any) from the authorization response (request)
|
||||||
|
// before doing an exact comparison with the authorizationRequest.getRedirectUri() parameters (if any)
|
||||||
|
requestUriParameters.retainAll(redirectUriParameters);
|
||||||
|
|
||||||
|
if (Objects.equals(requestUri.getScheme(), redirectUri.getScheme()) &&
|
||||||
|
Objects.equals(requestUri.getUserInfo(), redirectUri.getUserInfo()) &&
|
||||||
|
Objects.equals(requestUri.getHost(), redirectUri.getHost()) &&
|
||||||
|
Objects.equals(requestUri.getPort(), redirectUri.getPort()) &&
|
||||||
|
Objects.equals(requestUri.getPath(), redirectUri.getPath()) &&
|
||||||
|
Objects.equals(requestUriParameters.toString(), redirectUriParameters.toString())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -165,10 +186,7 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
|
|||||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
||||||
|
|
||||||
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
|
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
|
||||||
String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
String redirectUri = UrlUtils.buildFullRequestUrl(request);
|
||||||
.replaceQuery(null)
|
|
||||||
.build()
|
|
||||||
.toUriString();
|
|
||||||
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params, redirectUri);
|
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params, redirectUri);
|
||||||
|
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authenticationRequest = new OAuth2AuthorizationCodeAuthenticationToken(
|
OAuth2AuthorizationCodeAuthenticationToken authenticationRequest = new OAuth2AuthorizationCodeAuthenticationToken(
|
||||||
@@ -183,7 +201,7 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
|
|||||||
} catch (OAuth2AuthorizationException ex) {
|
} catch (OAuth2AuthorizationException ex) {
|
||||||
OAuth2Error error = ex.getError();
|
OAuth2Error error = ex.getError();
|
||||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
UriComponentsBuilder uriBuilder = UriComponentsBuilder
|
||||||
.fromUriString(authorizationResponse.getRedirectUri())
|
.fromUriString(authorizationRequest.getRedirectUri())
|
||||||
.queryParam(OAuth2ParameterNames.ERROR, error.getErrorCode());
|
.queryParam(OAuth2ParameterNames.ERROR, error.getErrorCode());
|
||||||
if (!StringUtils.isEmpty(error.getDescription())) {
|
if (!StringUtils.isEmpty(error.getDescription())) {
|
||||||
uriBuilder.queryParam(OAuth2ParameterNames.ERROR_DESCRIPTION, error.getDescription());
|
uriBuilder.queryParam(OAuth2ParameterNames.ERROR_DESCRIPTION, error.getDescription());
|
||||||
@@ -206,7 +224,7 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, currentAuthentication, request, response);
|
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, currentAuthentication, request, response);
|
||||||
|
|
||||||
String redirectUrl = authorizationResponse.getRedirectUri();
|
String redirectUrl = authorizationRequest.getRedirectUri();
|
||||||
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
|
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
|
||||||
if (savedRequest != null) {
|
if (savedRequest != null) {
|
||||||
redirectUrl = savedRequest.getRedirectUrl();
|
redirectUrl = savedRequest.getRedirectUrl();
|
||||||
|
|||||||
+40
-19
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -37,13 +37,20 @@ import org.springframework.security.web.server.authentication.ServerAuthenticati
|
|||||||
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
|
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
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.web.util.UriComponents;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@code Filter} for the OAuth 2.0 Authorization Code Grant,
|
* A {@code Filter} for the OAuth 2.0 Authorization Code Grant,
|
||||||
* which handles the processing of the OAuth 2.0 Authorization Response.
|
* which handles the processing of the OAuth 2.0 Authorization Response.
|
||||||
@@ -138,10 +145,10 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter {
|
|||||||
@Override
|
@Override
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||||
return this.requiresAuthenticationMatcher.matches(exchange)
|
return this.requiresAuthenticationMatcher.matches(exchange)
|
||||||
.filter( matchResult -> matchResult.isMatch())
|
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
|
||||||
.flatMap( matchResult -> this.authenticationConverter.convert(exchange))
|
.flatMap(matchResult -> this.authenticationConverter.convert(exchange))
|
||||||
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
|
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
|
||||||
.flatMap( token -> authenticate(exchange, chain, token));
|
.flatMap(token -> authenticate(exchange, chain, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<Void> authenticate(ServerWebExchange exchange,
|
private Mono<Void> authenticate(ServerWebExchange exchange,
|
||||||
@@ -171,20 +178,34 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Mono<ServerWebExchangeMatcher.MatchResult> matchesAuthorizationResponse(ServerWebExchange exchange) {
|
private Mono<ServerWebExchangeMatcher.MatchResult> matchesAuthorizationResponse(ServerWebExchange exchange) {
|
||||||
return this.authorizationRequestRepository.loadAuthorizationRequest(exchange)
|
return Mono.just(exchange)
|
||||||
.flatMap(authorizationRequest -> {
|
.filter(exch -> OAuth2AuthorizationResponseUtils.isAuthorizationResponse(exch.getRequest().getQueryParams()))
|
||||||
String requestUrl = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
|
.flatMap(exch -> this.authorizationRequestRepository.loadAuthorizationRequest(exchange)
|
||||||
.query(null)
|
.flatMap(authorizationRequest ->
|
||||||
.build()
|
matchesRedirectUri(exch.getRequest().getURI(), authorizationRequest.getRedirectUri())))
|
||||||
.toUriString();
|
|
||||||
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
|
|
||||||
if (requestUrl.equals(authorizationRequest.getRedirectUri()) &&
|
|
||||||
OAuth2AuthorizationResponseUtils.isAuthorizationResponse(queryParams)) {
|
|
||||||
return ServerWebExchangeMatcher.MatchResult.match();
|
|
||||||
}
|
|
||||||
return ServerWebExchangeMatcher.MatchResult.notMatch();
|
|
||||||
})
|
|
||||||
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
|
|
||||||
.switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
|
.switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Mono<ServerWebExchangeMatcher.MatchResult> matchesRedirectUri(
|
||||||
|
URI authorizationResponseUri, String authorizationRequestRedirectUri) {
|
||||||
|
UriComponents requestUri = UriComponentsBuilder.fromUri(authorizationResponseUri).build();
|
||||||
|
UriComponents redirectUri = UriComponentsBuilder.fromUriString(authorizationRequestRedirectUri).build();
|
||||||
|
Set<Map.Entry<String, List<String>>> requestUriParameters =
|
||||||
|
new LinkedHashSet<>(requestUri.getQueryParams().entrySet());
|
||||||
|
Set<Map.Entry<String, List<String>>> redirectUriParameters =
|
||||||
|
new LinkedHashSet<>(redirectUri.getQueryParams().entrySet());
|
||||||
|
// Remove the additional request parameters (if any) from the authorization response (request)
|
||||||
|
// before doing an exact comparison with the authorizationRequest.getRedirectUri() parameters (if any)
|
||||||
|
requestUriParameters.retainAll(redirectUriParameters);
|
||||||
|
|
||||||
|
if (Objects.equals(requestUri.getScheme(), redirectUri.getScheme()) &&
|
||||||
|
Objects.equals(requestUri.getUserInfo(), redirectUri.getUserInfo()) &&
|
||||||
|
Objects.equals(requestUri.getHost(), redirectUri.getHost()) &&
|
||||||
|
Objects.equals(requestUri.getPort(), redirectUri.getPort()) &&
|
||||||
|
Objects.equals(requestUri.getPath(), redirectUri.getPath()) &&
|
||||||
|
Objects.equals(requestUriParameters.toString(), redirectUriParameters.toString())) {
|
||||||
|
return ServerWebExchangeMatcher.MatchResult.match();
|
||||||
|
}
|
||||||
|
return ServerWebExchangeMatcher.MatchResult.notMatch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-7
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -28,7 +28,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
|
|||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
@@ -103,14 +102,10 @@ public class ServerOAuth2AuthorizationCodeAuthenticationTokenConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static OAuth2AuthorizationResponse convertResponse(ServerWebExchange exchange) {
|
private static OAuth2AuthorizationResponse convertResponse(ServerWebExchange exchange) {
|
||||||
MultiValueMap<String, String> queryParams = exchange.getRequest()
|
|
||||||
.getQueryParams();
|
|
||||||
String redirectUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
|
String redirectUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
|
||||||
.query(null)
|
|
||||||
.build()
|
.build()
|
||||||
.toUriString();
|
.toUriString();
|
||||||
|
|
||||||
return OAuth2AuthorizationResponseUtils
|
return OAuth2AuthorizationResponseUtils
|
||||||
.convert(queryParams, redirectUri);
|
.convert(exchange.getRequest().getQueryParams(), redirectUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-3
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -15,6 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.client.authentication;
|
package org.springframework.security.oauth2.client.authentication;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -33,13 +37,12 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExch
|
|||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
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.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;
|
||||||
|
import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses.accessTokenResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link OAuth2AuthorizationCodeAuthenticationProvider}.
|
* Tests for {@link OAuth2AuthorizationCodeAuthenticationProvider}.
|
||||||
@@ -130,4 +133,26 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
|||||||
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken);
|
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken);
|
||||||
assertThat(authenticationResult.getRefreshToken()).isEqualTo(refreshToken);
|
assertThat(authenticationResult.getRefreshToken()).isEqualTo(refreshToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-5368
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenAuthorizationSuccessResponseThenAdditionalParametersIncluded() {
|
||||||
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
additionalParameters.put("param1", "value1");
|
||||||
|
additionalParameters.put("param2", "value2");
|
||||||
|
|
||||||
|
OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponse().additionalParameters(additionalParameters)
|
||||||
|
.build();
|
||||||
|
when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse);
|
||||||
|
|
||||||
|
OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(this.authorizationRequest,
|
||||||
|
this.authorizationResponse);
|
||||||
|
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authentication = (OAuth2AuthorizationCodeAuthenticationToken) this.authenticationProvider
|
||||||
|
.authenticate(
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(this.clientRegistration, authorizationExchange));
|
||||||
|
|
||||||
|
assertThat(authentication.getAdditionalParameters())
|
||||||
|
.containsAllEntriesOf(accessTokenResponse.getAdditionalParameters());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+191
-127
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -18,10 +18,6 @@ package org.springframework.security.oauth2.client.web;
|
|||||||
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.junit.runner.RunWith;
|
|
||||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||||
@@ -39,36 +35,44 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
|||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||||
import org.springframework.security.web.savedrequest.RequestCache;
|
import org.springframework.security.web.savedrequest.RequestCache;
|
||||||
|
import org.springframework.security.web.util.UrlUtils;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
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.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes;
|
||||||
|
import static org.springframework.security.oauth2.core.TestOAuth2RefreshTokens.refreshToken;
|
||||||
|
import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges.success;
|
||||||
|
import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests.request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link OAuth2AuthorizationCodeGrantFilter}.
|
* Tests for {@link OAuth2AuthorizationCodeGrantFilter}.
|
||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
*/
|
*/
|
||||||
@PowerMockIgnore("javax.security.*")
|
|
||||||
@PrepareForTest({OAuth2AuthorizationRequest.class, OAuth2AuthorizationExchange.class, OAuth2AuthorizationCodeGrantFilter.class})
|
|
||||||
@RunWith(PowerMockRunner.class)
|
|
||||||
public class OAuth2AuthorizationCodeGrantFilterTests {
|
public class OAuth2AuthorizationCodeGrantFilterTests {
|
||||||
private ClientRegistration registration1;
|
private ClientRegistration registration1;
|
||||||
private String principalName1 = "principal-1";
|
private String principalName1 = "principal-1";
|
||||||
@@ -132,8 +136,7 @@ public class OAuth2AuthorizationCodeGrantFilterTests {
|
|||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
request.setServletPath(requestUri);
|
request.setServletPath(requestUri);
|
||||||
// NOTE: A valid Authorization Response contains either a 'code' or 'error' parameter.
|
// NOTE: A valid Authorization Response contains either a 'code' or 'error' parameter.
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
this.filter.doFilter(request, response, filterChain);
|
this.filter.doFilter(request, response, filterChain);
|
||||||
@@ -143,94 +146,142 @@ public class OAuth2AuthorizationCodeGrantFilterTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationRequestNotFoundThenNotProcessed() throws Exception {
|
public void doFilterWhenAuthorizationRequestNotFoundThenNotProcessed() throws Exception {
|
||||||
String requestUri = "/path";
|
MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/path");
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
request.setServletPath(requestUri);
|
|
||||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
|
||||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
|
||||||
|
|
||||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
|
||||||
|
|
||||||
this.filter.doFilter(request, response, filterChain);
|
|
||||||
|
|
||||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void doFilterWhenAuthorizationResponseUrlDoesNotMatchAuthorizationRequestRedirectUriThenNotProcessed() throws Exception {
|
|
||||||
String requestUri = "/callback/client-1";
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
|
||||||
request.setServletPath(requestUri);
|
|
||||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
|
||||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
|
||||||
|
|
||||||
HttpServletResponse response = mock(HttpServletResponse.class);
|
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
|
||||||
|
|
||||||
this.setUpAuthorizationRequest(request, response, this.registration1);
|
|
||||||
request.setRequestURI(requestUri + "-no-match");
|
|
||||||
|
|
||||||
this.filter.doFilter(request, response, filterChain);
|
|
||||||
|
|
||||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void doFilterWhenAuthorizationResponseValidThenAuthorizationRequestRemoved() throws Exception {
|
|
||||||
String requestUri = "/callback/client-1";
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
|
||||||
request.setServletPath(requestUri);
|
|
||||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
|
||||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
this.setUpAuthorizationRequest(request, response, this.registration1);
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
|
||||||
|
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationRequestRedirectUriDoesNotMatchThenNotProcessed() throws Exception {
|
||||||
|
String requestUri = "/callback/client-1";
|
||||||
|
MockHttpServletRequest authorizationRequest = createAuthorizationRequest(requestUri);
|
||||||
|
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
|
||||||
|
authorizationResponse.setRequestURI(requestUri + "-no-match");
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
|
||||||
|
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
// gh-7963
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationRequestRedirectUriParametersMatchThenProcessed() throws Exception {
|
||||||
|
// 1) redirect_uri with query parameters
|
||||||
|
String requestUri = "/callback/client-1";
|
||||||
|
Map<String, String> parameters = new LinkedHashMap<>();
|
||||||
|
parameters.put("param1", "value1");
|
||||||
|
parameters.put("param2", "value2");
|
||||||
|
MockHttpServletRequest authorizationRequest = createAuthorizationRequest(requestUri, parameters);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
|
||||||
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
verifyZeroInteractions(filterChain);
|
||||||
|
|
||||||
|
// 2) redirect_uri with query parameters AND authorization response additional parameters
|
||||||
|
Map<String, String> additionalParameters = new LinkedHashMap<>();
|
||||||
|
additionalParameters.put("auth-param1", "value1");
|
||||||
|
additionalParameters.put("auth-param2", "value2");
|
||||||
|
response = new MockHttpServletResponse();
|
||||||
|
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
|
||||||
|
authorizationResponse = createAuthorizationResponse(authorizationRequest, additionalParameters);
|
||||||
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
verifyZeroInteractions(filterChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// gh-7963
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationRequestRedirectUriParametersDoesNotMatchThenNotProcessed() throws Exception {
|
||||||
|
String requestUri = "/callback/client-1";
|
||||||
|
Map<String, String> parameters = new LinkedHashMap<>();
|
||||||
|
parameters.put("param1", "value1");
|
||||||
|
parameters.put("param2", "value2");
|
||||||
|
MockHttpServletRequest authorizationRequest = createAuthorizationRequest(requestUri, parameters);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
|
||||||
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
|
// 1) Parameter value
|
||||||
|
Map<String, String> parametersNotMatch = new LinkedHashMap<>(parameters);
|
||||||
|
parametersNotMatch.put("param2", "value8");
|
||||||
|
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(
|
||||||
|
createAuthorizationRequest(requestUri, parametersNotMatch));
|
||||||
|
authorizationResponse.setSession(authorizationRequest.getSession());
|
||||||
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
|
||||||
|
// 2) Parameter order
|
||||||
|
parametersNotMatch = new LinkedHashMap<>();
|
||||||
|
parametersNotMatch.put("param2", "value2");
|
||||||
|
parametersNotMatch.put("param1", "value1");
|
||||||
|
authorizationResponse = createAuthorizationResponse(
|
||||||
|
createAuthorizationRequest(requestUri, parametersNotMatch));
|
||||||
|
authorizationResponse.setSession(authorizationRequest.getSession());
|
||||||
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
verify(filterChain, times(2)).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
|
||||||
|
// 3) Parameter missing
|
||||||
|
parametersNotMatch = new LinkedHashMap<>(parameters);
|
||||||
|
parametersNotMatch.remove("param2");
|
||||||
|
authorizationResponse = createAuthorizationResponse(
|
||||||
|
createAuthorizationRequest(requestUri, parametersNotMatch));
|
||||||
|
authorizationResponse.setSession(authorizationRequest.getSession());
|
||||||
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
verify(filterChain, times(3)).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationRequestMatchThenAuthorizationRequestRemoved() throws Exception {
|
||||||
|
MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1");
|
||||||
|
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
|
||||||
this.setUpAuthenticationResult(this.registration1);
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
|
||||||
this.filter.doFilter(request, response, filterChain);
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
|
||||||
assertThat(this.authorizationRequestRepository.loadAuthorizationRequest(request)).isNull();
|
assertThat(this.authorizationRequestRepository.loadAuthorizationRequest(authorizationResponse)).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationFailsThenHandleOAuth2AuthorizationException() throws Exception {
|
public void doFilterWhenAuthorizationFailsThenHandleOAuth2AuthorizationException() throws Exception {
|
||||||
String requestUri = "/callback/client-1";
|
MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1");
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
request.setServletPath(requestUri);
|
|
||||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
|
||||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
|
||||||
|
|
||||||
this.setUpAuthorizationRequest(request, response, this.registration1);
|
|
||||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT);
|
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT);
|
||||||
when(this.authenticationManager.authenticate(any(Authentication.class)))
|
when(this.authenticationManager.authenticate(any(Authentication.class)))
|
||||||
.thenThrow(new OAuth2AuthorizationException(error));
|
.thenThrow(new OAuth2AuthorizationException(error));
|
||||||
|
|
||||||
this.filter.doFilter(request, response, filterChain);
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
|
||||||
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1?error=invalid_grant");
|
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1?error=invalid_grant");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationResponseSuccessThenAuthorizedClientSavedToService() throws Exception {
|
public void doFilterWhenAuthorizationSucceedsThenAuthorizedClientSavedToService() throws Exception {
|
||||||
String requestUri = "/callback/client-1";
|
MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1");
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
request.setServletPath(requestUri);
|
|
||||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
|
||||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
|
||||||
this.setUpAuthorizationRequest(request, response, this.registration1);
|
|
||||||
this.setUpAuthenticationResult(this.registration1);
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
|
||||||
this.filter.doFilter(request, response, filterChain);
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
|
||||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientService.loadAuthorizedClient(
|
OAuth2AuthorizedClient authorizedClient = this.authorizedClientService.loadAuthorizedClient(
|
||||||
this.registration1.getRegistrationId(), this.principalName1);
|
this.registration1.getRegistrationId(), this.principalName1);
|
||||||
@@ -242,40 +293,31 @@ public class OAuth2AuthorizationCodeGrantFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationResponseSuccessThenRedirected() throws Exception {
|
public void doFilterWhenAuthorizationSucceedsThenRedirected() throws Exception {
|
||||||
String requestUri = "/callback/client-1";
|
MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1");
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
request.setServletPath(requestUri);
|
|
||||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
|
||||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
|
||||||
this.setUpAuthorizationRequest(request, response, this.registration1);
|
|
||||||
this.setUpAuthenticationResult(this.registration1);
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
|
||||||
this.filter.doFilter(request, response, filterChain);
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
|
||||||
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1");
|
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationResponseSuccessHasSavedRequestThenRedirectedToSavedRequest() throws Exception {
|
public void doFilterWhenAuthorizationSucceedsAndHasSavedRequestThenRedirectToSavedRequest() throws Exception {
|
||||||
String requestUri = "/saved-request";
|
String requestUri = "/saved-request";
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
request.setServletPath(requestUri);
|
request.setServletPath(requestUri);
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
RequestCache requestCache = new HttpSessionRequestCache();
|
RequestCache requestCache = new HttpSessionRequestCache();
|
||||||
requestCache.saveRequest(request, response);
|
requestCache.saveRequest(request, response);
|
||||||
|
request.setRequestURI("/callback/client-1");
|
||||||
requestUri = "/callback/client-1";
|
|
||||||
request.setRequestURI(requestUri);
|
|
||||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||||
|
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
this.setUpAuthorizationRequest(request, response, this.registration1);
|
this.setUpAuthorizationRequest(request, response, this.registration1);
|
||||||
this.setUpAuthenticationResult(this.registration1);
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
|
||||||
@@ -285,36 +327,30 @@ public class OAuth2AuthorizationCodeGrantFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationResponseSuccessAndAnonymousAccessThenAuthorizedClientSavedToHttpSession() throws Exception {
|
public void doFilterWhenAuthorizationSucceedsAndAnonymousAccessThenAuthorizedClientSavedToHttpSession() throws Exception {
|
||||||
AnonymousAuthenticationToken anonymousPrincipal =
|
AnonymousAuthenticationToken anonymousPrincipal =
|
||||||
new AnonymousAuthenticationToken("key-1234", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
|
new AnonymousAuthenticationToken("key-1234", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
|
||||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||||
securityContext.setAuthentication(anonymousPrincipal);
|
securityContext.setAuthentication(anonymousPrincipal);
|
||||||
SecurityContextHolder.setContext(securityContext);
|
SecurityContextHolder.setContext(securityContext);
|
||||||
|
|
||||||
String requestUri = "/callback/client-1";
|
MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1");
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
request.setServletPath(requestUri);
|
|
||||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
|
||||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
|
||||||
this.setUpAuthorizationRequest(request, response, this.registration1);
|
|
||||||
this.setUpAuthenticationResult(this.registration1);
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
|
||||||
this.filter.doFilter(request, response, filterChain);
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
|
||||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientRepository.loadAuthorizedClient(
|
OAuth2AuthorizedClient authorizedClient = this.authorizedClientRepository.loadAuthorizedClient(
|
||||||
this.registration1.getRegistrationId(), anonymousPrincipal, request);
|
this.registration1.getRegistrationId(), anonymousPrincipal, authorizationResponse);
|
||||||
assertThat(authorizedClient).isNotNull();
|
assertThat(authorizedClient).isNotNull();
|
||||||
|
|
||||||
assertThat(authorizedClient.getClientRegistration()).isEqualTo(this.registration1);
|
assertThat(authorizedClient.getClientRegistration()).isEqualTo(this.registration1);
|
||||||
assertThat(authorizedClient.getPrincipalName()).isEqualTo(anonymousPrincipal.getName());
|
assertThat(authorizedClient.getPrincipalName()).isEqualTo(anonymousPrincipal.getName());
|
||||||
assertThat(authorizedClient.getAccessToken()).isNotNull();
|
assertThat(authorizedClient.getAccessToken()).isNotNull();
|
||||||
|
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = authorizationResponse.getSession(false);
|
||||||
assertThat(session).isNotNull();
|
assertThat(session).isNotNull();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -326,33 +362,27 @@ public class OAuth2AuthorizationCodeGrantFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationResponseSuccessAndAnonymousAccessNullAuthenticationThenAuthorizedClientSavedToHttpSession() throws Exception {
|
public void doFilterWhenAuthorizationSucceedsAndAnonymousAccessNullAuthenticationThenAuthorizedClientSavedToHttpSession() throws Exception {
|
||||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||||
SecurityContextHolder.setContext(securityContext); // null Authentication
|
SecurityContextHolder.setContext(securityContext); // null Authentication
|
||||||
|
|
||||||
String requestUri = "/callback/client-1";
|
MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1");
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
request.setServletPath(requestUri);
|
|
||||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
|
||||||
request.addParameter(OAuth2ParameterNames.STATE, "state");
|
|
||||||
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
|
||||||
this.setUpAuthorizationRequest(request, response, this.registration1);
|
|
||||||
this.setUpAuthenticationResult(this.registration1);
|
this.setUpAuthenticationResult(this.registration1);
|
||||||
|
|
||||||
this.filter.doFilter(request, response, filterChain);
|
this.filter.doFilter(authorizationResponse, response, filterChain);
|
||||||
|
|
||||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientRepository.loadAuthorizedClient(
|
OAuth2AuthorizedClient authorizedClient = this.authorizedClientRepository.loadAuthorizedClient(
|
||||||
this.registration1.getRegistrationId(), null, request);
|
this.registration1.getRegistrationId(), null, authorizationResponse);
|
||||||
assertThat(authorizedClient).isNotNull();
|
assertThat(authorizedClient).isNotNull();
|
||||||
|
|
||||||
assertThat(authorizedClient.getClientRegistration()).isEqualTo(this.registration1);
|
assertThat(authorizedClient.getClientRegistration()).isEqualTo(this.registration1);
|
||||||
assertThat(authorizedClient.getPrincipalName()).isEqualTo("anonymousUser");
|
assertThat(authorizedClient.getPrincipalName()).isEqualTo("anonymousUser");
|
||||||
assertThat(authorizedClient.getAccessToken()).isNotNull();
|
assertThat(authorizedClient.getAccessToken()).isNotNull();
|
||||||
|
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = authorizationResponse.getSession(false);
|
||||||
assertThat(session).isNotNull();
|
assertThat(session).isNotNull();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -363,23 +393,57 @@ public class OAuth2AuthorizationCodeGrantFilterTests {
|
|||||||
assertThat(authorizedClients.values().iterator().next()).isSameAs(authorizedClient);
|
assertThat(authorizedClients.values().iterator().next()).isSameAs(authorizedClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static MockHttpServletRequest createAuthorizationRequest(String requestUri) {
|
||||||
|
return createAuthorizationRequest(requestUri, new LinkedHashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MockHttpServletRequest createAuthorizationRequest(String requestUri, Map<String, String> parameters) {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
request.setServletPath(requestUri);
|
||||||
|
if (!CollectionUtils.isEmpty(parameters)) {
|
||||||
|
parameters.forEach(request::addParameter);
|
||||||
|
request.setQueryString(
|
||||||
|
parameters.entrySet().stream()
|
||||||
|
.map(e -> e.getKey() + "=" + e.getValue())
|
||||||
|
.collect(Collectors.joining("&")));
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MockHttpServletRequest createAuthorizationResponse(MockHttpServletRequest authorizationRequest) {
|
||||||
|
return createAuthorizationResponse(authorizationRequest, new LinkedHashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MockHttpServletRequest createAuthorizationResponse(
|
||||||
|
MockHttpServletRequest authorizationRequest, Map<String, String> additionalParameters) {
|
||||||
|
MockHttpServletRequest authorizationResponse = new MockHttpServletRequest(
|
||||||
|
authorizationRequest.getMethod(), authorizationRequest.getRequestURI());
|
||||||
|
authorizationResponse.setServletPath(authorizationRequest.getRequestURI());
|
||||||
|
authorizationRequest.getParameterMap().forEach(authorizationResponse::addParameter);
|
||||||
|
authorizationResponse.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||||
|
authorizationResponse.addParameter(OAuth2ParameterNames.STATE, "state");
|
||||||
|
additionalParameters.forEach(authorizationResponse::addParameter);
|
||||||
|
authorizationResponse.setQueryString(
|
||||||
|
authorizationResponse.getParameterMap().entrySet().stream()
|
||||||
|
.map(e -> e.getKey() + "=" + e.getValue()[0])
|
||||||
|
.collect(Collectors.joining("&")));
|
||||||
|
authorizationResponse.setSession(authorizationRequest.getSession());
|
||||||
|
return authorizationResponse;
|
||||||
|
}
|
||||||
|
|
||||||
private void setUpAuthorizationRequest(HttpServletRequest request, HttpServletResponse response,
|
private void setUpAuthorizationRequest(HttpServletRequest request, HttpServletResponse response,
|
||||||
ClientRegistration registration) {
|
ClientRegistration registration) {
|
||||||
Map<String, Object> additionalParameters = new HashMap<>();
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, registration.getRegistrationId());
|
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, registration.getRegistrationId());
|
||||||
OAuth2AuthorizationRequest authorizationRequest = mock(OAuth2AuthorizationRequest.class);
|
OAuth2AuthorizationRequest authorizationRequest = request()
|
||||||
when(authorizationRequest.getAdditionalParameters()).thenReturn(additionalParameters);
|
.additionalParameters(additionalParameters)
|
||||||
when(authorizationRequest.getRedirectUri()).thenReturn(request.getRequestURL().toString());
|
.redirectUri(UrlUtils.buildFullRequestUrl(request)).build();
|
||||||
when(authorizationRequest.getState()).thenReturn("state");
|
|
||||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
|
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUpAuthenticationResult(ClientRegistration registration) {
|
private void setUpAuthenticationResult(ClientRegistration registration) {
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authentication = mock(OAuth2AuthorizationCodeAuthenticationToken.class);
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
when(authentication.getClientRegistration()).thenReturn(registration);
|
new OAuth2AuthorizationCodeAuthenticationToken(registration, success(), noScopes(), refreshToken());
|
||||||
when(authentication.getAuthorizationExchange()).thenReturn(mock(OAuth2AuthorizationExchange.class));
|
|
||||||
when(authentication.getAccessToken()).thenReturn(mock(OAuth2AccessToken.class));
|
|
||||||
when(authentication.getRefreshToken()).thenReturn(mock(OAuth2RefreshToken.class));
|
|
||||||
when(this.authenticationManager.authenticate(any(Authentication.class))).thenReturn(authentication);
|
when(this.authenticationManager.authenticate(any(Authentication.class))).thenReturn(authentication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+154
-39
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -25,25 +25,28 @@ import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
|||||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthorizationCodeAuthenticationTokens;
|
import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthorizationCodeAuthenticationTokens;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses;
|
|
||||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
|
||||||
import org.springframework.web.server.handler.DefaultWebFilterChain;
|
import org.springframework.web.server.handler.DefaultWebFilterChain;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests.request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
@@ -101,7 +104,7 @@ public class OAuth2AuthorizationCodeGrantWebFilterTests {
|
|||||||
MockServerWebExchange exchange = MockServerWebExchange
|
MockServerWebExchange exchange = MockServerWebExchange
|
||||||
.from(MockServerHttpRequest.get("/"));
|
.from(MockServerHttpRequest.get("/"));
|
||||||
DefaultWebFilterChain chain = new DefaultWebFilterChain(
|
DefaultWebFilterChain chain = new DefaultWebFilterChain(
|
||||||
e -> e.getResponse().setComplete());
|
e -> e.getResponse().setComplete(), Collections.emptyList());
|
||||||
|
|
||||||
this.filter.filter(exchange, chain).block();
|
this.filter.filter(exchange, chain).block();
|
||||||
|
|
||||||
@@ -110,42 +113,154 @@ public class OAuth2AuthorizationCodeGrantWebFilterTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void filterWhenMatchThenAuthorizedClientSaved() {
|
public void filterWhenMatchThenAuthorizedClientSaved() {
|
||||||
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
|
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||||
.redirectUri("/authorize/registration-id")
|
when(this.clientRegistrationRepository.findByRegistrationId(any()))
|
||||||
.build();
|
.thenReturn(Mono.just(clientRegistration));
|
||||||
OAuth2AuthorizationResponse authorizationResponse = TestOAuth2AuthorizationResponses.success()
|
|
||||||
.redirectUri("/authorize/registration-id")
|
|
||||||
.build();
|
|
||||||
OAuth2AuthorizationExchange authorizationExchange =
|
|
||||||
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse);
|
|
||||||
ClientRegistration registration = TestClientRegistrations.clientRegistration().build();
|
|
||||||
Mono<Authentication> authentication = Mono.just(
|
|
||||||
new OAuth2AuthorizationCodeAuthenticationToken(registration, authorizationExchange));
|
|
||||||
OAuth2AuthorizationCodeAuthenticationToken authenticated = TestOAuth2AuthorizationCodeAuthenticationTokens
|
|
||||||
.authenticated();
|
|
||||||
|
|
||||||
when(this.authenticationManager.authenticate(any())).thenReturn(
|
|
||||||
Mono.just(authenticated));
|
|
||||||
when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any()))
|
when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any()))
|
||||||
.thenReturn(Mono.empty());
|
.thenReturn(Mono.empty());
|
||||||
ServerAuthenticationConverter converter = e -> authentication;
|
when(this.authenticationManager.authenticate(any()))
|
||||||
|
.thenReturn(Mono.just(TestOAuth2AuthorizationCodeAuthenticationTokens.authenticated()));
|
||||||
|
|
||||||
this.filter = new OAuth2AuthorizationCodeGrantWebFilter(
|
MockServerHttpRequest authorizationRequest =
|
||||||
this.authenticationManager, converter, this.authorizedClientRepository);
|
createAuthorizationRequest("/authorization/callback");
|
||||||
|
OAuth2AuthorizationRequest oauth2AuthorizationRequest =
|
||||||
MockServerHttpRequest request = MockServerHttpRequest
|
createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration);
|
||||||
.get("/authorize/registration-id")
|
MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
.queryParam(OAuth2ParameterNames.CODE, "code")
|
MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse);
|
||||||
.queryParam(OAuth2ParameterNames.STATE, "state")
|
this.authorizationRequestRepository.saveAuthorizationRequest(oauth2AuthorizationRequest, exchange).block();
|
||||||
.build();
|
|
||||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
|
||||||
DefaultWebFilterChain chain = new DefaultWebFilterChain(
|
DefaultWebFilterChain chain = new DefaultWebFilterChain(
|
||||||
e -> e.getResponse().setComplete());
|
e -> e.getResponse().setComplete(), Collections.emptyList());
|
||||||
|
|
||||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, exchange).block();
|
|
||||||
|
|
||||||
this.filter.filter(exchange, chain).block();
|
this.filter.filter(exchange, chain).block();
|
||||||
|
|
||||||
verify(this.authorizedClientRepository).saveAuthorizedClient(any(), any(AnonymousAuthenticationToken.class), any());
|
verify(this.authorizedClientRepository).saveAuthorizedClient(any(), any(AnonymousAuthenticationToken.class), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-7966
|
||||||
|
@Test
|
||||||
|
public void filterWhenAuthorizationRequestRedirectUriParametersMatchThenProcessed() {
|
||||||
|
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||||
|
when(this.clientRegistrationRepository.findByRegistrationId(any()))
|
||||||
|
.thenReturn(Mono.just(clientRegistration));
|
||||||
|
when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any()))
|
||||||
|
.thenReturn(Mono.empty());
|
||||||
|
when(this.authenticationManager.authenticate(any()))
|
||||||
|
.thenReturn(Mono.just(TestOAuth2AuthorizationCodeAuthenticationTokens.authenticated()));
|
||||||
|
|
||||||
|
// 1) redirect_uri with query parameters
|
||||||
|
Map<String, String> parameters = new LinkedHashMap<>();
|
||||||
|
parameters.put("param1", "value1");
|
||||||
|
parameters.put("param2", "value2");
|
||||||
|
MockServerHttpRequest authorizationRequest =
|
||||||
|
createAuthorizationRequest("/authorization/callback", parameters);
|
||||||
|
OAuth2AuthorizationRequest oauth2AuthorizationRequest =
|
||||||
|
createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration);
|
||||||
|
MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse);
|
||||||
|
this.authorizationRequestRepository.saveAuthorizationRequest(oauth2AuthorizationRequest, exchange).block();
|
||||||
|
DefaultWebFilterChain chain = new DefaultWebFilterChain(
|
||||||
|
e -> e.getResponse().setComplete(), Collections.emptyList());
|
||||||
|
|
||||||
|
this.filter.filter(exchange, chain).block();
|
||||||
|
verify(this.authenticationManager, times(1)).authenticate(any());
|
||||||
|
|
||||||
|
// 2) redirect_uri with query parameters AND authorization response additional parameters
|
||||||
|
Map<String, String> additionalParameters = new LinkedHashMap<>();
|
||||||
|
additionalParameters.put("auth-param1", "value1");
|
||||||
|
additionalParameters.put("auth-param2", "value2");
|
||||||
|
authorizationResponse = createAuthorizationResponse(authorizationRequest, additionalParameters);
|
||||||
|
exchange = MockServerWebExchange.from(authorizationResponse);
|
||||||
|
this.authorizationRequestRepository.saveAuthorizationRequest(oauth2AuthorizationRequest, exchange).block();
|
||||||
|
|
||||||
|
this.filter.filter(exchange, chain).block();
|
||||||
|
verify(this.authenticationManager, times(2)).authenticate(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
// gh-7966
|
||||||
|
@Test
|
||||||
|
public void filterWhenAuthorizationRequestRedirectUriParametersNotMatchThenNotProcessed() {
|
||||||
|
String requestUri = "/authorization/callback";
|
||||||
|
Map<String, String> parameters = new LinkedHashMap<>();
|
||||||
|
parameters.put("param1", "value1");
|
||||||
|
parameters.put("param2", "value2");
|
||||||
|
MockServerHttpRequest authorizationRequest =
|
||||||
|
createAuthorizationRequest(requestUri, parameters);
|
||||||
|
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||||
|
OAuth2AuthorizationRequest oauth2AuthorizationRequest =
|
||||||
|
createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration);
|
||||||
|
|
||||||
|
// 1) Parameter value
|
||||||
|
Map<String, String> parametersNotMatch = new LinkedHashMap<>(parameters);
|
||||||
|
parametersNotMatch.put("param2", "value8");
|
||||||
|
MockServerHttpRequest authorizationResponse = createAuthorizationResponse(
|
||||||
|
createAuthorizationRequest(requestUri, parametersNotMatch));
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse);
|
||||||
|
this.authorizationRequestRepository.saveAuthorizationRequest(oauth2AuthorizationRequest, exchange).block();
|
||||||
|
DefaultWebFilterChain chain = new DefaultWebFilterChain(
|
||||||
|
e -> e.getResponse().setComplete(), Collections.emptyList());
|
||||||
|
|
||||||
|
this.filter.filter(exchange, chain).block();
|
||||||
|
verifyZeroInteractions(this.authenticationManager);
|
||||||
|
|
||||||
|
// 2) Parameter order
|
||||||
|
parametersNotMatch = new LinkedHashMap<>();
|
||||||
|
parametersNotMatch.put("param2", "value2");
|
||||||
|
parametersNotMatch.put("param1", "value1");
|
||||||
|
authorizationResponse = createAuthorizationResponse(
|
||||||
|
createAuthorizationRequest(requestUri, parametersNotMatch));
|
||||||
|
exchange = MockServerWebExchange.from(authorizationResponse);
|
||||||
|
this.authorizationRequestRepository.saveAuthorizationRequest(oauth2AuthorizationRequest, exchange).block();
|
||||||
|
|
||||||
|
this.filter.filter(exchange, chain).block();
|
||||||
|
verifyZeroInteractions(this.authenticationManager);
|
||||||
|
|
||||||
|
// 3) Parameter missing
|
||||||
|
parametersNotMatch = new LinkedHashMap<>(parameters);
|
||||||
|
parametersNotMatch.remove("param2");
|
||||||
|
authorizationResponse = createAuthorizationResponse(
|
||||||
|
createAuthorizationRequest(requestUri, parametersNotMatch));
|
||||||
|
exchange = MockServerWebExchange.from(authorizationResponse);
|
||||||
|
this.authorizationRequestRepository.saveAuthorizationRequest(oauth2AuthorizationRequest, exchange).block();
|
||||||
|
|
||||||
|
this.filter.filter(exchange, chain).block();
|
||||||
|
verifyZeroInteractions(this.authenticationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(
|
||||||
|
MockServerHttpRequest authorizationRequest, ClientRegistration registration) {
|
||||||
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, registration.getRegistrationId());
|
||||||
|
return request()
|
||||||
|
.additionalParameters(additionalParameters)
|
||||||
|
.redirectUri(authorizationRequest.getURI().toString())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MockServerHttpRequest createAuthorizationRequest(String requestUri) {
|
||||||
|
return createAuthorizationRequest(requestUri, new LinkedHashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MockServerHttpRequest createAuthorizationRequest(String requestUri, Map<String, String> parameters) {
|
||||||
|
MockServerHttpRequest.BaseBuilder<?> builder = MockServerHttpRequest
|
||||||
|
.get(requestUri);
|
||||||
|
if (!CollectionUtils.isEmpty(parameters)) {
|
||||||
|
parameters.forEach(builder::queryParam);
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MockServerHttpRequest createAuthorizationResponse(MockServerHttpRequest authorizationRequest) {
|
||||||
|
return createAuthorizationResponse(authorizationRequest, new LinkedHashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MockServerHttpRequest createAuthorizationResponse(
|
||||||
|
MockServerHttpRequest authorizationRequest, Map<String, String> additionalParameters) {
|
||||||
|
MockServerHttpRequest.BaseBuilder<?> builder = MockServerHttpRequest
|
||||||
|
.get(authorizationRequest.getURI().toString());
|
||||||
|
builder.queryParam(OAuth2ParameterNames.CODE, "code");
|
||||||
|
builder.queryParam(OAuth2ParameterNames.STATE, "state");
|
||||||
|
additionalParameters.forEach(builder::queryParam);
|
||||||
|
builder.cookies(authorizationRequest.getCookies());
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-5
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -57,8 +57,8 @@ import java.util.stream.Stream;
|
|||||||
public class OAuth2AccessTokenResponseHttpMessageConverter extends AbstractHttpMessageConverter<OAuth2AccessTokenResponse> {
|
public class OAuth2AccessTokenResponseHttpMessageConverter extends AbstractHttpMessageConverter<OAuth2AccessTokenResponse> {
|
||||||
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||||
|
|
||||||
private static final ParameterizedTypeReference<Map<String, String>> PARAMETERIZED_RESPONSE_TYPE =
|
private static final ParameterizedTypeReference<Map<String, Object>> PARAMETERIZED_RESPONSE_TYPE =
|
||||||
new ParameterizedTypeReference<Map<String, String>>() {};
|
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||||
|
|
||||||
private GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
|
private GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
|
||||||
|
|
||||||
@@ -82,10 +82,16 @@ public class OAuth2AccessTokenResponseHttpMessageConverter extends AbstractHttpM
|
|||||||
throws IOException, HttpMessageNotReadableException {
|
throws IOException, HttpMessageNotReadableException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// gh-6463
|
||||||
|
// Parse parameter values as Object in order to handle potential JSON Object and then convert values to String
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, String> tokenResponseParameters = (Map<String, String>) this.jsonMessageConverter.read(
|
Map<String, Object> tokenResponseParameters = (Map<String, Object>) this.jsonMessageConverter.read(
|
||||||
PARAMETERIZED_RESPONSE_TYPE.getType(), null, inputMessage);
|
PARAMETERIZED_RESPONSE_TYPE.getType(), null, inputMessage);
|
||||||
return this.tokenResponseConverter.convert(tokenResponseParameters);
|
return this.tokenResponseConverter.convert(
|
||||||
|
tokenResponseParameters.entrySet().stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
Map.Entry::getKey,
|
||||||
|
entry -> entry.getValue().toString())));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new HttpMessageNotReadableException("An error occurred reading the OAuth 2.0 Access Token Response: " +
|
throw new HttpMessageNotReadableException("An error occurred reading the OAuth 2.0 Access Token Response: " +
|
||||||
ex.getMessage(), ex, inputMessage);
|
ex.getMessage(), ex, inputMessage);
|
||||||
|
|||||||
+12
-5
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -35,6 +35,7 @@ import java.nio.charset.Charset;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link HttpMessageConverter} for an {@link OAuth2Error OAuth 2.0 Error}.
|
* A {@link HttpMessageConverter} for an {@link OAuth2Error OAuth 2.0 Error}.
|
||||||
@@ -47,8 +48,8 @@ import java.util.Map;
|
|||||||
public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverter<OAuth2Error> {
|
public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverter<OAuth2Error> {
|
||||||
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||||
|
|
||||||
private static final ParameterizedTypeReference<Map<String, String>> PARAMETERIZED_RESPONSE_TYPE =
|
private static final ParameterizedTypeReference<Map<String, Object>> PARAMETERIZED_RESPONSE_TYPE =
|
||||||
new ParameterizedTypeReference<Map<String, String>>() {};
|
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||||
|
|
||||||
private GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
|
private GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
|
||||||
|
|
||||||
@@ -70,10 +71,16 @@ public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverte
|
|||||||
throws IOException, HttpMessageNotReadableException {
|
throws IOException, HttpMessageNotReadableException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// gh-8157
|
||||||
|
// Parse parameter values as Object in order to handle potential JSON Object and then convert values to String
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, String> errorParameters = (Map<String, String>) this.jsonMessageConverter.read(
|
Map<String, Object> errorParameters = (Map<String, Object>) this.jsonMessageConverter.read(
|
||||||
PARAMETERIZED_RESPONSE_TYPE.getType(), null, inputMessage);
|
PARAMETERIZED_RESPONSE_TYPE.getType(), null, inputMessage);
|
||||||
return this.errorConverter.convert(errorParameters);
|
return this.errorConverter.convert(
|
||||||
|
errorParameters.entrySet().stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
Map.Entry::getKey,
|
||||||
|
entry -> String.valueOf(entry.getValue()))));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new HttpMessageNotReadableException("An error occurred reading the OAuth 2.0 Error: " +
|
throw new HttpMessageNotReadableException("An error occurred reading the OAuth 2.0 Error: " +
|
||||||
ex.getMessage(), ex, inputMessage);
|
ex.getMessage(), ex, inputMessage);
|
||||||
|
|||||||
+34
-1
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -96,6 +96,39 @@ public class OAuth2AccessTokenResponseHttpMessageConverterTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-6463
|
||||||
|
@Test
|
||||||
|
public void readInternalWhenSuccessfulTokenResponseWithObjectThenReadOAuth2AccessTokenResponse() throws Exception {
|
||||||
|
String tokenResponse = "{\n" +
|
||||||
|
" \"access_token\": \"access-token-1234\",\n" +
|
||||||
|
" \"token_type\": \"bearer\",\n" +
|
||||||
|
" \"expires_in\": 3600,\n" +
|
||||||
|
" \"scope\": \"read write\",\n" +
|
||||||
|
" \"refresh_token\": \"refresh-token-1234\",\n" +
|
||||||
|
" \"custom_object_1\": {\"name1\": \"value1\"},\n" +
|
||||||
|
" \"custom_object_2\": [\"value1\", \"value2\"],\n" +
|
||||||
|
" \"custom_parameter_1\": \"custom-value-1\",\n" +
|
||||||
|
" \"custom_parameter_2\": \"custom-value-2\"\n" +
|
||||||
|
"}\n";
|
||||||
|
|
||||||
|
MockClientHttpResponse response = new MockClientHttpResponse(
|
||||||
|
tokenResponse.getBytes(), HttpStatus.OK);
|
||||||
|
|
||||||
|
OAuth2AccessTokenResponse accessTokenResponse = this.messageConverter.readInternal(
|
||||||
|
OAuth2AccessTokenResponse.class, response);
|
||||||
|
|
||||||
|
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234");
|
||||||
|
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER);
|
||||||
|
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBeforeOrEqualTo(Instant.now().plusSeconds(3600));
|
||||||
|
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read", "write");
|
||||||
|
assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234");
|
||||||
|
assertThat(accessTokenResponse.getAdditionalParameters()).containsExactly(
|
||||||
|
entry("custom_object_1", "{name1=value1}"),
|
||||||
|
entry("custom_object_2", "[value1, value2]"),
|
||||||
|
entry("custom_parameter_1", "custom-value-1"),
|
||||||
|
entry("custom_parameter_2", "custom-value-2"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readInternalWhenConversionFailsThenThrowHttpMessageNotReadableException() {
|
public void readInternalWhenConversionFailsThenThrowHttpMessageNotReadableException() {
|
||||||
Converter tokenResponseConverter = mock(Converter.class);
|
Converter tokenResponseConverter = mock(Converter.class);
|
||||||
|
|||||||
+20
-1
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -78,6 +78,25 @@ public class OAuth2ErrorHttpMessageConverterTests {
|
|||||||
assertThat(oauth2Error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6749#section-5.2");
|
assertThat(oauth2Error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6749#section-5.2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-8157
|
||||||
|
@Test
|
||||||
|
public void readInternalWhenErrorResponseWithObjectThenReadOAuth2Error() throws Exception {
|
||||||
|
String errorResponse = "{\n" +
|
||||||
|
" \"error\": \"unauthorized_client\",\n" +
|
||||||
|
" \"error_description\": \"The client is not authorized\",\n" +
|
||||||
|
" \"error_codes\": [65001],\n" +
|
||||||
|
" \"error_uri\": \"https://tools.ietf.org/html/rfc6749#section-5.2\"\n" +
|
||||||
|
"}\n";
|
||||||
|
|
||||||
|
MockClientHttpResponse response = new MockClientHttpResponse(
|
||||||
|
errorResponse.getBytes(), HttpStatus.BAD_REQUEST);
|
||||||
|
|
||||||
|
OAuth2Error oauth2Error = this.messageConverter.readInternal(OAuth2Error.class, response);
|
||||||
|
assertThat(oauth2Error.getErrorCode()).isEqualTo("unauthorized_client");
|
||||||
|
assertThat(oauth2Error.getDescription()).isEqualTo("The client is not authorized");
|
||||||
|
assertThat(oauth2Error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6749#section-5.2");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readInternalWhenConversionFailsThenThrowHttpMessageNotReadableException() {
|
public void readInternalWhenConversionFailsThenThrowHttpMessageNotReadableException() {
|
||||||
Converter errorConverter = mock(Converter.class);
|
Converter errorConverter = mock(Converter.class);
|
||||||
|
|||||||
+1
-1
@@ -66,7 +66,7 @@ class JettyCasService extends Server {
|
|||||||
String password = System.getProperty('javax.net.ssl.trustStorePassword','password')
|
String password = System.getProperty('javax.net.ssl.trustStorePassword','password')
|
||||||
|
|
||||||
|
|
||||||
SslContextFactory sslContextFactory = new SslContextFactory();
|
SslContextFactory sslContextFactory = new SslContextFactory.Server();
|
||||||
sslContextFactory.setKeyStorePath(getTrustStore());
|
sslContextFactory.setKeyStorePath(getTrustStore());
|
||||||
sslContextFactory.setKeyStorePassword(password);
|
sslContextFactory.setKeyStorePassword(password);
|
||||||
sslContextFactory.setKeyManagerPassword(password);
|
sslContextFactory.setKeyManagerPassword(password);
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
releasenotes:
|
||||||
|
sections:
|
||||||
|
- title: "New Features"
|
||||||
|
emoji: ":star:"
|
||||||
|
labels: ["enhancement"]
|
||||||
|
- title: "Bug Fixes"
|
||||||
|
emoji: ":beetle:"
|
||||||
|
labels: ["bug", "regression"]
|
||||||
|
- title: "Dependency Upgrades"
|
||||||
|
emoji: ":hammer:"
|
||||||
|
labels: ["dependency-upgrade"]
|
||||||
|
- title: "Non-passive"
|
||||||
|
emoji: ":rewind:"
|
||||||
|
labels: ["breaks-passivity"]
|
||||||
+1
-1
@@ -43,7 +43,7 @@ final class WithMockUserSecurityContextFactory implements
|
|||||||
.username() : withUser.value();
|
.username() : withUser.value();
|
||||||
if (username == null) {
|
if (username == null) {
|
||||||
throw new IllegalArgumentException(withUser
|
throw new IllegalArgumentException(withUser
|
||||||
+ " cannot have null username on both username and value properites");
|
+ " cannot have null username on both username and value properties");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
|
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
|
||||||
|
|||||||
+1
-1
@@ -563,6 +563,6 @@ public class SwitchUserFilter extends GenericFilterBean
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static RequestMatcher createMatcher(String pattern) {
|
private static RequestMatcher createMatcher(String pattern) {
|
||||||
return new AntPathRequestMatcher(pattern, null, true, new UrlPathHelper());
|
return new AntPathRequestMatcher(pattern, "POST", true, new UrlPathHelper());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-6
@@ -42,7 +42,6 @@ import org.springframework.security.core.AuthenticationException;
|
|||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.authentication.logout.CompositeLogoutHandler;
|
|
||||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
@@ -82,7 +81,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
|
|||||||
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||||
private AuthenticationEntryPoint authenticationEntryPoint;
|
private AuthenticationEntryPoint authenticationEntryPoint;
|
||||||
private AuthenticationManager authenticationManager;
|
private AuthenticationManager authenticationManager;
|
||||||
private LogoutHandler logoutHandler;
|
private List<LogoutHandler> logoutHandlers;
|
||||||
|
|
||||||
HttpServlet3RequestFactory(String rolePrefix) {
|
HttpServlet3RequestFactory(String rolePrefix) {
|
||||||
this.rolePrefix = rolePrefix;
|
this.rolePrefix = rolePrefix;
|
||||||
@@ -146,7 +145,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
|
|||||||
* {@link HttpServletRequest#logout()}.
|
* {@link HttpServletRequest#logout()}.
|
||||||
*/
|
*/
|
||||||
public void setLogoutHandlers(List<LogoutHandler> logoutHandlers) {
|
public void setLogoutHandlers(List<LogoutHandler> logoutHandlers) {
|
||||||
this.logoutHandler = CollectionUtils.isEmpty(logoutHandlers) ? null : new CompositeLogoutHandler(logoutHandlers);
|
this.logoutHandlers = logoutHandlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -246,8 +245,8 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void logout() throws ServletException {
|
public void logout() throws ServletException {
|
||||||
LogoutHandler handler = HttpServlet3RequestFactory.this.logoutHandler;
|
List<LogoutHandler> handlers = HttpServlet3RequestFactory.this.logoutHandlers;
|
||||||
if (handler == null) {
|
if (CollectionUtils.isEmpty(handlers)) {
|
||||||
HttpServlet3RequestFactory.this.logger.debug(
|
HttpServlet3RequestFactory.this.logger.debug(
|
||||||
"logoutHandlers is null, so allowing original HttpServletRequest to handle logout");
|
"logoutHandlers is null, so allowing original HttpServletRequest to handle logout");
|
||||||
super.logout();
|
super.logout();
|
||||||
@@ -255,7 +254,9 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
|
|||||||
}
|
}
|
||||||
Authentication authentication = SecurityContextHolder.getContext()
|
Authentication authentication = SecurityContextHolder.getContext()
|
||||||
.getAuthentication();
|
.getAuthentication();
|
||||||
handler.logout(this, this.response, authentication);
|
for (LogoutHandler handler : handlers) {
|
||||||
|
handler.logout(this, this.response, authentication);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAuthenticated() {
|
private boolean isAuthenticated() {
|
||||||
|
|||||||
+1
-1
@@ -107,7 +107,7 @@ public final class AntPathRequestMatcher
|
|||||||
*
|
*
|
||||||
* @param pattern the ant pattern to use for matching
|
* @param pattern the ant pattern to use for matching
|
||||||
* @param httpMethod the HTTP method. The {@code matches} method will return false if
|
* @param httpMethod the HTTP method. The {@code matches} method will return false if
|
||||||
* the incoming request doesn't doesn't have the same method.
|
* the incoming request doesn't have the same method.
|
||||||
* @param caseSensitive true if the matcher should consider case, else false
|
* @param caseSensitive true if the matcher should consider case, else false
|
||||||
* @param urlPathHelper if non-null, will be used for extracting the path from the HttpServletRequest
|
* @param urlPathHelper if non-null, will be used for extracting the path from the HttpServletRequest
|
||||||
*/
|
*/
|
||||||
|
|||||||
+42
-5
@@ -16,11 +16,17 @@
|
|||||||
|
|
||||||
package org.springframework.security.web.authentication.switchuser;
|
package org.springframework.security.web.authentication.switchuser;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import java.util.ArrayList;
|
||||||
import static org.mockito.Mockito.*;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.security.authentication.AccountExpiredException;
|
import org.springframework.security.authentication.AccountExpiredException;
|
||||||
@@ -42,8 +48,10 @@ import org.springframework.security.web.DefaultRedirectStrategy;
|
|||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import java.util.*;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests
|
* Tests
|
||||||
@@ -75,6 +83,7 @@ public class SwitchUserFilterTests {
|
|||||||
request.setScheme("http");
|
request.setScheme("http");
|
||||||
request.setServerName("localhost");
|
request.setServerName("localhost");
|
||||||
request.setRequestURI("/login/impersonate");
|
request.setRequestURI("/login/impersonate");
|
||||||
|
request.setMethod("POST");
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
@@ -125,6 +134,20 @@ public class SwitchUserFilterTests {
|
|||||||
assertThat(filter.requiresExitUser(request)).isFalse();
|
assertThat(filter.requiresExitUser(request)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// gh-4183
|
||||||
|
public void requiresExitUserWhenGetThenDoesNotMatch() {
|
||||||
|
SwitchUserFilter filter = new SwitchUserFilter();
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setScheme("http");
|
||||||
|
request.setServerName("localhost");
|
||||||
|
request.setRequestURI("/login/impersonate");
|
||||||
|
request.setMethod("GET");
|
||||||
|
|
||||||
|
assertThat(filter.requiresExitUser(request)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void requiresExitUserWhenMatcherThenWorks() {
|
public void requiresExitUserWhenMatcherThenWorks() {
|
||||||
SwitchUserFilter filter = new SwitchUserFilter();
|
SwitchUserFilter filter = new SwitchUserFilter();
|
||||||
@@ -159,6 +182,20 @@ public class SwitchUserFilterTests {
|
|||||||
assertThat(filter.requiresSwitchUser(request)).isFalse();
|
assertThat(filter.requiresSwitchUser(request)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// gh-4183
|
||||||
|
public void requiresSwitchUserWhenGetThenDoesNotMatch() {
|
||||||
|
SwitchUserFilter filter = new SwitchUserFilter();
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setScheme("http");
|
||||||
|
request.setServerName("localhost");
|
||||||
|
request.setRequestURI("/login/impersonate");
|
||||||
|
request.setMethod("GET");
|
||||||
|
|
||||||
|
assertThat(filter.requiresSwitchUser(request)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void requiresSwitchUserWhenMatcherThenWorks() {
|
public void requiresSwitchUserWhenMatcherThenWorks() {
|
||||||
SwitchUserFilter filter = new SwitchUserFilter();
|
SwitchUserFilter filter = new SwitchUserFilter();
|
||||||
|
|||||||
Reference in New Issue
Block a user