Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8407ba4f4d | |||
| 7bdd1b4d83 | |||
| 1a2e9390ca | |||
| 8a3f5d5ffe | |||
| 010817e9e1 | |||
| 3dcd022f93 | |||
| 7f5b24bed0 | |||
| 670c99e7ea | |||
| 641de854d7 | |||
| 1f386b1db3 | |||
| 4f0172d0f0 | |||
| ca272e4267 | |||
| d956ebf59b | |||
| acfe4bdcfb | |||
| 48a0514965 | |||
| e937366f50 | |||
| 0c85dd9cd1 | |||
| 7e7f85c18c | |||
| 157498bf44 | |||
| ce4345b0ed | |||
| 7859970ff6 | |||
| fa09295fd6 | |||
| 9a8d324d72 | |||
| 6f4d05193e | |||
| e146a7c16b | |||
| a372ec9ef5 | |||
| 0b0abfb911 |
@@ -0,0 +1,22 @@
|
|||||||
|
name: PR Build
|
||||||
|
|
||||||
|
on: pull_request
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '8'
|
||||||
|
- name: Cache Gradle packages
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.gradle/caches
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: ./gradlew clean build --continue
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
language: java
|
|
||||||
|
|
||||||
jdk:
|
|
||||||
- openjdk8
|
|
||||||
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
|
|
||||||
before_cache:
|
|
||||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.gradle/caches/
|
|
||||||
- $HOME/.gradle/wrapper/
|
|
||||||
|
|
||||||
script: ./gradlew build --refresh-dependencies --no-daemon --continue
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge]
|
image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge]
|
||||||
|
|
||||||
image:https://travis-ci.org/spring-projects/spring-security.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-security"]
|
|
||||||
|
|
||||||
= Spring Security
|
= Spring Security
|
||||||
|
|
||||||
Spring Security provides security services for the https://docs.spring.io[Spring IO Platform]. Spring Security 5.0 requires Spring 5.0 as
|
Spring Security provides security services for the https://docs.spring.io[Spring IO Platform]. Spring Security 5.0 requires Spring 5.0 as
|
||||||
|
|||||||
+8
-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.
|
||||||
@@ -31,6 +31,7 @@ import org.springframework.context.ApplicationContextAware;
|
|||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.access.PermissionEvaluator;
|
import org.springframework.security.access.PermissionEvaluator;
|
||||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||||
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
|
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
|
||||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||||
import org.springframework.security.config.annotation.SecurityBuilder;
|
import org.springframework.security.config.annotation.SecurityBuilder;
|
||||||
@@ -74,6 +75,7 @@ import org.springframework.web.filter.DelegatingFilterProxy;
|
|||||||
* @see WebSecurityConfiguration
|
* @see WebSecurityConfiguration
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
|
* @author Evgeniy Cheban
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
public final class WebSecurity extends
|
public final class WebSecurity extends
|
||||||
@@ -385,6 +387,11 @@ public final class WebSecurity extends
|
|||||||
throws BeansException {
|
throws BeansException {
|
||||||
this.defaultWebSecurityExpressionHandler
|
this.defaultWebSecurityExpressionHandler
|
||||||
.setApplicationContext(applicationContext);
|
.setApplicationContext(applicationContext);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.defaultWebSecurityExpressionHandler.setRoleHierarchy(applicationContext.getBean(RoleHierarchy.class));
|
||||||
|
} catch (NoSuchBeanDefinitionException e) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean(
|
this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean(
|
||||||
PermissionEvaluator.class));
|
PermissionEvaluator.class));
|
||||||
|
|||||||
+11
@@ -332,6 +332,13 @@ public abstract class WebSecurityConfigurerAdapter implements
|
|||||||
/**
|
/**
|
||||||
* Override this method to configure {@link WebSecurity}. For example, if you wish to
|
* Override this method to configure {@link WebSecurity}. For example, if you wish to
|
||||||
* ignore certain requests.
|
* ignore certain requests.
|
||||||
|
*
|
||||||
|
* Endpoints specified in this method will be ignored by Spring Security, meaning it
|
||||||
|
* will not protect them from CSRF, XSS, Clickjacking, and so on.
|
||||||
|
*
|
||||||
|
* Instead, if you want to protect endpoints against common vulnerabilities, then see
|
||||||
|
* {@link #configure(HttpSecurity)} and the {@link HttpSecurity#authorizeRequests}
|
||||||
|
* configuration method.
|
||||||
*/
|
*/
|
||||||
public void configure(WebSecurity web) throws Exception {
|
public void configure(WebSecurity web) throws Exception {
|
||||||
}
|
}
|
||||||
@@ -345,6 +352,10 @@ public abstract class WebSecurityConfigurerAdapter implements
|
|||||||
* http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
|
* http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
|
* Any endpoint that requires defense against common vulnerabilities can be specified here, including public ones.
|
||||||
|
* See {@link HttpSecurity#authorizeRequests} and the `permitAll()` authorization rule
|
||||||
|
* for more details on public endpoints.
|
||||||
|
*
|
||||||
* @param http the {@link HttpSecurity} to modify
|
* @param http the {@link HttpSecurity} to modify
|
||||||
* @throws Exception if an error occurs
|
* @throws Exception if an error occurs
|
||||||
*/
|
*/
|
||||||
|
|||||||
+8
-1
@@ -34,6 +34,8 @@ import java.util.Map;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.util.context.Context;
|
import reactor.util.context.Context;
|
||||||
|
|
||||||
@@ -578,7 +580,12 @@ public class ServerHttpSecurity {
|
|||||||
|
|
||||||
private ServerAuthenticationConverter getAuthenticationConverter(ReactiveClientRegistrationRepository clientRegistrationRepository) {
|
private ServerAuthenticationConverter getAuthenticationConverter(ReactiveClientRegistrationRepository clientRegistrationRepository) {
|
||||||
if (this.authenticationConverter == null) {
|
if (this.authenticationConverter == null) {
|
||||||
this.authenticationConverter = new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository);
|
ServerOAuth2AuthorizationCodeAuthenticationTokenConverter delegate =
|
||||||
|
new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository);
|
||||||
|
ServerAuthenticationConverter authenticationConverter = exchange ->
|
||||||
|
delegate.convert(exchange).onErrorMap(OAuth2AuthorizationException.class,
|
||||||
|
e -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()));
|
||||||
|
this.authenticationConverter = authenticationConverter;
|
||||||
}
|
}
|
||||||
return this.authenticationConverter;
|
return this.authenticationConverter;
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-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.
|
||||||
@@ -32,6 +32,8 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
|||||||
import org.springframework.security.access.PermissionEvaluator;
|
import org.springframework.security.access.PermissionEvaluator;
|
||||||
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
|
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
|
||||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||||
|
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
@@ -68,6 +70,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
|
* @author Evgeniy Cheban
|
||||||
*/
|
*/
|
||||||
public class WebSecurityConfigurationTests {
|
public class WebSecurityConfigurationTests {
|
||||||
@Rule
|
@Rule
|
||||||
@@ -270,6 +273,31 @@ public class WebSecurityConfigurationTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void securityExpressionHandlerWhenRoleHierarchyBeanThenRoleHierarchyUsed() {
|
||||||
|
this.spring.register(WebSecurityExpressionHandlerRoleHierarchyBeanConfig.class).autowire();
|
||||||
|
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "notused", "ROLE_ADMIN");
|
||||||
|
FilterInvocation invocation = new FilterInvocation(new MockHttpServletRequest("GET", ""),
|
||||||
|
new MockHttpServletResponse(), new MockFilterChain());
|
||||||
|
|
||||||
|
AbstractSecurityExpressionHandler handler = this.spring.getContext().getBean(AbstractSecurityExpressionHandler.class);
|
||||||
|
EvaluationContext evaluationContext = handler.createEvaluationContext(authentication, invocation);
|
||||||
|
Expression expression = handler.getExpressionParser()
|
||||||
|
.parseExpression("hasRole('ROLE_USER')");
|
||||||
|
boolean granted = expression.getValue(evaluationContext, Boolean.class);
|
||||||
|
assertThat(granted).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
static class WebSecurityExpressionHandlerRoleHierarchyBeanConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
@Bean
|
||||||
|
RoleHierarchy roleHierarchy() {
|
||||||
|
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
|
||||||
|
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
|
||||||
|
return roleHierarchy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEvaluatorUsed() throws Exception {
|
public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEvaluatorUsed() throws Exception {
|
||||||
this.spring.register(WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig.class).autowire();
|
this.spring.register(WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig.class).autowire();
|
||||||
|
|||||||
+24
-7
@@ -16,12 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.security.config.web.server;
|
package org.springframework.security.config.web.server;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
@@ -67,13 +61,18 @@ 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 org.springframework.web.server.WebHandler;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
@@ -301,6 +300,24 @@ public class OAuth2LoginTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-8609
|
||||||
|
@Test
|
||||||
|
public void oauth2LoginWhenAuthenticationConverterFailsThenDefaultRedirectToLogin() {
|
||||||
|
this.spring.register(OAuth2LoginWithMulitpleClientRegistrations.class).autowire();
|
||||||
|
|
||||||
|
WebTestClient webTestClient = WebTestClientBuilder
|
||||||
|
.bindToWebFilters(this.springSecurity)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
webTestClient.get()
|
||||||
|
.uri("/login/oauth2/code/google")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus()
|
||||||
|
.is3xxRedirection()
|
||||||
|
.expectHeader()
|
||||||
|
.valueEquals("Location", "/login?error");
|
||||||
|
}
|
||||||
|
|
||||||
static class GitHubWebFilter implements WebFilter {
|
static class GitHubWebFilter implements WebFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
|||||||
((CredentialsContainer) result).eraseCredentials();
|
((CredentialsContainer) result).eraseCredentials();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
|
// If the parent AuthenticationManager was attempted and successful then it will publish an AuthenticationSuccessEvent
|
||||||
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
|
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
|
||||||
if (parentResult == null) {
|
if (parentResult == null) {
|
||||||
eventPublisher.publishAuthenticationSuccess(result);
|
eventPublisher.publishAuthenticationSuccess(result);
|
||||||
@@ -235,7 +235,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
|||||||
"No AuthenticationProvider found for {0}"));
|
"No AuthenticationProvider found for {0}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
|
// If the parent AuthenticationManager was attempted and failed then it will publish an AbstractAuthenticationFailureEvent
|
||||||
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
|
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
|
||||||
if (parentException == null) {
|
if (parentException == null) {
|
||||||
prepareException(lastException, authentication);
|
prepareException(lastException, authentication);
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ asciidoctor {
|
|||||||
'gh-samples-url': "$ghUrl/samples"
|
'gh-samples-url': "$ghUrl/samples"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remotes {
|
||||||
|
docs {
|
||||||
|
retryCount = 5 // retry 5 times (default is 0)
|
||||||
|
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
docsZip {
|
docsZip {
|
||||||
from(project(':spring-security-docs-guides').asciidoctor) {
|
from(project(':spring-security-docs-guides').asciidoctor) {
|
||||||
into 'guides'
|
into 'guides'
|
||||||
|
|||||||
+3
-3
@@ -1,3 +1,3 @@
|
|||||||
gaeVersion=1.9.80
|
gaeVersion=1.9.81
|
||||||
springBootVersion=2.1.14.RELEASE
|
springBootVersion=2.1.16.RELEASE
|
||||||
version=5.1.11.RELEASE
|
version=5.1.12.RELEASE
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
if (!project.hasProperty('reactorVersion')) {
|
if (!project.hasProperty('reactorVersion')) {
|
||||||
ext.reactorVersion = 'Californium-SR18'
|
ext.reactorVersion = 'Californium-SR20'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.hasProperty('springVersion')) {
|
if (!project.hasProperty('springVersion')) {
|
||||||
ext.springVersion = '5.1.15.RELEASE'
|
ext.springVersion = '5.1.17.RELEASE'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.hasProperty('springDataVersion')) {
|
if (!project.hasProperty('springDataVersion')) {
|
||||||
ext.springDataVersion = 'Lovelace-SR17'
|
ext.springDataVersion = 'Lovelace-SR19'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyManagement {
|
dependencyManagement {
|
||||||
@@ -42,14 +42,14 @@ dependencyManagement {
|
|||||||
dependency 'ch.qos.logback:logback-core:1.2.3'
|
dependency 'ch.qos.logback:logback-core:1.2.3'
|
||||||
dependency 'com.fasterxml.jackson.core:jackson-annotations:2.9.10'
|
dependency 'com.fasterxml.jackson.core:jackson-annotations:2.9.10'
|
||||||
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.10'
|
dependency 'com.fasterxml.jackson.core:jackson-core:2.9.10'
|
||||||
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.10.2'
|
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.10.5'
|
||||||
dependency 'com.fasterxml:classmate:1.3.4'
|
dependency 'com.fasterxml:classmate:1.3.4'
|
||||||
dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1'
|
dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1'
|
||||||
dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.80'
|
dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.81'
|
||||||
dependency 'com.google.appengine:appengine-api-labs:1.9.80'
|
dependency 'com.google.appengine:appengine-api-labs:1.9.81'
|
||||||
dependency 'com.google.appengine:appengine-api-stubs:1.9.80'
|
dependency 'com.google.appengine:appengine-api-stubs:1.9.81'
|
||||||
dependency 'com.google.appengine:appengine-testing:1.9.80'
|
dependency 'com.google.appengine:appengine-testing:1.9.81'
|
||||||
dependency 'com.google.appengine:appengine:1.9.80'
|
dependency 'com.google.appengine:appengine:1.9.81'
|
||||||
dependency 'com.google.code.gson:gson:2.8.2'
|
dependency 'com.google.code.gson:gson:2.8.2'
|
||||||
dependency 'com.google.guava:guava:20.0'
|
dependency 'com.google.guava:guava:20.0'
|
||||||
dependency 'com.google.inject:guice:3.0'
|
dependency 'com.google.inject:guice:3.0'
|
||||||
@@ -147,9 +147,9 @@ 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.19'
|
dependency 'org.codehaus.groovy:groovy-all:2.4.20'
|
||||||
dependency 'org.codehaus.groovy:groovy-json:2.4.19'
|
dependency 'org.codehaus.groovy:groovy-json:2.4.20'
|
||||||
dependency 'org.codehaus.groovy:groovy:2.4.19'
|
dependency 'org.codehaus.groovy:groovy:2.4.20'
|
||||||
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.27.v20200227'
|
dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.27.v20200227'
|
||||||
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.27.v20200227'
|
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.27.v20200227'
|
||||||
@@ -171,8 +171,8 @@ dependencyManagement {
|
|||||||
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.18.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.17.Final'
|
||||||
dependency 'org.hibernate:hibernate-validator:6.0.19.Final'
|
dependency 'org.hibernate:hibernate-validator:6.0.20.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'
|
||||||
@@ -195,7 +195,7 @@ dependencyManagement {
|
|||||||
dependency 'org.slf4j:slf4j-api:1.7.30'
|
dependency 'org.slf4j:slf4j-api:1.7.30'
|
||||||
dependency 'org.slf4j:slf4j-nop:1.7.30'
|
dependency 'org.slf4j:slf4j-nop:1.7.30'
|
||||||
dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111'
|
dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111'
|
||||||
dependency 'org.springframework.ldap:spring-ldap-core:2.3.2.RELEASE'
|
dependency 'org.springframework.ldap:spring-ldap-core:2.3.3.RELEASE'
|
||||||
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE'
|
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE'
|
||||||
dependency 'org.unbescape:unbescape:1.1.5.RELEASE'
|
dependency 'org.unbescape:unbescape:1.1.5.RELEASE'
|
||||||
dependency 'org.w3c.css:sac:1.3'
|
dependency 'org.w3c.css:sac:1.3'
|
||||||
|
|||||||
+17
-2
@@ -20,7 +20,11 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,6 +44,7 @@ import org.springframework.util.Assert;
|
|||||||
* @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 OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
|
public class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
||||||
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,8 +64,18 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
|
|||||||
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
|
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
|
||||||
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
||||||
|
|
||||||
OAuth2AuthorizationExchangeValidator.validate(
|
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication
|
||||||
authorizationCodeAuthentication.getAuthorizationExchange());
|
.getAuthorizationExchange().getAuthorizationResponse();
|
||||||
|
if (authorizationResponse.statusError()) {
|
||||||
|
throw new OAuth2AuthorizationException(authorizationResponse.getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication
|
||||||
|
.getAuthorizationExchange().getAuthorizationRequest();
|
||||||
|
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
||||||
|
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
||||||
|
throw new OAuth2AuthorizationException(oauth2Error);
|
||||||
|
}
|
||||||
|
|
||||||
OAuth2AccessTokenResponse accessTokenResponse =
|
OAuth2AccessTokenResponse accessTokenResponse =
|
||||||
this.accessTokenResponseClient.getTokenResponse(
|
this.accessTokenResponseClient.getTokenResponse(
|
||||||
|
|||||||
+17
-4
@@ -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.
|
||||||
@@ -22,9 +22,13 @@ import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessT
|
|||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
|
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||||
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;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
@@ -55,8 +59,8 @@ import java.util.function.Function;
|
|||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
|
||||||
* @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 OAuth2AuthorizationCodeReactiveAuthenticationManager implements
|
public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements ReactiveAuthenticationManager {
|
||||||
ReactiveAuthenticationManager {
|
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
||||||
private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||||
|
|
||||||
public OAuth2AuthorizationCodeReactiveAuthenticationManager(
|
public OAuth2AuthorizationCodeReactiveAuthenticationManager(
|
||||||
@@ -70,7 +74,16 @@ public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements
|
|||||||
return Mono.defer(() -> {
|
return Mono.defer(() -> {
|
||||||
OAuth2AuthorizationCodeAuthenticationToken token = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
OAuth2AuthorizationCodeAuthenticationToken token = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
|
||||||
|
|
||||||
OAuth2AuthorizationExchangeValidator.validate(token.getAuthorizationExchange());
|
OAuth2AuthorizationResponse authorizationResponse = token.getAuthorizationExchange().getAuthorizationResponse();
|
||||||
|
if (authorizationResponse.statusError()) {
|
||||||
|
return Mono.error(new OAuth2AuthorizationException(authorizationResponse.getError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = token.getAuthorizationExchange().getAuthorizationRequest();
|
||||||
|
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
||||||
|
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
||||||
|
return Mono.error(new OAuth2AuthorizationException(oauth2Error));
|
||||||
|
}
|
||||||
|
|
||||||
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
|
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
|
||||||
token.getClientRegistration(),
|
token.getClientRegistration(),
|
||||||
|
|||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2002-2019 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.springframework.security.oauth2.client.authentication;
|
|
||||||
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A validator for an "exchange" of an OAuth 2.0 Authorization Request and Response.
|
|
||||||
*
|
|
||||||
* @author Joe Grandja
|
|
||||||
* @since 5.1
|
|
||||||
* @see OAuth2AuthorizationExchange
|
|
||||||
*/
|
|
||||||
final class OAuth2AuthorizationExchangeValidator {
|
|
||||||
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
|
||||||
|
|
||||||
static void validate(OAuth2AuthorizationExchange authorizationExchange) {
|
|
||||||
OAuth2AuthorizationRequest authorizationRequest = authorizationExchange.getAuthorizationRequest();
|
|
||||||
OAuth2AuthorizationResponse authorizationResponse = authorizationExchange.getAuthorizationResponse();
|
|
||||||
|
|
||||||
if (authorizationResponse.statusError()) {
|
|
||||||
throw new OAuth2AuthorizationException(authorizationResponse.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
|
||||||
throw new OAuth2AuthorizationException(oauth2Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+6
-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.
|
||||||
@@ -117,13 +117,14 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
|||||||
.getAuthorizationExchange().getAuthorizationResponse();
|
.getAuthorizationExchange().getAuthorizationResponse();
|
||||||
|
|
||||||
if (authorizationResponse.statusError()) {
|
if (authorizationResponse.statusError()) {
|
||||||
throw new OAuth2AuthenticationException(
|
return Mono.error(new OAuth2AuthenticationException(
|
||||||
authorizationResponse.getError(), authorizationResponse.getError().toString());
|
authorizationResponse.getError(), authorizationResponse.getError().toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
return Mono.error(new OAuth2AuthenticationException(
|
||||||
|
oauth2Error, oauth2Error.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
|
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
|
||||||
@@ -156,7 +157,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
|||||||
INVALID_ID_TOKEN_ERROR_CODE,
|
INVALID_ID_TOKEN_ERROR_CODE,
|
||||||
"Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId(),
|
"Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId(),
|
||||||
null);
|
null);
|
||||||
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
|
return Mono.error(new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return createOidcToken(clientRegistration, accessTokenResponse)
|
return createOidcToken(clientRegistration, accessTokenResponse)
|
||||||
|
|||||||
+12
-4
@@ -27,6 +27,8 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
|||||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
|
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
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 org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
@@ -146,15 +148,21 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter {
|
|||||||
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(ServerWebExchangeMatcher.MatchResult::isMatch)
|
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
|
||||||
.flatMap(matchResult -> this.authenticationConverter.convert(exchange))
|
.flatMap(matchResult ->
|
||||||
|
this.authenticationConverter.convert(exchange)
|
||||||
|
.onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(
|
||||||
|
e.getError(), e.getError().toString())))
|
||||||
.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))
|
||||||
|
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
|
||||||
|
.onAuthenticationFailure(new WebFilterExchange(exchange, chain), e));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<Void> authenticate(ServerWebExchange exchange,
|
private Mono<Void> authenticate(ServerWebExchange exchange, WebFilterChain chain, Authentication token) {
|
||||||
WebFilterChain chain, Authentication token) {
|
|
||||||
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
|
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
|
||||||
return this.authenticationManager.authenticate(token)
|
return this.authenticationManager.authenticate(token)
|
||||||
|
.onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(
|
||||||
|
e.getError(), e.getError().toString()))
|
||||||
.switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
|
.switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
|
||||||
.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange))
|
.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange))
|
||||||
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
|
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
|
||||||
|
|||||||
+1
-2
@@ -18,7 +18,6 @@ package org.springframework.security.oauth2.client.web.server;
|
|||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
|
|
||||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||||
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;
|
||||||
@@ -33,7 +32,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts from a {@link ServerWebExchange} to an {@link OAuth2LoginAuthenticationToken} that can be authenticated. The
|
* Converts from a {@link ServerWebExchange} to an {@link OAuth2AuthorizationCodeAuthenticationToken} that can be authenticated. The
|
||||||
* converter does not validate any errors it only performs a conversion.
|
* converter does not validate any errors it only performs a conversion.
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
|
|||||||
+54
@@ -29,6 +29,9 @@ import org.springframework.security.oauth2.client.authentication.TestOAuth2Autho
|
|||||||
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.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
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.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
@@ -41,6 +44,7 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -226,6 +230,56 @@ public class OAuth2AuthorizationCodeGrantWebFilterTests {
|
|||||||
verifyZeroInteractions(this.authenticationManager);
|
verifyZeroInteractions(this.authenticationManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-8609
|
||||||
|
@Test
|
||||||
|
public void filterWhenAuthenticationConverterThrowsOAuth2AuthorizationExceptionThenMappedToOAuth2AuthenticationException() {
|
||||||
|
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||||
|
when(this.clientRegistrationRepository.findByRegistrationId(any())).thenReturn(Mono.empty());
|
||||||
|
|
||||||
|
MockServerHttpRequest authorizationRequest =
|
||||||
|
createAuthorizationRequest("/authorization/callback");
|
||||||
|
OAuth2AuthorizationRequest oauth2AuthorizationRequest =
|
||||||
|
createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration);
|
||||||
|
MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse);
|
||||||
|
DefaultWebFilterChain chain = new DefaultWebFilterChain(
|
||||||
|
e -> e.getResponse().setComplete(), Collections.emptyList());
|
||||||
|
|
||||||
|
this.authorizationRequestRepository.saveAuthorizationRequest(oauth2AuthorizationRequest, exchange).block();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.filter.filter(exchange, chain).block())
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.hasMessageContaining("client_registration_not_found");
|
||||||
|
verifyZeroInteractions(this.authenticationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
// gh-8609
|
||||||
|
@Test
|
||||||
|
public void filterWhenAuthenticationManagerThrowsOAuth2AuthorizationExceptionThenMappedToOAuth2AuthenticationException() {
|
||||||
|
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||||
|
when(this.clientRegistrationRepository.findByRegistrationId(any()))
|
||||||
|
.thenReturn(Mono.just(clientRegistration));
|
||||||
|
|
||||||
|
MockServerHttpRequest authorizationRequest =
|
||||||
|
createAuthorizationRequest("/authorization/callback");
|
||||||
|
OAuth2AuthorizationRequest oauth2AuthorizationRequest =
|
||||||
|
createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration);
|
||||||
|
|
||||||
|
when(this.authenticationManager.authenticate(any()))
|
||||||
|
.thenReturn(Mono.error(new OAuth2AuthorizationException(new OAuth2Error("authorization_error"))));
|
||||||
|
|
||||||
|
MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse);
|
||||||
|
DefaultWebFilterChain chain = new DefaultWebFilterChain(
|
||||||
|
e -> e.getResponse().setComplete(), Collections.emptyList());
|
||||||
|
|
||||||
|
this.authorizationRequestRepository.saveAuthorizationRequest(oauth2AuthorizationRequest, exchange).block();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.filter.filter(exchange, chain).block())
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.hasMessageContaining("authorization_error");
|
||||||
|
}
|
||||||
|
|
||||||
private static OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(
|
private static OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(
|
||||||
MockServerHttpRequest authorizationRequest, ClientRegistration registration) {
|
MockServerHttpRequest authorizationRequest, ClientRegistration registration) {
|
||||||
Map<String, Object> additionalParameters = new HashMap<>();
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
|||||||
+6
@@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.jwt;
|
package org.springframework.security.oauth2.jwt;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
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.OAuth2TokenValidator;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||||
@@ -28,6 +31,8 @@ import org.springframework.util.Assert;
|
|||||||
* @since 5.1
|
* @since 5.1
|
||||||
*/
|
*/
|
||||||
public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
|
public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
|
||||||
|
private final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
private static OAuth2Error INVALID_ISSUER =
|
private static OAuth2Error INVALID_ISSUER =
|
||||||
new OAuth2Error(
|
new OAuth2Error(
|
||||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||||
@@ -57,6 +62,7 @@ public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
|
|||||||
if (this.issuer.equals(tokenIssuer)) {
|
if (this.issuer.equals(tokenIssuer)) {
|
||||||
return OAuth2TokenValidatorResult.success();
|
return OAuth2TokenValidatorResult.success();
|
||||||
} else {
|
} else {
|
||||||
|
logger.debug(INVALID_ISSUER.getDescription());
|
||||||
return OAuth2TokenValidatorResult.failure(INVALID_ISSUER);
|
return OAuth2TokenValidatorResult.failure(INVALID_ISSUER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-11
@@ -20,11 +20,13 @@ import java.time.Duration;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
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.OAuth2TokenValidator;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,6 +44,8 @@ import org.springframework.util.Assert;
|
|||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
|
||||||
*/
|
*/
|
||||||
public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> {
|
public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> {
|
||||||
|
private final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
private static final Duration DEFAULT_MAX_CLOCK_SKEW = Duration.of(60, ChronoUnit.SECONDS);
|
private static final Duration DEFAULT_MAX_CLOCK_SKEW = Duration.of(60, ChronoUnit.SECONDS);
|
||||||
|
|
||||||
private final Duration maxClockSkew;
|
private final Duration maxClockSkew;
|
||||||
@@ -72,11 +76,8 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> {
|
|||||||
|
|
||||||
if (expiry != null) {
|
if (expiry != null) {
|
||||||
if (Instant.now(this.clock).minus(maxClockSkew).isAfter(expiry)) {
|
if (Instant.now(this.clock).minus(maxClockSkew).isAfter(expiry)) {
|
||||||
OAuth2Error error = new OAuth2Error(
|
OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt expired at %s", jwt.getExpiresAt()));
|
||||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
return OAuth2TokenValidatorResult.failure(oAuth2Error);
|
||||||
String.format("Jwt expired at %s", jwt.getExpiresAt()),
|
|
||||||
"https://tools.ietf.org/html/rfc6750#section-3.1");
|
|
||||||
return OAuth2TokenValidatorResult.failure(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,17 +85,22 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> {
|
|||||||
|
|
||||||
if (notBefore != null) {
|
if (notBefore != null) {
|
||||||
if (Instant.now(this.clock).plus(maxClockSkew).isBefore(notBefore)) {
|
if (Instant.now(this.clock).plus(maxClockSkew).isBefore(notBefore)) {
|
||||||
OAuth2Error error = new OAuth2Error(
|
OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt used before %s", jwt.getNotBefore()));
|
||||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
return OAuth2TokenValidatorResult.failure(oAuth2Error);
|
||||||
String.format("Jwt used before %s", jwt.getNotBefore()),
|
|
||||||
"https://tools.ietf.org/html/rfc6750#section-3.1");
|
|
||||||
return OAuth2TokenValidatorResult.failure(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return OAuth2TokenValidatorResult.success();
|
return OAuth2TokenValidatorResult.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OAuth2Error createOAuth2Error(String reason) {
|
||||||
|
logger.debug(reason);
|
||||||
|
return new OAuth2Error(
|
||||||
|
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||||
|
reason,
|
||||||
|
"https://tools.ietf.org/html/rfc6750#section-3.1");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* '
|
* '
|
||||||
* Use this {@link Clock} with {@link Instant#now()} for assessing
|
* Use this {@link Clock} with {@link Instant#now()} for assessing
|
||||||
|
|||||||
+13
-2
@@ -21,6 +21,7 @@ import java.net.URL;
|
|||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -47,10 +48,12 @@ import org.springframework.http.HttpMethod;
|
|||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.RequestEntity;
|
import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.client.RestOperations;
|
import org.springframework.web.client.RestOperations;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
@@ -190,9 +193,17 @@ public final class NimbusJwtDecoderJwkSupport implements JwtDecoder {
|
|||||||
private Jwt validateJwt(Jwt jwt){
|
private Jwt validateJwt(Jwt jwt){
|
||||||
OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt);
|
OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt);
|
||||||
if (result.hasErrors()) {
|
if (result.hasErrors()) {
|
||||||
String description = result.getErrors().iterator().next().getDescription();
|
Collection<OAuth2Error> errors = result.getErrors();
|
||||||
|
String validationErrorString = "Unable to validate Jwt";
|
||||||
|
for (OAuth2Error oAuth2Error : errors) {
|
||||||
|
if (!StringUtils.isEmpty(oAuth2Error.getDescription())) {
|
||||||
|
validationErrorString = String.format(
|
||||||
|
DECODING_ERROR_MESSAGE_TEMPLATE, oAuth2Error.getDescription());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
throw new JwtValidationException(
|
throw new JwtValidationException(
|
||||||
String.format(DECODING_ERROR_MESSAGE_TEMPLATE, description),
|
validationErrorString,
|
||||||
result.getErrors());
|
result.getErrors());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-3
@@ -17,6 +17,7 @@ package org.springframework.security.oauth2.jwt;
|
|||||||
|
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -40,10 +41,12 @@ import com.nimbusds.jwt.proc.DefaultJWTProcessor;
|
|||||||
import com.nimbusds.jwt.proc.JWTProcessor;
|
import com.nimbusds.jwt.proc.JWTProcessor;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of a {@link ReactiveJwtDecoder} that "decodes" a
|
* An implementation of a {@link ReactiveJwtDecoder} that "decodes" a
|
||||||
@@ -184,9 +187,16 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
|||||||
private Jwt validateJwt(Jwt jwt) {
|
private Jwt validateJwt(Jwt jwt) {
|
||||||
OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt);
|
OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt);
|
||||||
|
|
||||||
if ( result.hasErrors() ) {
|
if (result.hasErrors()) {
|
||||||
String message = result.getErrors().iterator().next().getDescription();
|
Collection<OAuth2Error> errors = result.getErrors();
|
||||||
throw new JwtValidationException(message, result.getErrors());
|
String validationErrorString = "Unable to validate Jwt";
|
||||||
|
for (OAuth2Error oAuth2Error : errors) {
|
||||||
|
if (!StringUtils.isEmpty(oAuth2Error.getDescription())) {
|
||||||
|
validationErrorString = oAuth2Error.getDescription();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new JwtValidationException(validationErrorString, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
return jwt;
|
return jwt;
|
||||||
|
|||||||
+23
@@ -33,6 +33,7 @@ import org.assertj.core.api.Assertions;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mockito;
|
||||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
@@ -241,6 +242,28 @@ public class NimbusJwtDecoderJwkSupportTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void decodeWhenReadingErrorPickTheFirstErrorMessage() throws Exception {
|
||||||
|
try ( MockWebServer server = new MockWebServer() ) {
|
||||||
|
server.enqueue(new MockResponse().setBody(JWK_SET));
|
||||||
|
String jwkSetUrl = server.url("/.well-known/jwks.json").toString();
|
||||||
|
|
||||||
|
NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(jwkSetUrl);
|
||||||
|
OAuth2TokenValidator<Jwt> jwtValidator = mock(OAuth2TokenValidator.class);
|
||||||
|
decoder.setJwtValidator(jwtValidator);
|
||||||
|
|
||||||
|
OAuth2Error errorEmpty = new OAuth2Error("mock-error", "", "mock-uri");
|
||||||
|
OAuth2Error error = new OAuth2Error("mock-error", "mock-description", "mock-uri");
|
||||||
|
OAuth2Error error2 = new OAuth2Error("mock-error-second", "mock-description-second", "mock-uri-second");
|
||||||
|
OAuth2TokenValidatorResult result = OAuth2TokenValidatorResult.failure(errorEmpty, error, error2);
|
||||||
|
Mockito.when(jwtValidator.validate(any(Jwt.class))).thenReturn(result);
|
||||||
|
|
||||||
|
Assertions.assertThatCode(() -> decoder.decode(SIGNED_JWT))
|
||||||
|
.isInstanceOf(JwtValidationException.class)
|
||||||
|
.hasMessageContaining("mock-description");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByClaimSetConverter() throws Exception {
|
public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByClaimSetConverter() throws Exception {
|
||||||
try ( MockWebServer server = new MockWebServer() ) {
|
try ( MockWebServer server = new MockWebServer() ) {
|
||||||
|
|||||||
+17
@@ -177,6 +177,23 @@ public class NimbusReactiveJwtDecoderTests {
|
|||||||
.hasMessageContaining("mock-description");
|
.hasMessageContaining("mock-description");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void decodeWhenReadingErrorPickTheFirstErrorMessage() {
|
||||||
|
OAuth2TokenValidator<Jwt> jwtValidator = mock(OAuth2TokenValidator.class);
|
||||||
|
this.decoder.setJwtValidator(jwtValidator);
|
||||||
|
|
||||||
|
OAuth2Error errorEmpty = new OAuth2Error("mock-error", "", "mock-uri");
|
||||||
|
OAuth2Error error = new OAuth2Error("mock-error", "mock-description", "mock-uri");
|
||||||
|
OAuth2Error error2 = new OAuth2Error("mock-error-second", "mock-description-second", "mock-uri-second");
|
||||||
|
OAuth2TokenValidatorResult result = OAuth2TokenValidatorResult.failure(errorEmpty, error, error2);
|
||||||
|
when(jwtValidator.validate(any(Jwt.class))).thenReturn(result);
|
||||||
|
|
||||||
|
assertThatCode(() -> this.decoder.decode(this.messageReadToken).block())
|
||||||
|
.isInstanceOf(JwtValidationException.class)
|
||||||
|
.hasMessageContaining("mock-description");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setJwtValidatorWhenGivenNullThrowsIllegalArgumentException() {
|
public void setJwtValidatorWhenGivenNullThrowsIllegalArgumentException() {
|
||||||
assertThatCode(() -> this.decoder.setJwtValidator(null))
|
assertThatCode(() -> this.decoder.setJwtValidator(null))
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ import org.springframework.util.StringUtils;
|
|||||||
*/
|
*/
|
||||||
public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
||||||
|
|
||||||
private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+)=*$");
|
private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$");
|
||||||
|
|
||||||
private boolean allowFormEncodedBodyParameter = false;
|
private boolean allowFormEncodedBodyParameter = false;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -43,12 +43,12 @@ import java.util.regex.Pattern;
|
|||||||
*/
|
*/
|
||||||
public class ServerBearerTokenAuthenticationConverter
|
public class ServerBearerTokenAuthenticationConverter
|
||||||
implements ServerAuthenticationConverter {
|
implements ServerAuthenticationConverter {
|
||||||
private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+)=*$");
|
private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$");
|
||||||
|
|
||||||
private boolean allowUriQueryParameter = false;
|
private boolean allowUriQueryParameter = false;
|
||||||
|
|
||||||
public Mono<Authentication> convert(ServerWebExchange exchange) {
|
public Mono<Authentication> convert(ServerWebExchange exchange) {
|
||||||
return Mono.justOrEmpty(token(exchange.getRequest()))
|
return Mono.fromCallable(() -> token(exchange.getRequest()))
|
||||||
.map(token -> {
|
.map(token -> {
|
||||||
if (token.isEmpty()) {
|
if (token.isEmpty()) {
|
||||||
BearerTokenError error = invalidTokenError();
|
BearerTokenError error = invalidTokenError();
|
||||||
|
|||||||
+11
-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.
|
||||||
@@ -51,6 +51,16 @@ public class DefaultBearerTokenResolverTests {
|
|||||||
assertThat(this.resolver.resolve(request)).isEqualTo(TEST_TOKEN);
|
assertThat(this.resolver.resolve(request)).isEqualTo(TEST_TOKEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-8502
|
||||||
|
@Test
|
||||||
|
public void resolveWhenHeaderEndsWithPaddingIndicatorThenTokenIsResolved() {
|
||||||
|
String token = TEST_TOKEN + "==";
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.addHeader("Authorization", "Bearer " + token);
|
||||||
|
|
||||||
|
assertThat(this.resolver.resolve(request)).isEqualTo(token);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resolveWhenNoHeaderIsPresentThenTokenIsNotResolved() {
|
public void resolveWhenNoHeaderIsPresentThenTokenIsNotResolved() {
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
|||||||
+26
-3
@@ -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.
|
||||||
@@ -16,8 +16,11 @@
|
|||||||
|
|
||||||
package org.springframework.security.oauth2.server.resource.web.server;
|
package org.springframework.security.oauth2.server.resource.web.server;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||||
@@ -27,8 +30,6 @@ import org.springframework.security.oauth2.server.resource.BearerTokenAuthentica
|
|||||||
import org.springframework.security.oauth2.server.resource.BearerTokenError;
|
import org.springframework.security.oauth2.server.resource.BearerTokenError;
|
||||||
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
|
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
|
||||||
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
import static org.assertj.core.api.Assertions.catchThrowableOfType;
|
import static org.assertj.core.api.Assertions.catchThrowableOfType;
|
||||||
@@ -56,6 +57,17 @@ public class ServerBearerTokenAuthenticationConverterTests {
|
|||||||
assertThat(convertToToken(request).getToken()).isEqualTo(TEST_TOKEN);
|
assertThat(convertToToken(request).getToken()).isEqualTo(TEST_TOKEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-8502
|
||||||
|
@Test
|
||||||
|
public void resolveWhenHeaderEndsWithPaddingIndicatorThenTokenIsResolved() {
|
||||||
|
String token = TEST_TOKEN + "==";
|
||||||
|
MockServerHttpRequest.BaseBuilder<?> request = MockServerHttpRequest
|
||||||
|
.get("/")
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token);
|
||||||
|
|
||||||
|
assertThat(convertToToken(request).getToken()).isEqualTo(token);
|
||||||
|
}
|
||||||
|
|
||||||
// gh-7011
|
// gh-7011
|
||||||
@Test
|
@Test
|
||||||
public void resolveWhenValidHeaderIsEmptyStringThenTokenIsResolved() {
|
public void resolveWhenValidHeaderIsEmptyStringThenTokenIsResolved() {
|
||||||
@@ -110,6 +122,17 @@ public class ServerBearerTokenAuthenticationConverterTests {
|
|||||||
.hasMessageContaining(("Bearer token is malformed"));
|
.hasMessageContaining(("Bearer token is malformed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-8865
|
||||||
|
@Test
|
||||||
|
public void resolveWhenHeaderWithInvalidCharactersIsPresentAndNotSubscribedThenNoneExceptionIsThrown() {
|
||||||
|
MockServerHttpRequest.BaseBuilder<?> request = MockServerHttpRequest
|
||||||
|
.get("/")
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, "Bearer an\"invalid\"token");
|
||||||
|
|
||||||
|
assertThatCode(() -> this.converter.convert(MockServerWebExchange.from(request)))
|
||||||
|
.doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resolveWhenValidHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown() {
|
public void resolveWhenValidHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown() {
|
||||||
MockServerHttpRequest.BaseBuilder<?> request = MockServerHttpRequest
|
MockServerHttpRequest.BaseBuilder<?> request = MockServerHttpRequest
|
||||||
|
|||||||
+6
-7
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 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.
|
||||||
@@ -89,17 +89,16 @@ public class AuthenticationWebFilter implements WebFilter {
|
|||||||
.filter( matchResult -> matchResult.isMatch())
|
.filter( matchResult -> 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))
|
||||||
|
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
|
||||||
|
.onAuthenticationFailure(new WebFilterExchange(exchange, chain), e));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<Void> authenticate(ServerWebExchange exchange,
|
private Mono<Void> authenticate(ServerWebExchange exchange, WebFilterChain chain, Authentication token) {
|
||||||
WebFilterChain chain, Authentication token) {
|
|
||||||
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
|
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
|
||||||
return this.authenticationManager.authenticate(token)
|
return this.authenticationManager.authenticate(token)
|
||||||
.switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
|
.switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
|
||||||
.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange))
|
.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange));
|
||||||
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
|
|
||||||
.onAuthenticationFailure(webFilterExchange, e));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Mono<Void> onAuthenticationSuccess(Authentication authentication, WebFilterExchange webFilterExchange) {
|
protected Mono<Void> onAuthenticationSuccess(Authentication authentication, WebFilterExchange webFilterExchange) {
|
||||||
|
|||||||
+3
-3
@@ -105,7 +105,7 @@ public class LoginPageGeneratingWebFilter implements WebFilter {
|
|||||||
+ " </head>\n"
|
+ " </head>\n"
|
||||||
+ " <body>\n"
|
+ " <body>\n"
|
||||||
+ " <div class=\"container\">\n"
|
+ " <div class=\"container\">\n"
|
||||||
+ formLogin(queryParams, csrfTokenHtmlInput)
|
+ formLogin(queryParams, contextPath, csrfTokenHtmlInput)
|
||||||
+ oauth2LoginLinks(queryParams, contextPath, this.oauth2AuthenticationUrlToClientName)
|
+ oauth2LoginLinks(queryParams, contextPath, this.oauth2AuthenticationUrlToClientName)
|
||||||
+ " </div>\n"
|
+ " </div>\n"
|
||||||
+ " </body>\n"
|
+ " </body>\n"
|
||||||
@@ -114,13 +114,13 @@ public class LoginPageGeneratingWebFilter implements WebFilter {
|
|||||||
return page.getBytes(Charset.defaultCharset());
|
return page.getBytes(Charset.defaultCharset());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formLogin(MultiValueMap<String, String> queryParams, String csrfTokenHtmlInput) {
|
private String formLogin(MultiValueMap<String, String> queryParams, String contextPath, String csrfTokenHtmlInput) {
|
||||||
if (!this.formLoginEnabled) {
|
if (!this.formLoginEnabled) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
boolean isError = queryParams.containsKey("error");
|
boolean isError = queryParams.containsKey("error");
|
||||||
boolean isLogoutSuccess = queryParams.containsKey("logout");
|
boolean isLogoutSuccess = queryParams.containsKey("logout");
|
||||||
return " <form class=\"form-signin\" method=\"post\" action=\"/login\">\n"
|
return " <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + "/login\">\n"
|
||||||
+ " <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
|
+ " <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
|
||||||
+ createError(isError)
|
+ createError(isError)
|
||||||
+ createLogoutSuccess(isLogoutSuccess)
|
+ createLogoutSuccess(isLogoutSuccess)
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.web.server.ui;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||||
|
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
|
||||||
|
public class LoginPageGeneratingWebFilterTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenLoginWithContextPathThenActionContainsContextPath() throws Exception {
|
||||||
|
LoginPageGeneratingWebFilter filter = new LoginPageGeneratingWebFilter();
|
||||||
|
filter.setFormLoginEnabled(true);
|
||||||
|
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/login").contextPath("/test"));
|
||||||
|
|
||||||
|
filter.filter(exchange, e -> Mono.empty()).block();
|
||||||
|
|
||||||
|
assertThat(exchange.getResponse().getBodyAsString().block()).contains("action=\"/test/login\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenLoginWithNoContextPathThenActionDoesNotContainsContextPath() throws Exception {
|
||||||
|
LoginPageGeneratingWebFilter filter = new LoginPageGeneratingWebFilter();
|
||||||
|
filter.setFormLoginEnabled(true);
|
||||||
|
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/login"));
|
||||||
|
|
||||||
|
filter.filter(exchange, e -> Mono.empty()).block();
|
||||||
|
|
||||||
|
assertThat(exchange.getResponse().getBodyAsString().block()).contains("action=\"/login\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user