1
0
mirror of synced 2026-08-05 01:36:56 +00:00

Compare commits

...

42 Commits

Author SHA1 Message Date
Josh Cummings 8407ba4f4d Release 5.1.12.RELEASE 2020-08-05 08:59:44 -06:00
Josh Cummings 7bdd1b4d83 Update to Spring Ldap 2.3.3
Closes gh-8943
2020-08-05 09:01:25 -06:00
Josh Cummings 1a2e9390ca Update to Hibernate Validator 6.0.20
Closes gh-8942
2020-08-05 09:01:18 -06:00
Josh Cummings 8a3f5d5ffe Update to Hibernate Entitymanager 5.3.17
Closes gh-8941
2020-08-05 09:01:09 -06:00
Josh Cummings 010817e9e1 Update to Groovy 2.4.20
Closes gh-8940
2020-08-05 09:01:03 -06:00
Josh Cummings 3dcd022f93 Update to Spring Boot 2.1.16.RELEASE
Closes gh-8939
2020-08-05 09:00:52 -06:00
Josh Cummings 7f5b24bed0 Update to Google App Engine 1.9.81
Closes gh-8938
2020-08-05 09:00:44 -06:00
Josh Cummings 670c99e7ea Update to Jackson Databind 2.9.10.5
Closes gh-8937
2020-08-05 09:00:37 -06:00
Josh Cummings 641de854d7 Update to Project Reactor Californium-SR20
Closes gh-8936
2020-08-05 09:00:29 -06:00
Josh Cummings 1f386b1db3 Update to Spring Framework 5.1.17
Closes gh-8935
2020-08-05 09:00:20 -06:00
Josh Cummings 4f0172d0f0 Update to Spring Data Lovelace-SR19
Closes gh-8934
2020-08-05 08:59:44 -06:00
Dávid Kováč ca272e4267 Resolve Bearer token after subscribing to publisher
Bearer token was resolved immediately after calling method convert. In situations when malformed token was provided or authorization header and access token query param were present in request exception was thrown instead of signalling error.
After this change Bearer token is resolved on subscription and invalid states are handled by signaling error to subscriber.

Closes gh-8865
2020-08-03 11:11:56 -05:00
Josh Cummings d956ebf59b Fix Broken Test
Issue gh-8589
2020-07-31 13:45:10 -06:00
Josh Cummings acfe4bdcfb Polish to Avoid NPE
Issue gh-5648

Co-authored-by: MattyA <mat.auburn@gmail.com>
2020-07-31 09:00:24 -06:00
Josh Cummings 48a0514965 Additional Jwt Validation Debug Messages
Closes gh-8589

Co-authored-by: MattyA <mat.auburn@gmail.com>
2020-07-31 09:00:16 -06:00
Josh Cummings e937366f50 Polish WebSecurityConfigurerAdapter JavaDoc
Issue gh-8784
2020-07-20 15:25:02 -06:00
Romil Patel 0c85dd9cd1 WebSecurityConfigurerAdapter JavaDoc
Closes gh-8784
2020-07-20 15:24:59 -06:00
Josh Cummings 7e7f85c18c Polish Bearer Token Padding
Issue gh-8502
2020-07-16 13:15:06 -06:00
kothasa 157498bf44 Bearer Token Padding
Closes gh-8502
2020-07-16 13:13:04 -06:00
wangsong ce4345b0ed Fix ProviderManager Javadoc typo
Closes gh-8800
2020-07-07 17:17:39 -05:00
Rob Winch 7859970ff6 LoginPageGeneratingWebFilter honors context path
Closes gh-8807
2020-07-07 14:02:32 -05:00
Ellie Bahadori fa09295fd6 Use Github Actions workflow for PRs and remove Travis
Closes gh-8717
2020-06-30 05:23:30 -04:00
Rob Winch 9a8d324d72 Better scp Retry Settings 2020-06-25 11:36:36 -05:00
Evgeniy Cheban 6f4d05193e DefaultWebSecurityExpressionHandler uses RoleHierarchy bean
Fixes gh-7059
2020-06-10 17:20:52 -04:00
Joe Grandja e146a7c16b OAuth2LoginAuthenticationWebFilter should handle OAuth2AuthorizationException
Issue gh-8609
2020-06-09 17:10:07 -04:00
Joe Grandja a372ec9ef5 OAuth2AuthorizationCodeGrantWebFilter should handle OAuth2AuthorizationException
Fixes gh-8609
2020-06-09 17:10:07 -04:00
Eleftheria Stein 0b0abfb911 Next development version 2020-06-03 18:04:07 -04:00
Eleftheria Stein 37568780a1 Release 5.1.11.RELEASE 2020-06-03 17:48:40 -04:00
Josh Cummings d80b1865a5 Polish setAllowedHostnames
Added JavaDoc to method, including @since attribute

Issue gh-4310
2020-06-03 08:54:06 -06:00
Eddú Meléndez 52c80c78e5 Add support for allowedHostnames in StrictHttpFirewall
Introduce a new method `setAllowedHostnames` which perform the validation
against untrusted hostnames.

Fixes gh-4310
2020-06-03 08:53:59 -06:00
Eleftheria Stein ded83cc1b3 Update to jaxb-impl 2.3.3
Fixes gh-8634
2020-06-02 18:45:14 -04:00
Eleftheria Stein 7a1833c1df Update to okhttp 3.12.12
Fixes gh-8635
2020-06-02 18:45:14 -04:00
Eleftheria Stein e6630ea0f1 Update to mockwebserver 3.12.12
Fixes gh-8633
2020-06-02 18:45:14 -04:00
Eleftheria Stein 2400e8fde2 Update to Spring Boot 2.1.14.RELEASE
Fixes gh-8632
2020-06-02 18:45:14 -04:00
justmehyp f05d70a4a5 Remove unused field 'digester' in Md4PasswordEncoder
`private Digester digester;`  defined in Md4PasswordEncoder is never used. So remove it.

Closes gh-8553
2020-05-21 11:21:40 -05:00
Maksim Vinogradov 8bb4e72aff Prevent StackOverflowError for AccessControlEntryImpl.hashCode
Getting StackOverflowError when invoke AclImpl.hashCode because of
cross-references between AclImpl and AccessControlEntryImpl

Remove from AccessControlEntryImpl.hashCode method invocation of
acl.hashCode

fixes gh-5401
2020-05-21 10:08:56 -05:00
Rob Winch f58a262eb4 Revert "Create the CSRF token on the bounded elactic scheduler"
Reactor did not add bounded elastic until a later version.

This reverts commit c0154f2315.
2020-05-18 11:10:41 -05:00
cbornet c0154f2315 Create the CSRF token on the bounded elactic scheduler
The CSRF token is created with a call to UUID.randomUUID which is blocking.
This change ensures this blocking call is done on the bounded elastic scheduler which supports blocking calls.

Fixes gh-8128
2020-05-18 11:07:08 -05:00
Artyom Tarynin cea2b556d6 Update AntPathRequestMatcher.java
Fixes gh-8512
2020-05-14 10:36:30 -04:00
Dávid Kovács faa02e8bc0 Document NoOpPasswordEncoder will not be removed
This commit adds extension to deprecation notice.

Fixes gh-8506
2020-05-13 12:56:22 -05:00
Rob Winch d9f57492d4 Fix non-standard HTTP method for CsrfWebFilter
Closes gh-8452
2020-05-12 13:21:22 -05:00
Eleftheria Stein b007fdc333 Next development version 2020-05-06 16:27:44 -04:00
42 changed files with 542 additions and 174 deletions
+22
View File
@@ -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
View File
@@ -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
-2
View File
@@ -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://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 provides security services for the https://docs.spring.io[Spring IO Platform]. Spring Security 5.0 requires Spring 5.0 as
@@ -1,5 +1,5 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
* Copyright 2002-2016 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.
@@ -131,10 +131,9 @@ public class AccessControlEntryImpl implements AccessControlEntry,
@Override
public int hashCode() {
int result = this.acl.hashCode();
result = 31 * result + this.permission.hashCode();
int result = this.permission.hashCode();
result = 31 * result + (this.id != null ? this.id.hashCode() : 0);
result = 31 * result + this.sid.hashCode();
result = 31 * result + (this.sid.hashCode());
result = 31 * result + (this.auditFailure ? 1 : 0);
result = 31 * result + (this.auditSuccess ? 1 : 0);
result = 31 * result + (this.granting ? 1 : 0);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@@ -560,6 +560,25 @@ public class AclImplTests {
childAcl.setParent(changeParentAcl);
}
@Test
public void hashCodeWithoutStackOverFlow() throws Exception {
//given
Sid sid = new PrincipalSid("pSid");
ObjectIdentity oid = new ObjectIdentityImpl("type", 1);
AclAuthorizationStrategy authStrategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("role"));
PermissionGrantingStrategy grantingStrategy = new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
AclImpl acl = new AclImpl(oid, 1L, authStrategy, grantingStrategy, null, null, false, sid);
AccessControlEntryImpl ace = new AccessControlEntryImpl(1L, acl, sid, BasePermission.READ, true, true, true);
Field fieldAces = FieldUtils.getField(AclImpl.class, "aces");
fieldAces.setAccessible(true);
List<AccessControlEntryImpl> aces = (List<AccessControlEntryImpl>) fieldAces.get(acl);
aces.add(ace);
//when - then none StackOverFlowError been raised
ace.hashCode();
}
// ~ Inner Classes
// ==================================================================================================
@@ -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");
* 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.security.access.PermissionEvaluator;
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.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder;
@@ -74,6 +75,7 @@ import org.springframework.web.filter.DelegatingFilterProxy;
* @see WebSecurityConfiguration
*
* @author Rob Winch
* @author Evgeniy Cheban
* @since 3.2
*/
public final class WebSecurity extends
@@ -385,6 +387,11 @@ public final class WebSecurity extends
throws BeansException {
this.defaultWebSecurityExpressionHandler
.setApplicationContext(applicationContext);
try {
this.defaultWebSecurityExpressionHandler.setRoleHierarchy(applicationContext.getBean(RoleHierarchy.class));
} catch (NoSuchBeanDefinitionException e) {}
try {
this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean(
PermissionEvaluator.class));
@@ -332,6 +332,13 @@ public abstract class WebSecurityConfigurerAdapter implements
/**
* Override this method to configure {@link WebSecurity}. For example, if you wish to
* 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 {
}
@@ -345,6 +352,10 @@ public abstract class WebSecurityConfigurerAdapter implements
* http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
* </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
* @throws Exception if an error occurs
*/
@@ -34,6 +34,8 @@ import java.util.Map;
import java.util.Optional;
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.util.context.Context;
@@ -578,7 +580,12 @@ public class ServerHttpSecurity {
private ServerAuthenticationConverter getAuthenticationConverter(ReactiveClientRegistrationRepository clientRegistrationRepository) {
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;
}
@@ -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");
* 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.expression.AbstractSecurityExpressionHandler;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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 Joe Grandja
* @author Evgeniy Cheban
*/
public class WebSecurityConfigurationTests {
@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
public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEvaluatorUsed() throws Exception {
this.spring.register(WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig.class).autowire();
@@ -16,12 +16,6 @@
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.Test;
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.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.WebHandler;
import reactor.core.publisher.Mono;
import java.time.Duration;
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
* @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 {
@Override
@@ -218,7 +218,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
((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
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
@@ -235,7 +235,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
"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
if (parentException == null) {
prepareException(lastException, authentication);
@@ -83,8 +83,6 @@ public class Md4PasswordEncoder implements PasswordEncoder {
private StringKeyGenerator saltGenerator = new Base64StringKeyGenerator();
private boolean encodeHashAsBase64;
private Digester digester;
public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
this.encodeHashAsBase64 = encodeHashAsBase64;
@@ -26,7 +26,8 @@ package org.springframework.security.crypto.password;
* @deprecated This PasswordEncoder is not secure. Instead use an
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
* password upgrades.
* password upgrades. There are no plans to remove this support. It is deprecated to indicate that
* this is a legacy implementation and using it is considered insecure.
*/
@Deprecated
public final class NoOpPasswordEncoder implements PasswordEncoder {
@@ -10,6 +10,13 @@ asciidoctor {
'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 {
from(project(':spring-security-docs-guides').asciidoctor) {
into 'guides'
+3 -3
View File
@@ -1,3 +1,3 @@
gaeVersion=1.9.80
springBootVersion=2.1.13.RELEASE
version=5.1.10.RELEASE
gaeVersion=1.9.81
springBootVersion=2.1.16.RELEASE
version=5.1.12.RELEASE
+18 -18
View File
@@ -1,13 +1,13 @@
if (!project.hasProperty('reactorVersion')) {
ext.reactorVersion = 'Californium-SR18'
ext.reactorVersion = 'Californium-SR20'
}
if (!project.hasProperty('springVersion')) {
ext.springVersion = '5.1.15.RELEASE'
ext.springVersion = '5.1.17.RELEASE'
}
if (!project.hasProperty('springDataVersion')) {
ext.springDataVersion = 'Lovelace-SR17'
ext.springDataVersion = 'Lovelace-SR19'
}
dependencyManagement {
@@ -18,7 +18,7 @@ dependencyManagement {
}
dependencies {
dependency 'cglib:cglib-nodep:3.2.12'
dependency 'com.squareup.okhttp3:mockwebserver:3.12.10'
dependency 'com.squareup.okhttp3:mockwebserver:3.12.12'
dependency 'opensymphony:sitemesh:2.4.2'
dependency 'org.gebish:geb-spock:0.10.0'
dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
@@ -42,24 +42,24 @@ dependencyManagement {
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-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.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-labs:1.9.80'
dependency 'com.google.appengine:appengine-api-stubs:1.9.80'
dependency 'com.google.appengine:appengine-testing:1.9.80'
dependency 'com.google.appengine:appengine:1.9.80'
dependency 'com.google.appengine:appengine-api-1.0-sdk:1.9.81'
dependency 'com.google.appengine:appengine-api-labs:1.9.81'
dependency 'com.google.appengine:appengine-api-stubs:1.9.81'
dependency 'com.google.appengine:appengine-testing:1.9.81'
dependency 'com.google.appengine:appengine:1.9.81'
dependency 'com.google.code.gson:gson:2.8.2'
dependency 'com.google.guava:guava:20.0'
dependency 'com.google.inject:guice:3.0'
dependency 'com.nimbusds:lang-tag:1.4.3'
dependency 'com.nimbusds:nimbus-jose-jwt:6.0.2'
dependency 'com.nimbusds:oauth2-oidc-sdk:6.0'
dependency 'com.squareup.okhttp3:okhttp:3.12.10'
dependency 'com.squareup.okhttp3:okhttp:3.12.12'
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-impl:2.3.2'
dependency 'com.sun.xml.bind:jaxb-impl:2.3.3'
dependency 'com.unboundid:unboundid-ldapsdk:4.0.14'
dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1'
dependency 'commons-cli:commons-cli:1.4'
@@ -147,9 +147,9 @@ dependencyManagement {
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
dependency 'org.bouncycastle:bcpkix-jdk15on:1.64'
dependency 'org.bouncycastle:bcprov-jdk15on:1.58'
dependency 'org.codehaus.groovy:groovy-all:2.4.19'
dependency 'org.codehaus.groovy:groovy-json:2.4.19'
dependency 'org.codehaus.groovy:groovy:2.4.19'
dependency 'org.codehaus.groovy:groovy-all:2.4.20'
dependency 'org.codehaus.groovy:groovy-json:2.4.20'
dependency 'org.codehaus.groovy:groovy:2.4.20'
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-client:9.4.27.v20200227'
@@ -171,8 +171,8 @@ dependencyManagement {
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:hibernate-core:5.2.18.Final'
dependency 'org.hibernate:hibernate-entitymanager:5.3.15.Final'
dependency 'org.hibernate:hibernate-validator:6.0.19.Final'
dependency 'org.hibernate:hibernate-entitymanager:5.3.17.Final'
dependency 'org.hibernate:hibernate-validator:6.0.20.Final'
dependency 'org.hsqldb:hsqldb:2.4.1'
dependency 'org.jasig.cas.client:cas-client-core:3.5.1'
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-nop:1.7.30'
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.unbescape:unbescape:1.1.5.RELEASE'
dependency 'org.w3c.css:sac:1.3'
@@ -20,7 +20,11 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
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.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
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>
*/
public class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
/**
@@ -59,8 +64,18 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
OAuth2AuthorizationExchangeValidator.validate(
authorizationCodeAuthentication.getAuthorizationExchange());
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication
.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 =
this.accessTokenResponseClient.getTokenResponse(
@@ -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");
* 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.userinfo.ReactiveOAuth2UserService;
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.endpoint.OAuth2AccessTokenResponse;
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.util.Assert;
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.4">Section 4.1.4 Access Token Response</a>
*/
public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements
ReactiveAuthenticationManager {
public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements ReactiveAuthenticationManager {
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
public OAuth2AuthorizationCodeReactiveAuthenticationManager(
@@ -70,7 +74,16 @@ public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements
return Mono.defer(() -> {
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(
token.getClientRegistration(),
@@ -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 &quot;exchange&quot; 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);
}
}
}
@@ -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");
* you may not use this file except in compliance with the License.
@@ -117,13 +117,14 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
.getAuthorizationExchange().getAuthorizationResponse();
if (authorizationResponse.statusError()) {
throw new OAuth2AuthenticationException(
authorizationResponse.getError(), authorizationResponse.getError().toString());
return Mono.error(new OAuth2AuthenticationException(
authorizationResponse.getError(), authorizationResponse.getError().toString()));
}
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
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(
@@ -156,7 +157,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
INVALID_ID_TOKEN_ERROR_CODE,
"Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId(),
null);
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
return Mono.error(new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString()));
}
return createOidcToken(clientRegistration, accessTokenResponse)
@@ -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.registration.ReactiveClientRegistrationRepository;
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.OAuth2AuthorizationResponse;
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) {
return this.requiresAuthenticationMatcher.matches(exchange)
.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()))
.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,
WebFilterChain chain, Authentication token) {
private Mono<Void> authenticate(ServerWebExchange exchange, WebFilterChain chain, Authentication token) {
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
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()))))
.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange))
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
@@ -18,7 +18,6 @@ package org.springframework.security.oauth2.client.web.server;
import org.springframework.security.core.Authentication;
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.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
@@ -33,7 +32,7 @@ import org.springframework.web.util.UriComponentsBuilder;
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.
* @author Rob Winch
* @since 5.1
@@ -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.ReactiveClientRegistrationRepository;
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.OAuth2ParameterNames;
import org.springframework.util.CollectionUtils;
@@ -41,6 +44,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
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.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -226,6 +230,56 @@ public class OAuth2AuthorizationCodeGrantWebFilterTests {
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(
MockServerHttpRequest authorizationRequest, ClientRegistration registration) {
Map<String, Object> additionalParameters = new HashMap<>();
@@ -15,6 +15,9 @@
*/
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.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
@@ -28,6 +31,8 @@ import org.springframework.util.Assert;
* @since 5.1
*/
public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
private final Log logger = LogFactory.getLog(getClass());
private static OAuth2Error INVALID_ISSUER =
new OAuth2Error(
OAuth2ErrorCodes.INVALID_REQUEST,
@@ -57,6 +62,7 @@ public final class JwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
if (this.issuer.equals(tokenIssuer)) {
return OAuth2TokenValidatorResult.success();
} else {
logger.debug(INVALID_ISSUER.getDescription());
return OAuth2TokenValidatorResult.failure(INVALID_ISSUER);
}
}
@@ -20,11 +20,13 @@ import java.time.Duration;
import java.time.Instant;
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.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
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>
*/
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 final Duration maxClockSkew;
@@ -72,11 +76,8 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> {
if (expiry != null) {
if (Instant.now(this.clock).minus(maxClockSkew).isAfter(expiry)) {
OAuth2Error error = new OAuth2Error(
OAuth2ErrorCodes.INVALID_REQUEST,
String.format("Jwt expired at %s", jwt.getExpiresAt()),
"https://tools.ietf.org/html/rfc6750#section-3.1");
return OAuth2TokenValidatorResult.failure(error);
OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt expired at %s", jwt.getExpiresAt()));
return OAuth2TokenValidatorResult.failure(oAuth2Error);
}
}
@@ -84,17 +85,22 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> {
if (notBefore != null) {
if (Instant.now(this.clock).plus(maxClockSkew).isBefore(notBefore)) {
OAuth2Error error = new OAuth2Error(
OAuth2ErrorCodes.INVALID_REQUEST,
String.format("Jwt used before %s", jwt.getNotBefore()),
"https://tools.ietf.org/html/rfc6750#section-3.1");
return OAuth2TokenValidatorResult.failure(error);
OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt used before %s", jwt.getNotBefore()));
return OAuth2TokenValidatorResult.failure(oAuth2Error);
}
}
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
@@ -21,6 +21,7 @@ import java.net.URL;
import java.text.ParseException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -47,10 +48,12 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
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.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
@@ -190,9 +193,17 @@ public final class NimbusJwtDecoderJwkSupport implements JwtDecoder {
private Jwt validateJwt(Jwt jwt){
OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt);
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(
String.format(DECODING_ERROR_MESSAGE_TEMPLATE, description),
validationErrorString,
result.getErrors());
}
@@ -17,6 +17,7 @@ package org.springframework.security.oauth2.jwt;
import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -40,10 +41,12 @@ import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import com.nimbusds.jwt.proc.JWTProcessor;
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.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* An implementation of a {@link ReactiveJwtDecoder} that &quot;decodes&quot; a
@@ -184,9 +187,16 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
private Jwt validateJwt(Jwt jwt) {
OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt);
if ( result.hasErrors() ) {
String message = result.getErrors().iterator().next().getDescription();
throw new JwtValidationException(message, result.getErrors());
if (result.hasErrors()) {
Collection<OAuth2Error> errors = 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;
@@ -33,6 +33,7 @@ import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
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
public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByClaimSetConverter() throws Exception {
try ( MockWebServer server = new MockWebServer() ) {
@@ -177,6 +177,23 @@ public class NimbusReactiveJwtDecoderTests {
.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
public void setJwtValidatorWhenGivenNullThrowsIllegalArgumentException() {
assertThatCode(() -> this.decoder.setJwtValidator(null))
@@ -36,7 +36,7 @@ import org.springframework.util.StringUtils;
*/
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;
@@ -43,12 +43,12 @@ import java.util.regex.Pattern;
*/
public class ServerBearerTokenAuthenticationConverter
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;
public Mono<Authentication> convert(ServerWebExchange exchange) {
return Mono.justOrEmpty(token(exchange.getRequest()))
return Mono.fromCallable(() -> token(exchange.getRequest()))
.map(token -> {
if (token.isEmpty()) {
BearerTokenError error = invalidTokenError();
@@ -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");
* 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);
}
// 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
public void resolveWhenNoHeaderIsPresentThenTokenIsNotResolved() {
MockHttpServletRequest request = new MockHttpServletRequest();
@@ -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");
* 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;
import java.util.Base64;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
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.BearerTokenErrorCodes;
import java.util.Base64;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.catchThrowableOfType;
@@ -56,6 +57,17 @@ public class ServerBearerTokenAuthenticationConverterTests {
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
@Test
public void resolveWhenValidHeaderIsEmptyStringThenTokenIsResolved() {
@@ -110,6 +122,17 @@ public class ServerBearerTokenAuthenticationConverterTests {
.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
public void resolveWhenValidHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown() {
MockServerHttpRequest.BaseBuilder<?> request = MockServerHttpRequest
@@ -228,10 +228,15 @@ class DummyRequest extends HttpServletRequestWrapper {
public void setQueryString(String queryString) {
this.queryString = queryString;
}
@Override
public String getServerName() {
return null;
}
}
final class UnsupportedOperationExceptionInvocationHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
throw new UnsupportedOperationException(method + " is not supported");
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-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.
@@ -16,16 +16,17 @@
package org.springframework.security.web.firewall;
import org.springframework.http.HttpMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
/**
* <p>
@@ -66,10 +67,15 @@ import java.util.Set;
* Rejects URLs that contain a URL encoded percent. See
* {@link #setAllowUrlEncodedPercent(boolean)}
* </li>
* <li>
* Rejects hosts that are not allowed. See
* {@link #setAllowedHostnames(Predicate)}
* </li>
* </ul>
*
* @see DefaultHttpFirewall
* @author Rob Winch
* @author Eddú Meléndez
* @since 4.2.4
*/
public class StrictHttpFirewall implements HttpFirewall {
@@ -96,6 +102,8 @@ public class StrictHttpFirewall implements HttpFirewall {
private Set<String> allowedHttpMethods = createDefaultAllowedHttpMethods();
private Predicate<String> allowedHostnames = hostname -> true;
public StrictHttpFirewall() {
urlBlacklistsAddAll(FORBIDDEN_SEMICOLON);
urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH);
@@ -277,6 +285,21 @@ public class StrictHttpFirewall implements HttpFirewall {
}
}
/**
* <p>
* Determines which hostnames should be allowed. The default is to allow any hostname.
* </p>
*
* @param allowedHostnames the predicate for testing hostnames
* @since 5.1.11
*/
public void setAllowedHostnames(Predicate<String> allowedHostnames) {
if (allowedHostnames == null) {
throw new IllegalArgumentException("allowedHostnames cannot be null");
}
this.allowedHostnames = allowedHostnames;
}
private void urlBlacklistsAddAll(Collection<String> values) {
this.encodedUrlBlacklist.addAll(values);
this.decodedUrlBlacklist.addAll(values);
@@ -291,6 +314,7 @@ public class StrictHttpFirewall implements HttpFirewall {
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
rejectForbiddenHttpMethod(request);
rejectedBlacklistedUrls(request);
rejectedUntrustedHosts(request);
if (!isNormalized(request)) {
throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
@@ -332,6 +356,13 @@ public class StrictHttpFirewall implements HttpFirewall {
}
}
private void rejectedUntrustedHosts(HttpServletRequest request) {
String serverName = request.getServerName();
if (serverName != null && !this.allowedHostnames.test(serverName)) {
throw new RequestRejectedException("The request was rejected because the domain " + serverName + " is untrusted.");
}
}
@Override
public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
return new FirewalledResponse(response);
@@ -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");
* 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())
.flatMap( matchResult -> this.authenticationConverter.convert(exchange))
.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,
WebFilterChain chain, Authentication token) {
private Mono<Void> authenticate(ServerWebExchange exchange, WebFilterChain chain, Authentication token) {
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
return this.authenticationManager.authenticate(token)
.switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange))
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
.onAuthenticationFailure(webFilterExchange, e));
.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange));
}
protected Mono<Void> onAuthenticationSuccess(Authentication authentication, WebFilterExchange webFilterExchange) {
@@ -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");
* you may not use this file except in compliance with the License.
@@ -55,6 +55,7 @@ import org.springframework.web.server.WebFilterChain;
* </p>
*
* @author Rob Winch
* @author Parikshit Dutta
* @since 5.0
*/
public class CsrfWebFilter implements WebFilter {
@@ -136,7 +137,7 @@ public class CsrfWebFilter implements WebFilter {
@Override
public Mono<MatchResult> matches(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest())
.map(r -> r.getMethod())
.flatMap(r -> Mono.justOrEmpty(r.getMethod()))
.filter(m -> ALLOWED_METHODS.contains(m))
.flatMap(m -> MatchResult.notMatch())
.switchIfEmpty(MatchResult.match());
@@ -105,7 +105,7 @@ public class LoginPageGeneratingWebFilter implements WebFilter {
+ " </head>\n"
+ " <body>\n"
+ " <div class=\"container\">\n"
+ formLogin(queryParams, csrfTokenHtmlInput)
+ formLogin(queryParams, contextPath, csrfTokenHtmlInput)
+ oauth2LoginLinks(queryParams, contextPath, this.oauth2AuthenticationUrlToClientName)
+ " </div>\n"
+ " </body>\n"
@@ -114,13 +114,13 @@ public class LoginPageGeneratingWebFilter implements WebFilter {
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) {
return "";
}
boolean isError = queryParams.containsKey("error");
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"
+ createError(isError)
+ createLogoutSuccess(isLogoutSuccess)
@@ -67,7 +67,7 @@ public final class AntPathRequestMatcher
/**
* Creates a matcher with the specific pattern which will match all HTTP methods in a
* case insensitive manner.
* case sensitive manner.
*
* @param pattern the ant pattern to use for matching
*/
@@ -76,7 +76,7 @@ public final class AntPathRequestMatcher
}
/**
* Creates a matcher with the supplied pattern and HTTP method in a case insensitive
* Creates a matcher with the supplied pattern and HTTP method in a case sensitive
* manner.
*
* @param pattern the ant pattern to use for matching
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-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.
@@ -29,6 +29,7 @@ import org.springframework.mock.web.MockHttpServletRequest;
/**
* @author Rob Winch
* @author Eddú Meléndez
*/
public class StrictHttpFirewallTests {
public String[] unnormalizedPaths = { "/..", "/./path/", "/path/path/.", "/path/path//.", "./path/../path//.",
@@ -428,4 +429,20 @@ public class StrictHttpFirewallTests {
this.firewall.getFirewalledRequest(request);
}
@Test
public void getFirewalledRequestWhenTrustedDomainThenNoException() {
this.request.addHeader("Host", "example.org");
this.firewall.setAllowedHostnames(hostname -> hostname.equals("example.org"));
assertThatCode(() -> this.firewall.getFirewalledRequest(this.request)).doesNotThrowAnyException();
}
@Test(expected = RequestRejectedException.class)
public void getFirewalledRequestWhenUntrustedDomainThenException() {
this.request.addHeader("Host", "example.org");
this.firewall.setAllowedHostnames(hostname -> hostname.equals("myexample.org"));
this.firewall.getFirewalledRequest(this.request);
}
}
@@ -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");
* you may not use this file except in compliance with the License.
@@ -20,10 +20,14 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;
@@ -33,9 +37,11 @@ import reactor.test.publisher.PublisherProbe;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.mock.web.server.MockServerWebExchange.from;
/**
* @author Rob Winch
* @author Parikshit Dutta
* @since 5.0
*/
@RunWith(MockitoJUnitRunner.class)
@@ -49,10 +55,10 @@ public class CsrfWebFilterTests {
private CsrfWebFilter csrfFilter = new CsrfWebFilter();
private MockServerWebExchange get = MockServerWebExchange.from(
private MockServerWebExchange get = from(
MockServerHttpRequest.get("/"));
private MockServerWebExchange post = MockServerWebExchange.from(
private MockServerWebExchange post = from(
MockServerHttpRequest.post("/"));
@Test
@@ -104,7 +110,7 @@ public class CsrfWebFilterTests {
this.csrfFilter.setCsrfTokenRepository(this.repository);
when(this.repository.loadToken(any()))
.thenReturn(Mono.just(this.token));
this.post = MockServerWebExchange.from(MockServerHttpRequest.post("/")
this.post = from(MockServerHttpRequest.post("/")
.body(this.token.getParameterName() + "="+this.token.getToken()+"INVALID"));
Mono<Void> result = this.csrfFilter.filter(this.post, this.chain);
@@ -125,7 +131,7 @@ public class CsrfWebFilterTests {
.thenReturn(Mono.just(this.token));
when(this.repository.generateToken(any()))
.thenReturn(Mono.just(this.token));
this.post = MockServerWebExchange.from(MockServerHttpRequest.post("/")
this.post = from(MockServerHttpRequest.post("/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(this.token.getParameterName() + "="+this.token.getToken()));
@@ -142,7 +148,7 @@ public class CsrfWebFilterTests {
this.csrfFilter.setCsrfTokenRepository(this.repository);
when(this.repository.loadToken(any()))
.thenReturn(Mono.just(this.token));
this.post = MockServerWebExchange.from(MockServerHttpRequest.post("/")
this.post = from(MockServerHttpRequest.post("/")
.header(this.token.getHeaderName(), this.token.getToken()+"INVALID"));
Mono<Void> result = this.csrfFilter.filter(this.post, this.chain);
@@ -163,7 +169,7 @@ public class CsrfWebFilterTests {
.thenReturn(Mono.just(this.token));
when(this.repository.generateToken(any()))
.thenReturn(Mono.just(this.token));
this.post = MockServerWebExchange.from(MockServerHttpRequest.post("/")
this.post = from(MockServerHttpRequest.post("/")
.header(this.token.getHeaderName(), this.token.getToken()));
Mono<Void> result = this.csrfFilter.filter(this.post, this.chain);
@@ -173,4 +179,14 @@ public class CsrfWebFilterTests {
chainResult.assertWasSubscribed();
}
@Test
// gh-8452
public void matchesRequireCsrfProtectionWhenNonStandardHTTPMethodIsUsed() {
HttpMethod customHttpMethod = HttpMethod.resolve("non-standard-http-method");
MockServerWebExchange nonStandardHttpRequest = from(MockServerHttpRequest.method(customHttpMethod, "/"));
ServerWebExchangeMatcher serverWebExchangeMatcher = CsrfWebFilter.DEFAULT_CSRF_MATCHER;
assertThat(serverWebExchangeMatcher.matches(nonStandardHttpRequest).map(MatchResult::isMatch).block()).isTrue();
}
}
@@ -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\"");
}
}