Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60e1db16ab | |||
| 3a3399d388 | |||
| 0e06882b3f | |||
| 7803089462 | |||
| 58cba234c0 | |||
| b856e90f8b | |||
| 3f7041a8d9 | |||
| ed03fe8154 | |||
| 21c01084ab | |||
| 8922103d12 | |||
| c7727891e3 | |||
| ee4bb21f6a | |||
| 1739a4f339 | |||
| 9d9c8956e3 | |||
| e11f90bb33 | |||
| 0eb7ad597c | |||
| 9c892c1c35 | |||
| c92dd46378 | |||
| f3c28cca77 | |||
| 0f177a9964 | |||
| 65b3584ac6 | |||
| 1d814f95d5 | |||
| 01be7eca6e | |||
| ff7f8f913d | |||
| 76aff9df62 | |||
| d963a13832 | |||
| 01269e239d | |||
| 21f0ccd088 | |||
| b1588c3d73 | |||
| e74ae71382 | |||
| 302da19708 | |||
| 30fbc269b9 | |||
| 27866fb767 | |||
| 006fa5ed70 | |||
| f6f351f419 | |||
| dbe2ef8758 | |||
| a15eb7eecf | |||
| b53cab9204 | |||
| 9925c6a4c0 | |||
| 0de2a51184 | |||
| eaeb419d65 | |||
| 060523292d | |||
| 523cdba8ae | |||
| e4165c4c80 | |||
| 7c622dba33 | |||
| fb8672a31b | |||
| 7d92b7bda7 | |||
| 410c891f75 | |||
| 6bcf499884 | |||
| e1b6a7ba29 | |||
| 8bb69c4514 | |||
| a752919cc9 | |||
| 2c1126c5aa | |||
| a840aa7531 | |||
| 560fb35dc5 | |||
| 6753e1da68 | |||
| 703f1f1e04 | |||
| f0d208667b | |||
| dc7387075c | |||
| c193a06ac5 | |||
| 3e27f6aece | |||
| 4314c335c8 | |||
| c87c5eb888 | |||
| 613ec13e95 | |||
| dabe2bacb0 | |||
| 6afe47f164 | |||
| 766a48c16e | |||
| c2473ed979 | |||
| f91608dcba | |||
| 67a18f564a | |||
| 5a4cfe1226 | |||
| 08f7a97ae7 | |||
| c41aeed6cb | |||
| c0200512a7 | |||
| d5062bb828 | |||
| 898bdeb0fd | |||
| c79cb8eff6 | |||
| d3a3c36ad3 | |||
| c9a8419e22 | |||
| ecb4a5749a | |||
| 362855b8b8 | |||
| 0a56dc4ef5 | |||
| 99db0ca2c5 | |||
| ab34c0308c | |||
| adf3e94c9f | |||
| 521706d496 | |||
| ea19b31133 | |||
| 46fdb250dc | |||
| b500b3ea69 | |||
| 59171434d5 | |||
| 5d18dd6d7d | |||
| 41b0e51dbb | |||
| 310c1148ce | |||
| 93defb2ff2 | |||
| fb7efffad3 | |||
| 6db79b70e6 | |||
| 78a618c260 | |||
| cfc5256fad | |||
| 289b11b873 | |||
| 8dc702c80f | |||
| bd0247adef |
Vendored
+11
-48
@@ -10,6 +10,7 @@ currentBuild.result = SUCCESS
|
||||
|
||||
|
||||
def ARTIFACTORY_CREDENTIALS = usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')
|
||||
def JENKINS_USER='-Duser.name="spring-builds+jenkins"'
|
||||
|
||||
try {
|
||||
parallel check: {
|
||||
@@ -20,15 +21,13 @@ try {
|
||||
try {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
sh "./gradlew clean check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
sh "./gradlew $JENKINS_USER clean check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
|
||||
currentBuild.result = 'FAILED: check'
|
||||
throw e
|
||||
} finally {
|
||||
junit '**/build/test-results/*/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,9 +42,9 @@ try {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
if ("master" == env.BRANCH_NAME) {
|
||||
sh "./gradlew sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
|
||||
sh "./gradlew $JENKINS_USER sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
|
||||
} else {
|
||||
sh "./gradlew sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
|
||||
sh "./gradlew $JENKINS_USER sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +64,7 @@ try {
|
||||
try {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
|
||||
sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
@@ -75,42 +74,6 @@ try {
|
||||
}
|
||||
}
|
||||
},
|
||||
jdk9: {
|
||||
stage('JDK 9') {
|
||||
node {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk9' }"]) {
|
||||
sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: jdk9'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
jdk10: {
|
||||
stage('JDK 10') {
|
||||
node {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk10' }"]) {
|
||||
sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: jdk10'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
jdk11: {
|
||||
stage('JDK 11') {
|
||||
node {
|
||||
@@ -119,7 +82,7 @@ try {
|
||||
try {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) {
|
||||
sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
@@ -137,7 +100,7 @@ try {
|
||||
try {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'openjdk12' }"]) {
|
||||
sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
@@ -156,10 +119,10 @@ try {
|
||||
sh "git clean -dfx"
|
||||
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
|
||||
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
|
||||
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
|
||||
withCredentials([usernamePassword(credentialsId: 'oss-s01-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
sh "./gradlew $JENKINS_USER deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhTokenUsername=$OSSRH_USERNAME -PossrhTokenPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,7 +139,7 @@ try {
|
||||
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
sh "./gradlew $JENKINS_USER deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,7 +154,7 @@ try {
|
||||
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
|
||||
sh "./gradlew $JENKINS_USER deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ Be sure to read the https://docs.spring.io/spring-security/site/docs/current/ref
|
||||
Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation].
|
||||
|
||||
== Quick Start
|
||||
We recommend you visit https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/[Spring Security Reference] and read the "Getting Started" page.
|
||||
See https://docs.spring.io/spring-security/site/docs/5.2.x/reference/html5/#servlet-hello[Hello Spring Security] to get started with a "Hello, World" application.
|
||||
|
||||
== Building from Source
|
||||
Spring Security uses a https://gradle.org[Gradle]-based build system.
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.23.1.RELEASE'
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.23.2.RELEASE'
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
|
||||
classpath 'io.spring.nohttp:nohttp-gradle:0.0.5.RELEASE'
|
||||
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
|
||||
classpath "io.freefair.gradle:aspectj-plugin:4.0.2"
|
||||
}
|
||||
repositories {
|
||||
|
||||
+13
-75
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.builders;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -53,14 +52,12 @@ import org.springframework.web.filter.CorsFilter;
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
final class FilterComparator implements Comparator<Filter>, Serializable {
|
||||
final class FilterOrderRegistration {
|
||||
private static final int INITIAL_ORDER = 100;
|
||||
private static final int ORDER_STEP = 100;
|
||||
private final Map<String, Integer> filterToOrder = new HashMap<>();
|
||||
|
||||
FilterComparator() {
|
||||
FilterOrderRegistration() {
|
||||
Step order = new Step(INITIAL_ORDER, ORDER_STEP);
|
||||
put(ChannelProcessingFilter.class, order.next());
|
||||
put(ConcurrentSessionFilter.class, order.next());
|
||||
@@ -111,78 +108,19 @@ final class FilterComparator implements Comparator<Filter>, Serializable {
|
||||
put(SwitchUserFilter.class, order.next());
|
||||
}
|
||||
|
||||
public int compare(Filter lhs, Filter rhs) {
|
||||
Integer left = getOrder(lhs.getClass());
|
||||
Integer right = getOrder(rhs.getClass());
|
||||
return left - right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a particular {@link Filter} is registered to be sorted
|
||||
*
|
||||
* @param filter
|
||||
* @return
|
||||
*/
|
||||
public boolean isRegistered(Class<? extends Filter> filter) {
|
||||
return getOrder(filter) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link Filter} to exist after a particular {@link Filter} that is
|
||||
* already registered.
|
||||
* Register a {@link Filter} with its specific position. If the {@link Filter} was
|
||||
* already registered before, the position previously defined is not going to be
|
||||
* overriden
|
||||
* @param filter the {@link Filter} to register
|
||||
* @param afterFilter the {@link Filter} that is already registered and that
|
||||
* {@code filter} should be placed after.
|
||||
* @param position the position to associate with the {@link Filter}
|
||||
*/
|
||||
public void registerAfter(Class<? extends Filter> filter,
|
||||
Class<? extends Filter> afterFilter) {
|
||||
Integer position = getOrder(afterFilter);
|
||||
if (position == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot register after unregistered Filter " + afterFilter);
|
||||
}
|
||||
|
||||
put(filter, position + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link Filter} to exist at a particular {@link Filter} position
|
||||
* @param filter the {@link Filter} to register
|
||||
* @param atFilter the {@link Filter} that is already registered and that
|
||||
* {@code filter} should be placed at.
|
||||
*/
|
||||
public void registerAt(Class<? extends Filter> filter,
|
||||
Class<? extends Filter> atFilter) {
|
||||
Integer position = getOrder(atFilter);
|
||||
if (position == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot register after unregistered Filter " + atFilter);
|
||||
}
|
||||
|
||||
put(filter, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link Filter} to exist before a particular {@link Filter} that is
|
||||
* already registered.
|
||||
* @param filter the {@link Filter} to register
|
||||
* @param beforeFilter the {@link Filter} that is already registered and that
|
||||
* {@code filter} should be placed before.
|
||||
*/
|
||||
public void registerBefore(Class<? extends Filter> filter,
|
||||
Class<? extends Filter> beforeFilter) {
|
||||
Integer position = getOrder(beforeFilter);
|
||||
if (position == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot register after unregistered Filter " + beforeFilter);
|
||||
}
|
||||
|
||||
put(filter, position - 1);
|
||||
}
|
||||
|
||||
private void put(Class<? extends Filter> filter, int position) {
|
||||
void put(Class<? extends Filter> filter, int position) {
|
||||
String className = filter.getName();
|
||||
filterToOrder.put(className, position);
|
||||
if (this.filterToOrder.containsKey(className)) {
|
||||
return;
|
||||
}
|
||||
this.filterToOrder.put(className, position);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,7 +130,7 @@ final class FilterComparator implements Comparator<Filter>, Serializable {
|
||||
* @param clazz the {@link Filter} class to determine the sort order
|
||||
* @return the sort order or null if not defined
|
||||
*/
|
||||
private Integer getOrder(Class<?> clazz) {
|
||||
Integer getOrder(Class<?> clazz) {
|
||||
while (clazz != null) {
|
||||
Integer result = filterToOrder.get(clazz.getName());
|
||||
if (result != null) {
|
||||
+65
-17
@@ -16,6 +16,8 @@
|
||||
package org.springframework.security.config.annotation.web.builders;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
@@ -78,10 +80,16 @@ import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
@@ -125,9 +133,9 @@ public final class HttpSecurity extends
|
||||
implements SecurityBuilder<DefaultSecurityFilterChain>,
|
||||
HttpSecurityBuilder<HttpSecurity> {
|
||||
private final RequestMatcherConfigurer requestMatcherConfigurer;
|
||||
private List<Filter> filters = new ArrayList<>();
|
||||
private List<OrderedFilter> filters = new ArrayList<>();
|
||||
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
|
||||
private FilterComparator comparator = new FilterComparator();
|
||||
private FilterOrderRegistration filterOrders = new FilterOrderRegistration();
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
@@ -2528,8 +2536,12 @@ public final class HttpSecurity extends
|
||||
|
||||
@Override
|
||||
protected DefaultSecurityFilterChain performBuild() {
|
||||
filters.sort(comparator);
|
||||
return new DefaultSecurityFilterChain(requestMatcher, filters);
|
||||
this.filters.sort(OrderComparator.INSTANCE);
|
||||
List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
|
||||
for (Filter filter : this.filters) {
|
||||
sortedFilters.add(((OrderedFilter) filter).filter);
|
||||
}
|
||||
return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2570,8 +2582,7 @@ public final class HttpSecurity extends
|
||||
* .servlet.Filter, java.lang.Class)
|
||||
*/
|
||||
public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
|
||||
comparator.registerAfter(filter.getClass(), afterFilter);
|
||||
return addFilter(filter);
|
||||
return addFilterAtOffsetOf(filter, 1, afterFilter);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2583,8 +2594,14 @@ public final class HttpSecurity extends
|
||||
*/
|
||||
public HttpSecurity addFilterBefore(Filter filter,
|
||||
Class<? extends Filter> beforeFilter) {
|
||||
comparator.registerBefore(filter.getClass(), beforeFilter);
|
||||
return addFilter(filter);
|
||||
return addFilterAtOffsetOf(filter, -1, beforeFilter);
|
||||
}
|
||||
|
||||
private HttpSecurity addFilterAtOffsetOf(Filter filter, int offset, Class<? extends Filter> registeredFilter) {
|
||||
int order = this.filterOrders.getOrder(registeredFilter) + offset;
|
||||
this.filters.add(new OrderedFilter(filter, order));
|
||||
this.filterOrders.put(filter.getClass(), order);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2595,14 +2612,12 @@ public final class HttpSecurity extends
|
||||
* servlet.Filter)
|
||||
*/
|
||||
public HttpSecurity addFilter(Filter filter) {
|
||||
Class<? extends Filter> filterClass = filter.getClass();
|
||||
if (!comparator.isRegistered(filterClass)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The Filter class "
|
||||
+ filterClass.getName()
|
||||
+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
|
||||
Integer order = this.filterOrders.getOrder(filter.getClass());
|
||||
if (order == null) {
|
||||
throw new IllegalArgumentException("The Filter class " + filter.getClass().getName()
|
||||
+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
|
||||
}
|
||||
this.filters.add(filter);
|
||||
this.filters.add(new OrderedFilter(filter, order));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -2626,8 +2641,7 @@ public final class HttpSecurity extends
|
||||
* @return the {@link HttpSecurity} for further customizations
|
||||
*/
|
||||
public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {
|
||||
this.comparator.registerAt(filter.getClass(), atFilter);
|
||||
return addFilter(filter);
|
||||
return addFilterAtOffsetOf(filter, 0, atFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3023,4 +3037,38 @@ public final class HttpSecurity extends
|
||||
}
|
||||
return apply(configurer);
|
||||
}
|
||||
|
||||
/*
|
||||
* A Filter that implements Ordered to be sorted. After sorting occurs, the original
|
||||
* filter is what is used by FilterChainProxy
|
||||
*/
|
||||
private static final class OrderedFilter implements Ordered, Filter {
|
||||
|
||||
private final Filter filter;
|
||||
|
||||
private final int order;
|
||||
|
||||
private OrderedFilter(Filter filter, int order) {
|
||||
this.filter = filter;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
this.filter.doFilter(servletRequest, servletResponse, filterChain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OrderedFilter{" + "filter=" + this.filter + ", order=" + this.order + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -100,7 +100,10 @@ public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
if (loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) {
|
||||
loginPageGeneratingFilter = postProcess(loginPageGeneratingFilter);
|
||||
http.addFilter(loginPageGeneratingFilter);
|
||||
http.addFilter(this.logoutPageGeneratingFilter);
|
||||
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
|
||||
if (logoutConfigurer != null) {
|
||||
http.addFilter(this.logoutPageGeneratingFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -57,6 +57,7 @@ import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequest
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
@@ -686,10 +687,12 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> exten
|
||||
this.authorizationEndpointConfig.authorizationRequestBaseUri :
|
||||
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
|
||||
Map<String, String> loginUrlToClientName = new HashMap<>();
|
||||
clientRegistrations.forEach(registration -> loginUrlToClientName.put(
|
||||
authorizationRequestBaseUri + "/" + registration.getRegistrationId(),
|
||||
registration.getClientName()));
|
||||
|
||||
clientRegistrations.forEach((registration) -> {
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(registration.getAuthorizationGrantType())) {
|
||||
String authorizationRequestUri = authorizationRequestBaseUri + "/" + registration.getRegistrationId();
|
||||
loginUrlToClientName.put(authorizationRequestUri, registration.getClientName());
|
||||
}
|
||||
});
|
||||
return loginUrlToClientName;
|
||||
}
|
||||
|
||||
|
||||
+11
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -52,6 +52,8 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition
|
||||
private static final String ATT_REF = "ref";
|
||||
private static final String ATT_ERASE_CREDENTIALS = "erase-credentials";
|
||||
|
||||
private static final String AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME = "defaultAuthenticationEventPublisher";
|
||||
|
||||
public BeanDefinition parse(Element element, ParserContext pc) {
|
||||
String id = element.getAttribute("id");
|
||||
|
||||
@@ -124,12 +126,14 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition
|
||||
false);
|
||||
}
|
||||
|
||||
// Add the default event publisher
|
||||
BeanDefinition publisher = new RootBeanDefinition(
|
||||
DefaultAuthenticationEventPublisher.class);
|
||||
String pubId = pc.getReaderContext().generateBeanName(publisher);
|
||||
pc.registerBeanComponent(new BeanComponentDefinition(publisher, pubId));
|
||||
providerManagerBldr.addPropertyReference("authenticationEventPublisher", pubId);
|
||||
if (!pc.getRegistry().containsBeanDefinition(AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME)) {
|
||||
// Add the default event publisher to the context
|
||||
BeanDefinition publisher = new RootBeanDefinition(DefaultAuthenticationEventPublisher.class);
|
||||
pc.registerBeanComponent(new BeanComponentDefinition(publisher, AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME));
|
||||
}
|
||||
|
||||
providerManagerBldr.addPropertyReference("authenticationEventPublisher",
|
||||
AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME);
|
||||
|
||||
pc.registerBeanComponent(new BeanComponentDefinition(providerManagerBldr
|
||||
.getBeanDefinition(), id));
|
||||
|
||||
+36
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -33,6 +33,7 @@ import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
|
||||
import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -85,6 +86,7 @@ import org.springframework.security.oauth2.client.web.server.ServerOAuth2Authori
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository;
|
||||
import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
@@ -112,6 +114,7 @@ import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
|
||||
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
|
||||
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
|
||||
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
|
||||
@@ -1285,7 +1288,11 @@ public class ServerHttpSecurity {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, String> result = new HashMap<>();
|
||||
registrations.iterator().forEachRemaining(r -> result.put("/oauth2/authorization/" + r.getRegistrationId(), r.getClientName()));
|
||||
registrations.iterator().forEachRemaining((r) -> {
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(r.getAuthorizationGrantType())) {
|
||||
result.put("/oauth2/authorization/" + r.getRegistrationId(), r.getClientName());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2960,11 +2967,17 @@ public class ServerHttpSecurity {
|
||||
* @see #httpBasic()
|
||||
*/
|
||||
public class HttpBasicSpec {
|
||||
|
||||
private final ServerWebExchangeMatcher xhrMatcher = (exchange) -> Mono.just(exchange.getRequest().getHeaders())
|
||||
.filter((h) -> h.getOrEmpty("X-Requested-With").contains("XMLHttpRequest"))
|
||||
.flatMap((h) -> ServerWebExchangeMatcher.MatchResult.match())
|
||||
.switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
|
||||
|
||||
private ReactiveAuthenticationManager authenticationManager;
|
||||
|
||||
private ServerSecurityContextRepository securityContextRepository;
|
||||
|
||||
private ServerAuthenticationEntryPoint entryPoint = new HttpBasicServerAuthenticationEntryPoint();
|
||||
private ServerAuthenticationEntryPoint entryPoint;
|
||||
|
||||
/**
|
||||
* The {@link ReactiveAuthenticationManager} used to authenticate. Defaults to
|
||||
@@ -3029,7 +3042,13 @@ public class ServerHttpSecurity {
|
||||
MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML,
|
||||
MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML);
|
||||
restMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
|
||||
ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(restMatcher, this.entryPoint));
|
||||
ServerWebExchangeMatcher notHtmlMatcher = new NegatedServerWebExchangeMatcher(
|
||||
new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML));
|
||||
ServerWebExchangeMatcher restNotHtmlMatcher = new AndServerWebExchangeMatcher(
|
||||
Arrays.asList(notHtmlMatcher, restMatcher));
|
||||
ServerWebExchangeMatcher preferredMatcher = new OrServerWebExchangeMatcher(
|
||||
Arrays.asList(this.xhrMatcher, restNotHtmlMatcher));
|
||||
ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(preferredMatcher, this.entryPoint));
|
||||
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(
|
||||
this.authenticationManager);
|
||||
authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(this.entryPoint));
|
||||
@@ -3038,7 +3057,15 @@ public class ServerHttpSecurity {
|
||||
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC);
|
||||
}
|
||||
|
||||
private HttpBasicSpec() {}
|
||||
private HttpBasicSpec() {
|
||||
List<DelegateEntry> entryPoints = new ArrayList<>();
|
||||
entryPoints
|
||||
.add(new DelegateEntry(this.xhrMatcher, new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)));
|
||||
DelegatingServerAuthenticationEntryPoint defaultEntryPoint = new DelegatingServerAuthenticationEntryPoint(
|
||||
entryPoints);
|
||||
defaultEntryPoint.setDefaultEntryPoint(new HttpBasicServerAuthenticationEntryPoint());
|
||||
this.entryPoint = defaultEntryPoint;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3239,7 +3266,10 @@ public class ServerHttpSecurity {
|
||||
}
|
||||
if (loginPage != null) {
|
||||
http.addFilterAt(loginPage, SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);
|
||||
http.addFilterAt(new LogoutPageGeneratingWebFilter(), SecurityWebFiltersOrder.LOGOUT_PAGE_GENERATING);
|
||||
if (http.logout != null) {
|
||||
http.addFilterAt(new LogoutPageGeneratingWebFilter(),
|
||||
SecurityWebFiltersOrder.LOGOUT_PAGE_GENERATING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.config.annotation.web.builders;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class FilterOrderRegistrationTests {
|
||||
|
||||
private final FilterOrderRegistration filterOrderRegistration = new FilterOrderRegistration();
|
||||
|
||||
@Test
|
||||
public void putWhenNewFilterThenInsertCorrect() {
|
||||
int position = 153;
|
||||
this.filterOrderRegistration.put(MyFilter.class, position);
|
||||
Integer order = this.filterOrderRegistration.getOrder(MyFilter.class);
|
||||
assertThat(order).isEqualTo(position);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putWhenCustomFilterAlreadyExistsThenDoesNotOverride() {
|
||||
int position = 160;
|
||||
this.filterOrderRegistration.put(MyFilter.class, position);
|
||||
this.filterOrderRegistration.put(MyFilter.class, 173);
|
||||
Integer order = this.filterOrderRegistration.getOrder(MyFilter.class);
|
||||
assertThat(order).isEqualTo(position);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putWhenPredefinedFilterThenDoesNotOverride() {
|
||||
int position = 100;
|
||||
Integer predefinedFilterOrderBefore = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class);
|
||||
this.filterOrderRegistration.put(MyFilter.class, position);
|
||||
Integer myFilterOrder = this.filterOrderRegistration.getOrder(MyFilter.class);
|
||||
Integer predefinedFilterOrderAfter = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class);
|
||||
assertThat(myFilterOrder).isEqualTo(position);
|
||||
assertThat(predefinedFilterOrderAfter).isEqualTo(predefinedFilterOrderBefore).isEqualTo(position);
|
||||
}
|
||||
|
||||
static class MyFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.config.annotation.web.builders;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
import org.assertj.core.api.ListAssert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
|
||||
import org.springframework.security.web.header.HeaderWriterFilter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class HttpSecurityAddFilterTest {
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
|
||||
@Test
|
||||
public void addFilterAfterWhenSameFilterDifferentPlacesThenOrderCorrect() {
|
||||
this.spring.register(MyFilterMultipleAfterConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class,
|
||||
ExceptionTranslationFilter.class, MyFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterBeforeWhenSameFilterDifferentPlacesThenOrderCorrect() {
|
||||
this.spring.register(MyFilterMultipleBeforeConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(MyFilter.class, WebAsyncManagerIntegrationFilter.class, MyFilter.class,
|
||||
ExceptionTranslationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterAtWhenSameFilterDifferentPlacesThenOrderCorrect() {
|
||||
this.spring.register(MyFilterMultipleAtConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(MyFilter.class, WebAsyncManagerIntegrationFilter.class, MyFilter.class,
|
||||
ExceptionTranslationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterAfterWhenAfterCustomFilterThenOrderCorrect() {
|
||||
this.spring.register(MyOtherFilterRelativeToMyFilterAfterConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class,
|
||||
MyOtherFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterBeforeWhenBeforeCustomFilterThenOrderCorrect() {
|
||||
this.spring.register(MyOtherFilterRelativeToMyFilterBeforeConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(MyOtherFilter.class, MyFilter.class,
|
||||
WebAsyncManagerIntegrationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterAtWhenAtCustomFilterThenOrderCorrect() {
|
||||
this.spring.register(MyOtherFilterRelativeToMyFilterAtConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class,
|
||||
MyOtherFilter.class, SecurityContextPersistenceFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterBeforeWhenCustomFilterDifferentPlacesThenOrderCorrect() {
|
||||
this.spring.register(MyOtherFilterBeforeToMyFilterMultipleAfterConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyOtherFilter.class,
|
||||
MyFilter.class, ExceptionTranslationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addFilterBeforeAndAfterWhenCustomFiltersDifferentPlacesThenOrderCorrect() {
|
||||
this.spring.register(MyAnotherFilterRelativeToMyCustomFiltersMultipleConfig.class).autowire();
|
||||
|
||||
assertThatFilters().containsSubsequence(HeaderWriterFilter.class, MyFilter.class, MyOtherFilter.class,
|
||||
MyOtherFilter.class, MyAnotherFilter.class, MyFilter.class, ExceptionTranslationFilter.class);
|
||||
}
|
||||
|
||||
private ListAssert<Class<?>> assertThatFilters() {
|
||||
FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class);
|
||||
List<Class<?>> filters = filterChain.getFilters("/").stream().map(Object::getClass)
|
||||
.collect(Collectors.toList());
|
||||
return assertThat(filters);
|
||||
}
|
||||
|
||||
public static class MyFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyOtherFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyAnotherFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyFilterMultipleAfterConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
|
||||
.addFilterAfter(new MyFilter(), ExceptionTranslationFilter.class);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyFilterMultipleBeforeConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterBefore(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
|
||||
.addFilterBefore(new MyFilter(), ExceptionTranslationFilter.class);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyFilterMultipleAtConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterAt(new MyFilter(), ChannelProcessingFilter.class)
|
||||
.addFilterAt(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyOtherFilterRelativeToMyFilterAfterConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
|
||||
.addFilterAfter(new MyOtherFilter(), MyFilter.class);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyOtherFilterRelativeToMyFilterBeforeConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterBefore(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
|
||||
.addFilterBefore(new MyOtherFilter(), MyFilter.class);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyOtherFilterRelativeToMyFilterAtConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterAt(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
|
||||
.addFilterAt(new MyOtherFilter(), MyFilter.class);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyOtherFilterBeforeToMyFilterMultipleAfterConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
|
||||
.addFilterAfter(new MyFilter(), ExceptionTranslationFilter.class)
|
||||
.addFilterBefore(new MyOtherFilter(), MyFilter.class);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class MyAnotherFilterRelativeToMyCustomFiltersMultipleConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.addFilterAfter(new MyFilter(), HeaderWriterFilter.class)
|
||||
.addFilterBefore(new MyOtherFilter(), ExceptionTranslationFilter.class)
|
||||
.addFilterAfter(new MyOtherFilter(), MyFilter.class)
|
||||
.addFilterAt(new MyAnotherFilter(), MyOtherFilter.class)
|
||||
.addFilterAfter(new MyFilter(), MyAnotherFilter.class);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+51
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -44,11 +44,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultLoginPageConfigurer}
|
||||
@@ -217,6 +220,18 @@ public class DefaultLoginPageConfigurerTests {
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formLoginWhenLogoutEnabledThenCreatesDefaultLogoutPage() throws Exception {
|
||||
this.spring.register(DefaultLogoutPageConfig.class).autowire();
|
||||
this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formLoginWhenLogoutDisabledThenDefaultLogoutPageDoesNotExist() throws Exception {
|
||||
this.spring.register(LogoutDisabledConfig.class).autowire();
|
||||
this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class DefaultLoginPageConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
@@ -552,6 +567,41 @@ public class DefaultLoginPageConfigurerTests {
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class DefaultLogoutPageConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class LogoutDisabledConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults())
|
||||
.logout((logout) -> logout
|
||||
.disable()
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ReflectingObjectPostProcessor implements ObjectPostProcessor<Object> {
|
||||
@Override
|
||||
public <O> O postProcess(O object) {
|
||||
|
||||
+35
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -116,6 +116,11 @@ public class OAuth2LoginConfigurerTests {
|
||||
.getBuilder("github").clientId("clientId").clientSecret("clientSecret")
|
||||
.build();
|
||||
|
||||
// @formatter:off
|
||||
private static final ClientRegistration CLIENT_CREDENTIALS_REGISTRATION = TestClientRegistrations.clientCredentials()
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
@@ -431,6 +436,18 @@ public class OAuth2LoginConfigurerTests {
|
||||
assertThat(this.response.getRedirectedUrl()).doesNotMatch("http://localhost/oauth2/authorization/google");
|
||||
}
|
||||
|
||||
// gh-9457
|
||||
@Test
|
||||
public void oauth2LoginWithOneAuthorizationCodeClientAndOtherClientsConfiguredThenRedirectForAuthorization()
|
||||
throws Exception {
|
||||
loadConfig(OAuth2LoginConfigAuthorizationCodeClientAndOtherClients.class);
|
||||
String requestUri = "/";
|
||||
this.request = new MockHttpServletRequest("GET", requestUri);
|
||||
this.request.setServletPath(requestUri);
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
|
||||
assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oauth2LoginWithCustomLoginPageThenRedirectCustomLoginPage() throws Exception {
|
||||
loadConfig(OAuth2LoginConfigCustomLoginPage.class);
|
||||
@@ -801,6 +818,23 @@ public class OAuth2LoginConfigurerTests {
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class OAuth2LoginConfigAuthorizationCodeClientAndOtherClients extends CommonWebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.oauth2Login()
|
||||
.clientRegistrationRepository(
|
||||
new InMemoryClientRegistrationRepository(
|
||||
GOOGLE_CLIENT_REGISTRATION, CLIENT_CREDENTIALS_REGISTRATION));
|
||||
// @formatter:on
|
||||
super.configure(http);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class OAuth2LoginConfigCustomLoginPage extends CommonWebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
|
||||
+26
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2021 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,6 +20,7 @@ import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.ProviderManager;
|
||||
@@ -49,6 +50,18 @@ public class AuthenticationManagerBeanDefinitionParserTests {
|
||||
+ " </user-service>"
|
||||
+ " </authentication-provider>"
|
||||
+ "</authentication-manager>";
|
||||
|
||||
// Issue #7282
|
||||
// @formatter:off
|
||||
private static final String CONTEXT_MULTI = "<authentication-manager id='amSecondary'>"
|
||||
+ " <authentication-provider>"
|
||||
+ " <user-service>"
|
||||
+ " <user name='john' password='{noop}doe' authorities='ROLE_C,ROLE_D' />"
|
||||
+ " </user-service>"
|
||||
+ " </authentication-provider>"
|
||||
+ "</authentication-manager>";
|
||||
// @formatter:on
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
|
||||
@@ -60,6 +73,18 @@ public class AuthenticationManagerBeanDefinitionParserTests {
|
||||
assertThat(context.getBeansOfType(AuthenticationProvider.class)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eventPublishersAreRegisteredAsTopLevelBeans() {
|
||||
ConfigurableApplicationContext context = this.spring.context(CONTEXT).getContext();
|
||||
assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlyOneEventPublisherIsRegisteredForMultipleAuthenticationManagers() {
|
||||
ConfigurableApplicationContext context = this.spring.context(CONTEXT + '\n' + CONTEXT_MULTI).getContext();
|
||||
assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eventsArePublishedByDefault() throws Exception {
|
||||
ConfigurableApplicationContext appContext = this.spring.context(CONTEXT)
|
||||
|
||||
+21
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -25,7 +25,10 @@ import org.springframework.security.web.server.context.WebSessionServerSecurityC
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
@@ -167,7 +170,8 @@ public class LogoutSpecTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logoutWhenDisabledThenPostToLogoutDoesNothing() {
|
||||
public void logoutWhenDisabledThenDefaultLogoutPageDoesNotExist() {
|
||||
// @formatter:off
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.authorizeExchange()
|
||||
.anyExchange().authenticated()
|
||||
@@ -177,7 +181,7 @@ public class LogoutSpecTests {
|
||||
.build();
|
||||
|
||||
WebTestClient webTestClient = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.bindToControllerAndWebFilters(HomeController.class, securityWebFilter)
|
||||
.build();
|
||||
|
||||
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
|
||||
@@ -191,15 +195,10 @@ public class LogoutSpecTests {
|
||||
.username("user")
|
||||
.password("password")
|
||||
.submit(FormLoginTests.HomePage.class);
|
||||
|
||||
// @formatter:on
|
||||
homePage.assertAt();
|
||||
|
||||
FormLoginTests.DefaultLogoutPage.to(driver)
|
||||
.assertAt()
|
||||
.logout();
|
||||
|
||||
homePage
|
||||
.assertAt();
|
||||
FormLoginTests.DefaultLogoutPage.to(driver);
|
||||
assertThat(driver.getPageSource()).isEmpty();
|
||||
}
|
||||
|
||||
|
||||
@@ -243,4 +242,15 @@ public class LogoutSpecTests {
|
||||
FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class)
|
||||
.assertAt();
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class HomeController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String ok() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+32
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -136,6 +136,11 @@ public class OAuth2LoginTests {
|
||||
.clientSecret("secret")
|
||||
.build();
|
||||
|
||||
// @formatter:off
|
||||
private static ClientRegistration clientCredentials = TestClientRegistrations.clientCredentials()
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
@Autowired
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
if (context.getBeanNamesForType(WebHandler.class).length > 0) {
|
||||
@@ -214,6 +219,32 @@ public class OAuth2LoginTests {
|
||||
}
|
||||
}
|
||||
|
||||
// gh-9457
|
||||
@Test
|
||||
public void defaultLoginPageWithAuthorizationCodeAndClientCredentialsClientRegistrationThenRedirect() {
|
||||
this.spring.register(OAuth2LoginWithAuthorizationCodeAndClientCredentialsClientRegistration.class).autowire();
|
||||
// @formatter:off
|
||||
WebTestClient webTestClient = WebTestClientBuilder
|
||||
.bindToWebFilters(new GitHubWebFilter(), this.springSecurity)
|
||||
.build();
|
||||
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
|
||||
.webTestClientSetup(webTestClient)
|
||||
.build();
|
||||
// @formatter:on
|
||||
driver.get("http://localhost/");
|
||||
assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize");
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
static class OAuth2LoginWithAuthorizationCodeAndClientCredentialsClientRegistration {
|
||||
|
||||
@Bean
|
||||
InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
|
||||
return new InMemoryReactiveClientRegistrationRepository(github, clientCredentials);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oauth2AuthorizeWhenCustomObjectsThenUsed() {
|
||||
this.spring.register(OAuth2LoginWithSingleClientRegistrations.class,
|
||||
|
||||
+23
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -41,6 +41,7 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
|
||||
@@ -48,6 +49,8 @@ import org.springframework.security.oauth2.client.web.server.authentication.OAut
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
|
||||
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
|
||||
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
|
||||
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
|
||||
@@ -184,6 +187,25 @@ public class ServerHttpSecurityTests {
|
||||
.expectBody().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWhenXHRRequestThenUnauthorized() {
|
||||
ServerAuthenticationEntryPoint authenticationEntryPoint = spy(
|
||||
new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED));
|
||||
this.http.httpBasic().authenticationEntryPoint(authenticationEntryPoint);
|
||||
this.http.authorizeExchange().anyExchange().authenticated();
|
||||
WebTestClient client = buildClient();
|
||||
// @formatter:off
|
||||
client.get().uri("/")
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectHeader().doesNotExist("WWW-Authenticate")
|
||||
.expectHeader().valueMatches(HttpHeaders.CACHE_CONTROL, ".+")
|
||||
.expectBody().isEmpty();
|
||||
// @formatter:on
|
||||
verify(authenticationEntryPoint).commence(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenServerWebExchangeFromContextThenFound() {
|
||||
SecurityWebFilterChain filter = this.http.build();
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2016 the original author or authors.
|
||||
* Copyright 2010-2021 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.
|
||||
@@ -287,7 +287,7 @@ public abstract class AbstractJaasAuthenticationProvider
|
||||
* subclasses for different functionality
|
||||
*
|
||||
* @param token The authentication token being processed
|
||||
* @param ase The excetion that caused the authentication failure
|
||||
* @param ase The exception that caused the authentication failure
|
||||
*/
|
||||
protected void publishFailureEvent(UsernamePasswordAuthenticationToken token,
|
||||
AuthenticationException ase) {
|
||||
|
||||
+1
-1
@@ -247,7 +247,7 @@ public class JaasAuthenticationProvider extends AbstractJaasAuthenticationProvid
|
||||
* subclasses for different functionality
|
||||
*
|
||||
* @param token The authentication token being processed
|
||||
* @param ase The excetion that caused the authentication failure
|
||||
* @param ase The exception that caused the authentication failure
|
||||
*/
|
||||
@Override
|
||||
protected void publishFailureEvent(UsernamePasswordAuthenticationToken token,
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -135,7 +135,7 @@ public class DelegatingPasswordEncoderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentExcetion() {
|
||||
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.passwordEncoder.matches(this.rawPassword, "{bcrypt" + this.rawPassword))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("There is no PasswordEncoder mapped for the id \"null\"");
|
||||
|
||||
@@ -15,6 +15,7 @@ remotes {
|
||||
docs {
|
||||
retryCount = 5 // retry 5 times (default is 0)
|
||||
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
|
||||
host = "docs-ip.spring.io"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -510,7 +510,7 @@ ReactiveJwtDecoder jwtDecoder() {
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
By default, Resource Server configures a clock skew of 30 seconds.
|
||||
By default, Resource Server configures a clock skew of 60 seconds.
|
||||
|
||||
[[webflux-oauth2resourceserver-validation-custom]]
|
||||
==== Configuring a Custom Validator
|
||||
|
||||
@@ -100,3 +100,35 @@ This is an example of the use of the `AuthenticatedVoter` which we will see in t
|
||||
It uses an `AuthenticationTrustResolver` to process this particular configuration attribute and grant access to anonymous users.
|
||||
The `AuthenticatedVoter` approach is more powerful, since it allows you to differentiate between anonymous, remember-me and fully-authenticated users.
|
||||
If you don't need this functionality though, then you can stick with `ROLE_ANONYMOUS`, which will be processed by Spring Security's standard `RoleVoter`.
|
||||
|
||||
[[anonymous-auth-mvc-controller]]
|
||||
=== Getting Anonymous Authentications with Spring MVC
|
||||
|
||||
https://docs.spring.io/spring-framework/docs/5.2.x/spring-framework-reference/web.html#mvc-ann-arguments[Spring MVC resolves parameters of type `Principal`] using its own argument resolver.
|
||||
|
||||
This means that a construct like this one:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@GetMapping("/")
|
||||
public String method(Authentication authentication) {
|
||||
if (authentication instanceof AnonymousAuthenticationToken) {
|
||||
return "anonymous";
|
||||
} else {
|
||||
return "not anonymous";
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
will always return "not anonymous", even for anonymous requests.
|
||||
The reason is that Spring MVC resolves the parameter using `HttpServletRequest#getPrincipal`, which is `null` when the request is anonymous.
|
||||
|
||||
If you'd like to obtain the `Authentication` in anonymous requests, use `@CurrentSecurityContext` instead:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@GetMapping("/")
|
||||
public String method(@CurrentSecurityContext SecurityContext context) {
|
||||
return context.getAuthentication().getName();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -531,7 +531,7 @@ JwtDecoder jwtDecoder() {
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
By default, Resource Server configures a clock skew of 30 seconds.
|
||||
By default, Resource Server configures a clock skew of 60 seconds.
|
||||
|
||||
[[oauth2resourceserver-jwt-validation-custom]]
|
||||
==== Configuring a Custom Validator
|
||||
@@ -1375,7 +1375,7 @@ Next, we can construct a `JWTProcessor`:
|
||||
JWTProcessor jwtProcessor(JWTClaimSetJWSKeySelector keySelector) {
|
||||
ConfigurableJWTProcessor<SecurityContext> jwtProcessor =
|
||||
new DefaultJWTProcessor();
|
||||
jwtProcessor.setJWTClaimSetJWSKeySelector(keySelector);
|
||||
jwtProcessor.setJWTClaimsSetAwareJWSKeySelector(keySelector);
|
||||
return jwtProcessor;
|
||||
}
|
||||
----
|
||||
@@ -1422,9 +1422,9 @@ Now that we have a tenant-aware processor and a tenant-aware validator, we can p
|
||||
----
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder(JWTProcessor jwtProcessor, OAuth2TokenValidator<Jwt> jwtValidator) {
|
||||
NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor);
|
||||
NimbusJwtDecoder decoder = new NimbusJwtDecoder(jwtProcessor);
|
||||
OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>
|
||||
(JwtValidators.createDefault(), this.jwtValidator);
|
||||
(JwtValidators.createDefault(), jwtValidator);
|
||||
decoder.setJwtValidator(validator);
|
||||
return decoder;
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
aspectjVersion=1.9.6
|
||||
gaeVersion=1.9.86
|
||||
aspectjVersion=1.9.7
|
||||
gaeVersion=1.9.93
|
||||
springBootVersion=2.2.13.RELEASE
|
||||
version=5.2.9.RELEASE
|
||||
version=5.2.15.RELEASE
|
||||
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
if (!project.hasProperty('reactorVersion')) {
|
||||
ext.reactorVersion = 'Dysprosium-SR16'
|
||||
ext.reactorVersion = 'Dysprosium-SR25'
|
||||
}
|
||||
|
||||
if (!project.hasProperty('springVersion')) {
|
||||
ext.springVersion = '5.2.12.RELEASE'
|
||||
ext.springVersion = '5.2.19.RELEASE'
|
||||
}
|
||||
|
||||
if (!project.hasProperty('springDataVersion')) {
|
||||
ext.springDataVersion = 'Moore-SR12'
|
||||
ext.springDataVersion = 'Moore-SR13'
|
||||
}
|
||||
|
||||
ext.rsocketVersion = '1.0.3'
|
||||
ext.rsocketVersion = '1.0.5'
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
@@ -40,15 +40,15 @@ dependencyManagement {
|
||||
dependencies {
|
||||
dependency 'antlr:antlr:2.7.7'
|
||||
dependency 'asm:asm:3.1'
|
||||
dependency 'ch.qos.logback:logback-classic:1.2.3'
|
||||
dependency 'ch.qos.logback:logback-core:1.2.3'
|
||||
dependency 'ch.qos.logback:logback-classic:1.2.9'
|
||||
dependency 'ch.qos.logback:logback-core:1.2.9'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.5'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-core:2.10.5'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.5.1'
|
||||
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:$gaeVersion'
|
||||
dependency 'com.google.appengine:appengine-api-labs:$gaeVersion'
|
||||
dependency 'com.google.appengine:appengine-api-labs:1.9.88'
|
||||
dependency 'com.google.appengine:appengine-api-stubs:$gaeVersion'
|
||||
dependency 'com.google.appengine:appengine-testing:$gaeVersion'
|
||||
dependency 'com.google.appengine:appengine:$gaeVersion'
|
||||
@@ -61,7 +61,7 @@ dependencyManagement {
|
||||
dependency 'com.squareup.okhttp3:okhttp:3.14.9'
|
||||
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.3'
|
||||
dependency 'com.sun.xml.bind:jaxb-impl:2.3.5'
|
||||
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'
|
||||
@@ -72,7 +72,7 @@ dependencyManagement {
|
||||
dependency 'commons-lang:commons-lang:2.6'
|
||||
dependency 'commons-logging:commons-logging:1.2'
|
||||
dependency 'dom4j:dom4j:1.6.1'
|
||||
dependency 'io.projectreactor.tools:blockhound:1.0.4.RELEASE'
|
||||
dependency 'io.projectreactor.tools:blockhound:1.0.6.RELEASE'
|
||||
dependency "io.rsocket:rsocket-core:${rsocketVersion}"
|
||||
dependency "io.rsocket:rsocket-transport-netty:${rsocketVersion}"
|
||||
dependency 'javax.activation:activation:1.1.1'
|
||||
@@ -91,7 +91,7 @@ dependencyManagement {
|
||||
dependency 'net.jcip:jcip-annotations:1.0'
|
||||
dependency 'net.minidev:accessors-smart:1.2'
|
||||
dependency 'net.minidev:json-smart:2.3'
|
||||
dependency 'net.sf.ehcache:ehcache:2.10.6'
|
||||
dependency 'net.sf.ehcache:ehcache:2.10.9.2'
|
||||
dependency 'net.sourceforge.htmlunit:htmlunit:2.36.0'
|
||||
dependency 'net.sourceforge.htmlunit:neko-htmlunit:2.34.0'
|
||||
dependency 'net.sourceforge.nekohtml:nekohtml:1.9.22'
|
||||
@@ -139,40 +139,40 @@ dependencyManagement {
|
||||
dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15'
|
||||
dependency 'org.apache.directory.shared:shared-ldap:0.9.15'
|
||||
dependency 'org.apache.httpcomponents:httpclient:4.5.13'
|
||||
dependency 'org.apache.httpcomponents:httpcore:4.4.8'
|
||||
dependency 'org.apache.httpcomponents:httpmime:4.5.3'
|
||||
dependency 'org.apache.httpcomponents:httpmime:4.5.13'
|
||||
dependency 'org.apache.httpcomponents:httpcore:4.4.15'
|
||||
dependency 'org.apache.mina:mina-core:2.0.0-M6'
|
||||
dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5'
|
||||
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
|
||||
dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.43'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.43'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.43'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.43'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.57'
|
||||
dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.43'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.56'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.56'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.56'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.56'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.73'
|
||||
dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.56'
|
||||
dependency "org.aspectj:aspectjrt:$aspectjVersion"
|
||||
dependency "org.aspectj:aspectjtools:$aspectjVersion"
|
||||
dependency "org.aspectj:aspectjweaver:$aspectjVersion"
|
||||
dependency 'org.assertj:assertj-core:3.12.2'
|
||||
dependency 'org.attoparser:attoparser:2.0.4.RELEASE'
|
||||
dependency 'org.attoparser:attoparser:2.0.5.RELEASE'
|
||||
dependency 'org.bouncycastle:bcpkix-jdk15on:1.64'
|
||||
dependency 'org.bouncycastle:bcprov-jdk15on:1.64'
|
||||
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.codehaus.groovy:groovy-all:2.4.21'
|
||||
dependency 'org.codehaus.groovy:groovy-json:2.4.21'
|
||||
dependency 'org.codehaus.groovy:groovy:2.4.21'
|
||||
dependency 'org.eclipse.jdt:ecj:3.12.3'
|
||||
dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty:jetty-client:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty:jetty-http:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty:jetty-io:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty:jetty-security:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty:jetty-server:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty:jetty-servlet:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty:jetty-util:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty:jetty-xml:9.4.36.v20210114'
|
||||
dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.jetty:jetty-client:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.jetty:jetty-http:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.jetty:jetty-io:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.jetty:jetty-security:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.jetty:jetty-server:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.jetty:jetty-servlet:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.jetty:jetty-util:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.jetty:jetty-xml:9.4.44.v20210927'
|
||||
dependency 'org.eclipse.persistence:javax.persistence:2.2.1'
|
||||
dependency 'org.gebish:geb-ast:0.10.0'
|
||||
dependency 'org.gebish:geb-core:0.10.0'
|
||||
@@ -182,20 +182,20 @@ 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.4.28.Final'
|
||||
dependency 'org.hibernate:hibernate-entitymanager:5.4.33'
|
||||
dependency 'org.hibernate:hibernate-validator:6.1.7.Final'
|
||||
dependency 'org.hsqldb:hsqldb:2.5.1'
|
||||
dependency 'org.hsqldb:hsqldb:2.5.2'
|
||||
dependency 'org.jasig.cas.client:cas-client-core:3.5.1'
|
||||
dependency 'org.javassist:javassist:3.22.0-CR2'
|
||||
dependency 'org.jboss.logging:jboss-logging:3.3.1.Final'
|
||||
dependency 'org.jboss.logging:jboss-logging:3.3.3.Final'
|
||||
dependency 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.1.Final'
|
||||
dependency 'org.jboss:jandex:2.0.3.Final'
|
||||
dependency 'org.jboss:jandex:2.0.5.Final'
|
||||
dependency 'org.mockito:mockito-core:3.0.0'
|
||||
dependency 'org.objenesis:objenesis:2.6'
|
||||
dependency 'org.openid4java:openid4java-nodeps:0.9.6'
|
||||
dependency 'org.opensaml:opensaml-core:3.4.5'
|
||||
dependency 'org.opensaml:opensaml-saml-api:3.4.5'
|
||||
dependency 'org.opensaml:opensaml-saml-impl:3.4.5'
|
||||
dependency 'org.opensaml:opensaml-core:3.4.6'
|
||||
dependency 'org.opensaml:opensaml-saml-api:3.4.6'
|
||||
dependency 'org.opensaml:opensaml-saml-impl:3.4.6'
|
||||
dependency 'org.ow2.asm:asm:6.2.1'
|
||||
dependency 'org.reactivestreams:reactive-streams:1.0.3'
|
||||
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.36.0'
|
||||
@@ -203,16 +203,16 @@ dependencyManagement {
|
||||
dependency 'org.seleniumhq.selenium:selenium-support:3.141.59'
|
||||
dependency 'org.seleniumhq.selenium:selenium-api:3.141.59'
|
||||
dependency 'org.skyscreamer:jsonassert:1.5.0'
|
||||
dependency 'org.slf4j:jcl-over-slf4j:1.7.30'
|
||||
dependency 'org.slf4j:jul-to-slf4j:1.7.30'
|
||||
dependency 'org.slf4j:log4j-over-slf4j:1.7.30'
|
||||
dependency 'org.slf4j:slf4j-api:1.7.30'
|
||||
dependency 'org.slf4j:slf4j-nop:1.7.30'
|
||||
dependency 'org.slf4j:jcl-over-slf4j:1.7.32'
|
||||
dependency 'org.slf4j:jul-to-slf4j:1.7.32'
|
||||
dependency 'org.slf4j:log4j-over-slf4j:1.7.32'
|
||||
dependency 'org.slf4j:slf4j-api:1.7.32'
|
||||
dependency 'org.slf4j:slf4j-nop:1.7.32'
|
||||
dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111'
|
||||
dependency 'org.springframework.ldap:spring-ldap-core:2.3.3.RELEASE'
|
||||
dependency 'org.springframework.ldap:spring-ldap-core:2.3.4.RELEASE'
|
||||
dependency 'org.synchronoss.cloud:nio-multipart-parser:1.1.0'
|
||||
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.12.RELEASE'
|
||||
dependency 'org.unbescape:unbescape:1.1.5.RELEASE'
|
||||
dependency 'org.unbescape:unbescape:1.1.6.RELEASE'
|
||||
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.14.RELEASE'
|
||||
dependency 'org.w3c.css:sac:1.3'
|
||||
dependency 'xalan:serializer:2.7.2'
|
||||
dependency 'xalan:xalan:2.7.2'
|
||||
|
||||
+24
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -53,8 +53,17 @@ public class DefaultSpringSecurityContextSourceTests {
|
||||
|
||||
@Test
|
||||
public void supportsSpacesInUrl() {
|
||||
new DefaultSpringSecurityContextSource(
|
||||
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
|
||||
"ldap://myhost:10389/dc=spring%20framework,dc=org");
|
||||
assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("dc=spring framework,dc=org");
|
||||
}
|
||||
|
||||
// gh-9742
|
||||
@Test
|
||||
public void constructorWhenUrlEncodedSpacesWithPlusCharacterThenBaseDnIsProperlyDecoded() {
|
||||
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
|
||||
"ldap://blah:123/dc=spring+framework,dc=org ldap://blah:456/dc=spring+framework,dc=org");
|
||||
assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("dc=spring framework,dc=org");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -105,6 +114,7 @@ public class DefaultSpringSecurityContextSourceTests {
|
||||
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
|
||||
this.contextSource.getUrls()[0]
|
||||
+ "ou=space%20cadets,dc=springframework,dc=org");
|
||||
assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("ou=space cadets,dc=springframework,dc=org");
|
||||
contextSource.afterPropertiesSet();
|
||||
contextSource.getContext(
|
||||
"uid=space cadet,ou=space cadets,dc=springframework,dc=org",
|
||||
@@ -147,6 +157,18 @@ public class DefaultSpringSecurityContextSourceTests {
|
||||
assertThat(ctxSrc.isPooled()).isTrue();
|
||||
}
|
||||
|
||||
// gh-9742
|
||||
@Test
|
||||
public void constructorWhenServerListWithSpacesInBaseDnThenSuccess() {
|
||||
List<String> serverUrls = new ArrayList<>();
|
||||
serverUrls.add("ldap://ad1.example.org:789");
|
||||
serverUrls.add("ldap://ad2.example.org:389");
|
||||
serverUrls.add("ldaps://ad3.example.org:636");
|
||||
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(serverUrls,
|
||||
"dc=spring framework,dc=org");
|
||||
assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("dc=spring framework,dc=org");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void instantiationFailsWithIncorrectServerUrl() {
|
||||
List<String> serverUrls = new ArrayList<>();
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.ldap.search;
|
||||
|
||||
import javax.naming.ldap.LdapName;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
|
||||
import org.springframework.security.ldap.server.ApacheDSContainer;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Additional tests for {@link FilterBasedLdapUserSearch} with spaces in the base dn.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = FilterBasedLdapUserSearchWithSpacesTests.ApacheDsContainerWithSpacesConfig.class)
|
||||
public class FilterBasedLdapUserSearchWithSpacesTests {
|
||||
|
||||
@Autowired
|
||||
private DefaultSpringSecurityContextSource contextSource;
|
||||
|
||||
// gh-9742
|
||||
@Test
|
||||
public void searchForUserWhenSpacesInBaseDnThenSuccess() throws Exception {
|
||||
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=space cadets", "(uid={0})",
|
||||
this.contextSource);
|
||||
locator.setSearchSubtree(false);
|
||||
locator.setSearchTimeLimit(0);
|
||||
locator.setDerefLinkFlag(false);
|
||||
|
||||
DirContextOperations bob = locator.searchForUser("space cadet");
|
||||
assertThat(bob.getStringAttribute("uid")).isEqualTo("space cadet");
|
||||
assertThat(bob.getDn()).isEqualTo(new LdapName("uid=space cadet,ou=space cadets"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ApacheDsContainerWithSpacesConfig implements DisposableBean {
|
||||
|
||||
private ApacheDSContainer container;
|
||||
|
||||
@Bean
|
||||
ApacheDSContainer ldapContainer() throws Exception {
|
||||
this.container = new ApacheDSContainer("dc=spring framework,dc=org",
|
||||
"classpath:test-server-with-spaces.ldif");
|
||||
this.container.setPort(53390);
|
||||
return this.container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ContextSource contextSource(ApacheDSContainer ldapContainer) {
|
||||
return new DefaultSpringSecurityContextSource(
|
||||
"ldap://127.0.0.1:" + ldapContainer.getPort() + "/dc=spring%20framework,dc=org");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
this.container.stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
dn: ou=space cadets,dc=spring framework,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: space cadets
|
||||
|
||||
dn: uid=space cadet,ou=space cadets,dc=spring framework,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
cn: Space Cadet
|
||||
sn: Cadet
|
||||
uid: space cadet
|
||||
userPassword: spacecadetspassword
|
||||
+26
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package org.springframework.security.ldap;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
@@ -85,7 +89,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
||||
}
|
||||
|
||||
setUrls(urls.toArray(new String[0]));
|
||||
setBase(this.rootDn);
|
||||
setBase((this.rootDn != null) ? decodeUrl(this.rootDn) : null);
|
||||
setPooled(true);
|
||||
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
|
||||
@Override
|
||||
@@ -150,7 +154,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
||||
Assert.notNull(baseDn, "The Base DN for the LDAP server must not be null.");
|
||||
Assert.notEmpty(urls, "At least one LDAP server URL must be provided.");
|
||||
|
||||
String trimmedBaseDn = baseDn.trim();
|
||||
String encodedBaseDn = encodeUrl(baseDn.trim());
|
||||
StringBuilder providerUrl = new StringBuilder();
|
||||
|
||||
for (String serverUrl : urls) {
|
||||
@@ -163,7 +167,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
||||
if (!trimmedUrl.endsWith("/")) {
|
||||
providerUrl.append("/");
|
||||
}
|
||||
providerUrl.append(trimmedBaseDn);
|
||||
providerUrl.append(encodedBaseDn);
|
||||
providerUrl.append(" ");
|
||||
}
|
||||
|
||||
@@ -171,4 +175,22 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
||||
|
||||
}
|
||||
|
||||
private static String encodeUrl(String url) {
|
||||
try {
|
||||
return URLEncoder.encode(url, StandardCharsets.UTF_8.toString());
|
||||
}
|
||||
catch (UnsupportedEncodingException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String decodeUrl(String url) {
|
||||
try {
|
||||
return URLDecoder.decode(url, StandardCharsets.UTF_8.toString());
|
||||
}
|
||||
catch (UnsupportedEncodingException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler {
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse response) throws IOException {
|
||||
if (!HttpStatus.BAD_REQUEST.equals(response.getStatusCode())) {
|
||||
if (HttpStatus.BAD_REQUEST.value() != response.getRawStatusCode()) {
|
||||
this.defaultErrorHandler.handleError(response);
|
||||
}
|
||||
|
||||
|
||||
+51
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 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 java.util.Map;
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Rob Winch
|
||||
* @author Craig Andrews
|
||||
* @since 5.0
|
||||
* @see AuthorizationRequestRepository
|
||||
* @see OAuth2AuthorizationRequest
|
||||
@@ -41,6 +42,8 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au
|
||||
|
||||
private final String sessionAttributeName = DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME;
|
||||
|
||||
private boolean allowMultipleAuthorizationRequests;
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
|
||||
Assert.notNull(request, "request cannot be null");
|
||||
@@ -63,9 +66,14 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au
|
||||
}
|
||||
String state = authorizationRequest.getState();
|
||||
Assert.hasText(state, "authorizationRequest.state cannot be empty");
|
||||
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(request);
|
||||
authorizationRequests.put(state, authorizationRequest);
|
||||
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests);
|
||||
if (this.allowMultipleAuthorizationRequests) {
|
||||
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(request);
|
||||
authorizationRequests.put(state, authorizationRequest);
|
||||
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests);
|
||||
}
|
||||
else {
|
||||
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,11 +85,16 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au
|
||||
}
|
||||
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(request);
|
||||
OAuth2AuthorizationRequest originalRequest = authorizationRequests.remove(stateParameter);
|
||||
if (!authorizationRequests.isEmpty()) {
|
||||
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests);
|
||||
} else {
|
||||
if (authorizationRequests.size() == 0) {
|
||||
request.getSession().removeAttribute(this.sessionAttributeName);
|
||||
}
|
||||
else if (authorizationRequests.size() == 1) {
|
||||
request.getSession().setAttribute(this.sessionAttributeName,
|
||||
authorizationRequests.values().iterator().next());
|
||||
}
|
||||
else {
|
||||
request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests);
|
||||
}
|
||||
return originalRequest;
|
||||
}
|
||||
|
||||
@@ -107,11 +120,38 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au
|
||||
*/
|
||||
private Map<String, OAuth2AuthorizationRequest> getAuthorizationRequests(HttpServletRequest request) {
|
||||
HttpSession session = request.getSession(false);
|
||||
Map<String, OAuth2AuthorizationRequest> authorizationRequests = session == null ? null :
|
||||
(Map<String, OAuth2AuthorizationRequest>) session.getAttribute(this.sessionAttributeName);
|
||||
if (authorizationRequests == null) {
|
||||
Object sessionAttributeValue = (session != null) ? session.getAttribute(this.sessionAttributeName) : null;
|
||||
if (sessionAttributeValue == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return authorizationRequests;
|
||||
else if (sessionAttributeValue instanceof OAuth2AuthorizationRequest) {
|
||||
OAuth2AuthorizationRequest auth2AuthorizationRequest = (OAuth2AuthorizationRequest) sessionAttributeValue;
|
||||
Map<String, OAuth2AuthorizationRequest> authorizationRequests = new HashMap<>(1);
|
||||
authorizationRequests.put(auth2AuthorizationRequest.getState(), auth2AuthorizationRequest);
|
||||
return authorizationRequests;
|
||||
}
|
||||
else if (sessionAttributeValue instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, OAuth2AuthorizationRequest> authorizationRequests = (Map<String, OAuth2AuthorizationRequest>) sessionAttributeValue;
|
||||
return authorizationRequests;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"authorizationRequests is supposed to be a Map or OAuth2AuthorizationRequest but actually is a "
|
||||
+ sessionAttributeValue.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if multiple {@link OAuth2AuthorizationRequest}s should be stored per
|
||||
* session. Default is false (not allow multiple {@link OAuth2AuthorizationRequest}
|
||||
* per session).
|
||||
* @param allowMultipleAuthorizationRequests true allows more than one
|
||||
* {@link OAuth2AuthorizationRequest} to be stored per session.
|
||||
* @since 5.5
|
||||
*/
|
||||
@Deprecated
|
||||
public void setAllowMultipleAuthorizationRequests(boolean allowMultipleAuthorizationRequests) {
|
||||
this.allowMultipleAuthorizationRequests = allowMultipleAuthorizationRequests;
|
||||
}
|
||||
}
|
||||
|
||||
+76
-51
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -34,6 +34,7 @@ import reactor.core.publisher.Mono;
|
||||
* {@link OAuth2AuthorizationRequest} in the {@code WebSession}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Steve Riesenberg
|
||||
* @since 5.1
|
||||
* @see AuthorizationRequestRepository
|
||||
* @see OAuth2AuthorizationRequest
|
||||
@@ -46,6 +47,8 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository
|
||||
|
||||
private final String sessionAttributeName = DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME;
|
||||
|
||||
private boolean allowMultipleAuthorizationRequests;
|
||||
|
||||
@Override
|
||||
public Mono<OAuth2AuthorizationRequest> loadAuthorizationRequest(
|
||||
ServerWebExchange exchange) {
|
||||
@@ -53,17 +56,33 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository
|
||||
if (state == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return getStateToAuthorizationRequest(exchange)
|
||||
.filter(stateToAuthorizationRequest -> stateToAuthorizationRequest.containsKey(state))
|
||||
.map(stateToAuthorizationRequest -> stateToAuthorizationRequest.get(state));
|
||||
// @formatter:off
|
||||
return this.getSessionAttributes(exchange)
|
||||
.filter((sessionAttrs) -> sessionAttrs.containsKey(this.sessionAttributeName))
|
||||
.map(this::getAuthorizationRequests)
|
||||
.filter((stateToAuthorizationRequest) -> stateToAuthorizationRequest.containsKey(state))
|
||||
.map((stateToAuthorizationRequest) -> stateToAuthorizationRequest.get(state));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> saveAuthorizationRequest(
|
||||
OAuth2AuthorizationRequest authorizationRequest, ServerWebExchange exchange) {
|
||||
Assert.notNull(authorizationRequest, "authorizationRequest cannot be null");
|
||||
return saveStateToAuthorizationRequest(exchange)
|
||||
.doOnNext(stateToAuthorizationRequest -> stateToAuthorizationRequest.put(authorizationRequest.getState(), authorizationRequest))
|
||||
Assert.notNull(exchange, "exchange cannot be null");
|
||||
// @formatter:off
|
||||
return getSessionAttributes(exchange)
|
||||
.doOnNext((sessionAttrs) -> {
|
||||
if (this.allowMultipleAuthorizationRequests) {
|
||||
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(
|
||||
sessionAttrs);
|
||||
authorizationRequests.put(authorizationRequest.getState(), authorizationRequest);
|
||||
sessionAttrs.put(this.sessionAttributeName, authorizationRequests);
|
||||
}
|
||||
else {
|
||||
sessionAttrs.put(this.sessionAttributeName, authorizationRequest);
|
||||
}
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
@@ -74,27 +93,24 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository
|
||||
if (state == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return exchange.getSession()
|
||||
.map(WebSession::getAttributes)
|
||||
.handle((sessionAttrs, sink) -> {
|
||||
Map<String, OAuth2AuthorizationRequest> stateToAuthzRequest = sessionAttrsMapStateToAuthorizationRequest(sessionAttrs);
|
||||
if (stateToAuthzRequest == null) {
|
||||
sink.complete();
|
||||
return;
|
||||
}
|
||||
OAuth2AuthorizationRequest removedValue = stateToAuthzRequest.remove(state);
|
||||
if (stateToAuthzRequest.isEmpty()) {
|
||||
sessionAttrs.remove(this.sessionAttributeName);
|
||||
} else if (removedValue != null) {
|
||||
// gh-7327 Overwrite the existing Map to ensure the state is saved for distributed sessions
|
||||
sessionAttrs.put(this.sessionAttributeName, stateToAuthzRequest);
|
||||
}
|
||||
if (removedValue == null) {
|
||||
sink.complete();
|
||||
} else {
|
||||
sink.next(removedValue);
|
||||
}
|
||||
});
|
||||
// @formatter:off
|
||||
return getSessionAttributes(exchange)
|
||||
.flatMap((sessionAttrs) -> {
|
||||
Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(
|
||||
sessionAttrs);
|
||||
OAuth2AuthorizationRequest originalRequest = authorizationRequests.remove(state);
|
||||
if (authorizationRequests.isEmpty()) {
|
||||
sessionAttrs.remove(this.sessionAttributeName);
|
||||
}
|
||||
else if (authorizationRequests.size() == 1) {
|
||||
sessionAttrs.put(this.sessionAttributeName, authorizationRequests.values().iterator().next());
|
||||
}
|
||||
else {
|
||||
sessionAttrs.put(this.sessionAttributeName, authorizationRequests);
|
||||
}
|
||||
return Mono.justOrEmpty(originalRequest);
|
||||
});
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,31 +127,40 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository
|
||||
return exchange.getSession().map(WebSession::getAttributes);
|
||||
}
|
||||
|
||||
private Mono<Map<String, OAuth2AuthorizationRequest>> getStateToAuthorizationRequest(ServerWebExchange exchange) {
|
||||
Assert.notNull(exchange, "exchange cannot be null");
|
||||
|
||||
return getSessionAttributes(exchange)
|
||||
.flatMap(sessionAttrs -> Mono.justOrEmpty(this.sessionAttrsMapStateToAuthorizationRequest(sessionAttrs)));
|
||||
private Map<String, OAuth2AuthorizationRequest> getAuthorizationRequests(Map<String, Object> sessionAttrs) {
|
||||
Object sessionAttributeValue = sessionAttrs.get(this.sessionAttributeName);
|
||||
if (sessionAttributeValue == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
else if (sessionAttributeValue instanceof OAuth2AuthorizationRequest) {
|
||||
OAuth2AuthorizationRequest oauth2AuthorizationRequest = (OAuth2AuthorizationRequest) sessionAttributeValue;
|
||||
Map<String, OAuth2AuthorizationRequest> authorizationRequests = new HashMap<>(1);
|
||||
authorizationRequests.put(oauth2AuthorizationRequest.getState(), oauth2AuthorizationRequest);
|
||||
return authorizationRequests;
|
||||
}
|
||||
else if (sessionAttributeValue instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, OAuth2AuthorizationRequest> authorizationRequests = (Map<String, OAuth2AuthorizationRequest>) sessionAttrs
|
||||
.get(this.sessionAttributeName);
|
||||
return authorizationRequests;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"authorizationRequests is supposed to be a Map or OAuth2AuthorizationRequest but actually is a "
|
||||
+ sessionAttributeValue.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private Mono<Map<String, OAuth2AuthorizationRequest>> saveStateToAuthorizationRequest(ServerWebExchange exchange) {
|
||||
Assert.notNull(exchange, "exchange cannot be null");
|
||||
|
||||
return getSessionAttributes(exchange)
|
||||
.doOnNext(sessionAttrs -> {
|
||||
Object stateToAuthzRequest = sessionAttrs.get(this.sessionAttributeName);
|
||||
|
||||
if (stateToAuthzRequest == null) {
|
||||
stateToAuthzRequest = new HashMap<String, OAuth2AuthorizationRequest>();
|
||||
}
|
||||
|
||||
// No matter stateToAuthzRequest was in session or not, we should always put it into session again
|
||||
// in case of redis or hazelcast session. #6215
|
||||
sessionAttrs.put(this.sessionAttributeName, stateToAuthzRequest);
|
||||
}).flatMap(sessionAttrs -> Mono.justOrEmpty(this.sessionAttrsMapStateToAuthorizationRequest(sessionAttrs)));
|
||||
}
|
||||
|
||||
private Map<String, OAuth2AuthorizationRequest> sessionAttrsMapStateToAuthorizationRequest(Map<String, Object> sessionAttrs) {
|
||||
return (Map<String, OAuth2AuthorizationRequest>) sessionAttrs.get(this.sessionAttributeName);
|
||||
/**
|
||||
* Configure if multiple {@link OAuth2AuthorizationRequest}s should be stored per
|
||||
* session. Default is false (not allow multiple {@link OAuth2AuthorizationRequest}
|
||||
* per session).
|
||||
* @param allowMultipleAuthorizationRequests true allows more than one
|
||||
* {@link OAuth2AuthorizationRequest} to be stored per session.
|
||||
* @since 5.5
|
||||
*/
|
||||
@Deprecated
|
||||
public void setAllowMultipleAuthorizationRequests(boolean allowMultipleAuthorizationRequests) {
|
||||
this.allowMultipleAuthorizationRequests = allowMultipleAuthorizationRequests;
|
||||
}
|
||||
}
|
||||
|
||||
+52
@@ -15,12 +15,19 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.mock.http.MockHttpInputMessage;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.web.client.UnknownHttpStatusCodeException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
@@ -58,4 +65,49 @@ public class OAuth2ErrorResponseErrorHandlerTests {
|
||||
.isInstanceOf(OAuth2AuthorizationException.class)
|
||||
.hasMessage("[insufficient_scope] The access token expired");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleErrorWhenErrorResponseWithInvalidStatusCodeThenHandled() {
|
||||
CustomMockClientHttpResponse response = new CustomMockClientHttpResponse(new byte[0], 596);
|
||||
assertThatExceptionOfType(UnknownHttpStatusCodeException.class)
|
||||
.isThrownBy(() -> this.errorHandler.handleError(response)).withMessage("596 : [no body]");
|
||||
}
|
||||
|
||||
private static final class CustomMockClientHttpResponse extends MockHttpInputMessage implements ClientHttpResponse {
|
||||
|
||||
private final int statusCode;
|
||||
|
||||
private CustomMockClientHttpResponse(byte[] content, int statusCode) {
|
||||
super(content);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpStatus getStatusCode() throws IOException {
|
||||
return HttpStatus.valueOf(getRawStatusCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawStatusCode() {
|
||||
return this.statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatusText() throws IOException {
|
||||
HttpStatus httpStatus = HttpStatus.resolve(this.statusCode);
|
||||
return (httpStatus != null) ? httpStatus.getReasonPhrase() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
getBody().close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.web;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpSessionOAuth2AuthorizationRequestRepository} when
|
||||
* {@link HttpSessionOAuth2AuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)}
|
||||
* is enabled.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Craig Andrews
|
||||
*/
|
||||
public class HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests
|
||||
extends HttpSessionOAuth2AuthorizationRequestRepositoryTests {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||
this.authorizationRequestRepository.setAllowMultipleAuthorizationRequests(true);
|
||||
}
|
||||
|
||||
// gh-5110
|
||||
@Test
|
||||
public void loadAuthorizationRequestWhenMultipleSavedThenReturnMatchingAuthorizationRequest() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
String state1 = "state-1122";
|
||||
OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest1, request, response);
|
||||
String state2 = "state-3344";
|
||||
OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest2, request, response);
|
||||
String state3 = "state-5566";
|
||||
OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response);
|
||||
request.addParameter(OAuth2ParameterNames.STATE, state1);
|
||||
OAuth2AuthorizationRequest loadedAuthorizationRequest1 = this.authorizationRequestRepository
|
||||
.loadAuthorizationRequest(request);
|
||||
assertThat(loadedAuthorizationRequest1).isEqualTo(authorizationRequest1);
|
||||
request.removeParameter(OAuth2ParameterNames.STATE);
|
||||
request.addParameter(OAuth2ParameterNames.STATE, state2);
|
||||
OAuth2AuthorizationRequest loadedAuthorizationRequest2 = this.authorizationRequestRepository
|
||||
.loadAuthorizationRequest(request);
|
||||
assertThat(loadedAuthorizationRequest2).isEqualTo(authorizationRequest2);
|
||||
request.removeParameter(OAuth2ParameterNames.STATE);
|
||||
request.addParameter(OAuth2ParameterNames.STATE, state3);
|
||||
OAuth2AuthorizationRequest loadedAuthorizationRequest3 = this.authorizationRequestRepository
|
||||
.loadAuthorizationRequest(request);
|
||||
assertThat(loadedAuthorizationRequest3).isEqualTo(authorizationRequest3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequests() {
|
||||
// save 2 requests with legacy (allowMultipleAuthorizationRequests=true) and load
|
||||
// with new
|
||||
HttpSessionOAuth2AuthorizationRequestRepository legacy = new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||
legacy.setAllowMultipleAuthorizationRequests(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
String state1 = "state-1122";
|
||||
OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build();
|
||||
legacy.saveAuthorizationRequest(authorizationRequest1, request, response);
|
||||
String state2 = "state-3344";
|
||||
OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build();
|
||||
legacy.saveAuthorizationRequest(authorizationRequest2, request, response);
|
||||
|
||||
request.setParameter(OAuth2ParameterNames.STATE, state1);
|
||||
OAuth2AuthorizationRequest loaded = this.authorizationRequestRepository.loadAuthorizationRequest(request);
|
||||
|
||||
assertThat(loaded).isEqualTo(authorizationRequest1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequests() {
|
||||
// save 2 requests with legacy (allowMultipleAuthorizationRequests=true), save
|
||||
// with new, and load with new
|
||||
HttpSessionOAuth2AuthorizationRequestRepository legacy = new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||
legacy.setAllowMultipleAuthorizationRequests(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
String state1 = "state-1122";
|
||||
OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build();
|
||||
legacy.saveAuthorizationRequest(authorizationRequest1, request, response);
|
||||
String state2 = "state-3344";
|
||||
OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build();
|
||||
legacy.saveAuthorizationRequest(authorizationRequest2, request, response);
|
||||
String state3 = "state-5566";
|
||||
OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build();
|
||||
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response);
|
||||
request.setParameter(OAuth2ParameterNames.STATE, state3);
|
||||
OAuth2AuthorizationRequest loaded = this.authorizationRequestRepository.loadAuthorizationRequest(request);
|
||||
|
||||
assertThat(loaded).isEqualTo(authorizationRequest3);
|
||||
}
|
||||
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.web;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpSessionOAuth2AuthorizationRequestRepository} when
|
||||
* {@link HttpSessionOAuth2AuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)}
|
||||
* is disabled.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Craig Andrews
|
||||
*/
|
||||
public class HttpSessionOAuth2AuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests
|
||||
extends HttpSessionOAuth2AuthorizationRequestRepositoryTests {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||
this.authorizationRequestRepository.setAllowMultipleAuthorizationRequests(false);
|
||||
}
|
||||
|
||||
// gh-5145
|
||||
@Test
|
||||
public void loadAuthorizationRequestWhenMultipleSavedThenReturnLastAuthorizationRequest() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
String state1 = "state-1122";
|
||||
OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest1, request, response);
|
||||
String state2 = "state-3344";
|
||||
OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest2, request, response);
|
||||
String state3 = "state-5566";
|
||||
OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response);
|
||||
request.addParameter(OAuth2ParameterNames.STATE, state1);
|
||||
OAuth2AuthorizationRequest loadedAuthorizationRequest1 = this.authorizationRequestRepository
|
||||
.loadAuthorizationRequest(request);
|
||||
assertThat(loadedAuthorizationRequest1).isNull();
|
||||
request.removeParameter(OAuth2ParameterNames.STATE);
|
||||
request.addParameter(OAuth2ParameterNames.STATE, state2);
|
||||
OAuth2AuthorizationRequest loadedAuthorizationRequest2 = this.authorizationRequestRepository
|
||||
.loadAuthorizationRequest(request);
|
||||
assertThat(loadedAuthorizationRequest2).isNull();
|
||||
request.removeParameter(OAuth2ParameterNames.STATE);
|
||||
request.addParameter(OAuth2ParameterNames.STATE, state3);
|
||||
OAuth2AuthorizationRequest loadedAuthorizationRequest3 = this.authorizationRequestRepository
|
||||
.loadAuthorizationRequest(request);
|
||||
assertThat(loadedAuthorizationRequest3).isEqualTo(authorizationRequest3);
|
||||
}
|
||||
|
||||
}
|
||||
+8
-45
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -34,11 +34,12 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
* Tests for {@link HttpSessionOAuth2AuthorizationRequestRepository}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Craig Andrews
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HttpSessionOAuth2AuthorizationRequestRepositoryTests {
|
||||
private HttpSessionOAuth2AuthorizationRequestRepository authorizationRequestRepository =
|
||||
new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||
public abstract class HttpSessionOAuth2AuthorizationRequestRepositoryTests {
|
||||
|
||||
protected HttpSessionOAuth2AuthorizationRequestRepository authorizationRequestRepository;
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void loadAuthorizationRequestWhenHttpServletRequestIsNullThenThrowIllegalArgumentException() {
|
||||
@@ -70,42 +71,6 @@ public class HttpSessionOAuth2AuthorizationRequestRepositoryTests {
|
||||
assertThat(loadedAuthorizationRequest).isEqualTo(authorizationRequest);
|
||||
}
|
||||
|
||||
// gh-5110
|
||||
@Test
|
||||
public void loadAuthorizationRequestWhenMultipleSavedThenReturnMatchingAuthorizationRequest() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
String state1 = "state-1122";
|
||||
OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest1, request, response);
|
||||
|
||||
String state2 = "state-3344";
|
||||
OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest2, request, response);
|
||||
|
||||
String state3 = "state-5566";
|
||||
OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build();
|
||||
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response);
|
||||
|
||||
request.addParameter(OAuth2ParameterNames.STATE, state1);
|
||||
OAuth2AuthorizationRequest loadedAuthorizationRequest1 =
|
||||
this.authorizationRequestRepository.loadAuthorizationRequest(request);
|
||||
assertThat(loadedAuthorizationRequest1).isEqualTo(authorizationRequest1);
|
||||
|
||||
request.removeParameter(OAuth2ParameterNames.STATE);
|
||||
request.addParameter(OAuth2ParameterNames.STATE, state2);
|
||||
OAuth2AuthorizationRequest loadedAuthorizationRequest2 =
|
||||
this.authorizationRequestRepository.loadAuthorizationRequest(request);
|
||||
assertThat(loadedAuthorizationRequest2).isEqualTo(authorizationRequest2);
|
||||
|
||||
request.removeParameter(OAuth2ParameterNames.STATE);
|
||||
request.addParameter(OAuth2ParameterNames.STATE, state3);
|
||||
OAuth2AuthorizationRequest loadedAuthorizationRequest3 =
|
||||
this.authorizationRequestRepository.loadAuthorizationRequest(request);
|
||||
assertThat(loadedAuthorizationRequest3).isEqualTo(authorizationRequest3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizationRequestWhenSavedAndStateParameterNullThenReturnNull() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
@@ -284,11 +249,9 @@ public class HttpSessionOAuth2AuthorizationRequestRepositoryTests {
|
||||
assertThat(removedAuthorizationRequest).isNull();
|
||||
}
|
||||
|
||||
private OAuth2AuthorizationRequest.Builder createAuthorizationRequest() {
|
||||
return OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id-1234")
|
||||
.state("state-1234");
|
||||
protected OAuth2AuthorizationRequest.Builder createAuthorizationRequest() {
|
||||
return OAuth2AuthorizationRequest.authorizationCode().authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id-1234").state("state-1234");
|
||||
}
|
||||
|
||||
static class MockDistributedHttpSession extends MockHttpSession {
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.web.server;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link WebSessionOAuth2ServerAuthorizationRequestRepository} when
|
||||
* {@link WebSessionOAuth2ServerAuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)}
|
||||
* is enabled.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
|
||||
public class WebSessionOAuth2ServerAuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests
|
||||
extends WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.repository = new WebSessionOAuth2ServerAuthorizationRequestRepository();
|
||||
this.repository.setAllowMultipleAuthorizationRequests(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizationRequestWhenMultipleSavedThenAuthorizationRequest() {
|
||||
String oldState = "state0";
|
||||
// @formatter:off
|
||||
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, oldState)
|
||||
.build();
|
||||
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(oldState)
|
||||
.build();
|
||||
// @formatter:on
|
||||
WebSessionManager sessionManager = (e) -> this.exchange.getSession();
|
||||
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(),
|
||||
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(),
|
||||
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
// @formatter:off
|
||||
Mono<OAuth2AuthorizationRequest> saveAndSaveAndLoad = this.repository
|
||||
.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
|
||||
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
|
||||
.then(this.repository.loadAuthorizationRequest(oldExchange));
|
||||
StepVerifier.create(saveAndSaveAndLoad)
|
||||
.expectNext(oldAuthorizationRequest)
|
||||
.verifyComplete();
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
|
||||
.expectNext(this.authorizationRequest)
|
||||
.verifyComplete();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// gh-5145
|
||||
@Test
|
||||
public void loadAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequestsThenReturnOldAuthorizationRequest() {
|
||||
// save 2 requests with legacy (allowMultipleAuthorizationRequests=true) and load
|
||||
// with new
|
||||
WebSessionOAuth2ServerAuthorizationRequestRepository legacy = new WebSessionOAuth2ServerAuthorizationRequestRepository();
|
||||
legacy.setAllowMultipleAuthorizationRequests(true);
|
||||
// @formatter:off
|
||||
String state1 = "state-1122";
|
||||
OAuth2AuthorizationRequest authorizationRequest1 = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(state1)
|
||||
.build();
|
||||
StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest1, this.exchange))
|
||||
.verifyComplete();
|
||||
String state2 = "state-3344";
|
||||
OAuth2AuthorizationRequest authorizationRequest2 = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(state2)
|
||||
.build();
|
||||
StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest2, this.exchange))
|
||||
.verifyComplete();
|
||||
ServerHttpRequest newRequest = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, state1)
|
||||
.build();
|
||||
ServerWebExchange newExchange = this.exchange.mutate()
|
||||
.request(newRequest)
|
||||
.build();
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange))
|
||||
.expectNext(authorizationRequest1)
|
||||
.verifyComplete();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// gh-5145
|
||||
@Test
|
||||
public void saveAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequestsThenLoadNewAuthorizationRequest() {
|
||||
// save 2 requests with legacy (allowMultipleAuthorizationRequests=true), save
|
||||
// with new, and load with new
|
||||
WebSessionOAuth2ServerAuthorizationRequestRepository legacy = new WebSessionOAuth2ServerAuthorizationRequestRepository();
|
||||
legacy.setAllowMultipleAuthorizationRequests(true);
|
||||
// @formatter:off
|
||||
String state1 = "state-1122";
|
||||
OAuth2AuthorizationRequest authorizationRequest1 = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(state1)
|
||||
.build();
|
||||
StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest1, this.exchange))
|
||||
.verifyComplete();
|
||||
String state2 = "state-3344";
|
||||
OAuth2AuthorizationRequest authorizationRequest2 = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(state2)
|
||||
.build();
|
||||
StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest2, this.exchange))
|
||||
.verifyComplete();
|
||||
String state3 = "state-5566";
|
||||
OAuth2AuthorizationRequest authorizationRequest3 = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(state3)
|
||||
.build();
|
||||
ServerHttpRequest newRequest = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, state3)
|
||||
.build();
|
||||
ServerWebExchange newExchange = this.exchange.mutate()
|
||||
.request(newRequest)
|
||||
.build();
|
||||
Mono<OAuth2AuthorizationRequest> saveAndLoad = this.repository
|
||||
.saveAuthorizationRequest(authorizationRequest3, this.exchange)
|
||||
.then(this.repository.loadAuthorizationRequest(newExchange));
|
||||
StepVerifier.create(saveAndLoad)
|
||||
.expectNext(authorizationRequest3)
|
||||
.verifyComplete();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAuthorizationRequestWhenMultipleThenOnlyOneRemoved() {
|
||||
String oldState = "state0";
|
||||
// @formatter:off
|
||||
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, oldState)
|
||||
.build();
|
||||
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(oldState)
|
||||
.build();
|
||||
// @formatter:on
|
||||
WebSessionManager sessionManager = (e) -> this.exchange.getSession();
|
||||
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(),
|
||||
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(),
|
||||
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
// @formatter:off
|
||||
Mono<OAuth2AuthorizationRequest> saveAndSaveAndRemove = this.repository
|
||||
.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
|
||||
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
|
||||
.then(this.repository.removeAuthorizationRequest(this.exchange));
|
||||
StepVerifier.create(saveAndSaveAndRemove).expectNext(this.authorizationRequest)
|
||||
.verifyComplete();
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
|
||||
.verifyComplete();
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(oldExchange))
|
||||
.expectNext(oldAuthorizationRequest)
|
||||
.verifyComplete();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// gh-7327
|
||||
@Test
|
||||
public void removeAuthorizationRequestWhenMultipleThenRemovedAndSessionAttributeUpdated() {
|
||||
String oldState = "state0";
|
||||
// @formatter:off
|
||||
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, oldState)
|
||||
.build();
|
||||
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(oldState)
|
||||
.build();
|
||||
// @formatter:on
|
||||
Map<String, Object> sessionAttrs = spy(new HashMap<>());
|
||||
WebSession session = mock(WebSession.class);
|
||||
given(session.getAttributes()).willReturn(sessionAttrs);
|
||||
WebSessionManager sessionManager = (e) -> Mono.just(session);
|
||||
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(),
|
||||
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(),
|
||||
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
// @formatter:off
|
||||
Mono<OAuth2AuthorizationRequest> saveAndSaveAndRemove = this.repository
|
||||
.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
|
||||
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
|
||||
.then(this.repository.removeAuthorizationRequest(this.exchange));
|
||||
StepVerifier.create(saveAndSaveAndRemove).expectNext(this.authorizationRequest)
|
||||
.verifyComplete();
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
|
||||
.verifyComplete();
|
||||
// @formatter:on
|
||||
verify(sessionAttrs, times(3)).put(any(), any());
|
||||
}
|
||||
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.web.server;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link WebSessionOAuth2ServerAuthorizationRequestRepository} when
|
||||
* {@link WebSessionOAuth2ServerAuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)}
|
||||
* is disabled.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class WebSessionOAuth2ServerAuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests
|
||||
extends WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.repository = new WebSessionOAuth2ServerAuthorizationRequestRepository();
|
||||
this.repository.setAllowMultipleAuthorizationRequests(false);
|
||||
}
|
||||
|
||||
// gh-5145
|
||||
@Test
|
||||
public void loadAuthorizationRequestWhenMultipleSavedThenReturnLastAuthorizationRequest() {
|
||||
// @formatter:off
|
||||
String state1 = "state-1122";
|
||||
OAuth2AuthorizationRequest authorizationRequest1 = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(state1)
|
||||
.build();
|
||||
StepVerifier.create(this.repository.saveAuthorizationRequest(authorizationRequest1, this.exchange))
|
||||
.verifyComplete();
|
||||
String state2 = "state-3344";
|
||||
OAuth2AuthorizationRequest authorizationRequest2 = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(state2)
|
||||
.build();
|
||||
StepVerifier.create(this.repository.saveAuthorizationRequest(authorizationRequest2, this.exchange))
|
||||
.verifyComplete();
|
||||
String state3 = "state-5566";
|
||||
OAuth2AuthorizationRequest authorizationRequest3 = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(state3)
|
||||
.build();
|
||||
StepVerifier.create(this.repository.saveAuthorizationRequest(authorizationRequest3, this.exchange))
|
||||
.verifyComplete();
|
||||
ServerHttpRequest newRequest1 = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, state1)
|
||||
.build();
|
||||
ServerWebExchange newExchange1 = this.exchange.mutate()
|
||||
.request(newRequest1)
|
||||
.build();
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange1))
|
||||
.verifyComplete();
|
||||
ServerHttpRequest newRequest2 = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, state2)
|
||||
.build();
|
||||
ServerWebExchange newExchange2 = this.exchange.mutate()
|
||||
.request(newRequest2)
|
||||
.build();
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange2))
|
||||
.verifyComplete();
|
||||
ServerHttpRequest newRequest3 = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, state3)
|
||||
.build();
|
||||
ServerWebExchange newExchange3 = this.exchange.mutate()
|
||||
.request(newRequest3)
|
||||
.build();
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange3))
|
||||
.expectNext(authorizationRequest3)
|
||||
.verifyComplete();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// gh-5145
|
||||
@Test
|
||||
public void removeAuthorizationRequestWhenMultipleThenSessionAttributeRemoved() {
|
||||
String oldState = "state0";
|
||||
// @formatter:off
|
||||
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, oldState)
|
||||
.build();
|
||||
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(oldState)
|
||||
.build();
|
||||
// @formatter:on
|
||||
Map<String, Object> sessionAttrs = spy(new HashMap<>());
|
||||
WebSession session = mock(WebSession.class);
|
||||
given(session.getAttributes()).willReturn(sessionAttrs);
|
||||
WebSessionManager sessionManager = (e) -> Mono.just(session);
|
||||
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(),
|
||||
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(),
|
||||
sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
// @formatter:off
|
||||
Mono<OAuth2AuthorizationRequest> saveAndSaveAndRemove = this.repository
|
||||
.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
|
||||
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
|
||||
.then(this.repository.removeAuthorizationRequest(this.exchange));
|
||||
StepVerifier.create(saveAndSaveAndRemove).expectNext(this.authorizationRequest)
|
||||
.verifyComplete();
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
|
||||
.verifyComplete();
|
||||
// @formatter:on
|
||||
verify(sessionAttrs, times(2)).put(anyString(), any());
|
||||
verify(sessionAttrs).remove(anyString());
|
||||
}
|
||||
|
||||
}
|
||||
+11
-130
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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,51 +16,39 @@
|
||||
|
||||
package org.springframework.security.oauth2.client.web.server;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
*/
|
||||
public class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
|
||||
public abstract class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
|
||||
|
||||
private WebSessionOAuth2ServerAuthorizationRequestRepository repository =
|
||||
new WebSessionOAuth2ServerAuthorizationRequestRepository();
|
||||
protected WebSessionOAuth2ServerAuthorizationRequestRepository repository;
|
||||
|
||||
private OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
// @formatter:off
|
||||
protected OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state("state")
|
||||
.build();
|
||||
|
||||
private ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, "state"));
|
||||
protected ServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("/").queryParam(OAuth2ParameterNames.STATE, "state"));
|
||||
|
||||
@Test
|
||||
public void loadAuthorizationRequestWhenNullExchangeThenIllegalArgumentException() {
|
||||
@@ -106,39 +94,6 @@ public class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAuthorizationRequestWhenMultipleSavedThenAuthorizationRequest() {
|
||||
String oldState = "state0";
|
||||
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, oldState).build();
|
||||
|
||||
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(oldState)
|
||||
.build();
|
||||
|
||||
WebSessionManager sessionManager = e -> this.exchange.getSession();
|
||||
|
||||
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), sessionManager,
|
||||
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), sessionManager,
|
||||
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
|
||||
Mono<OAuth2AuthorizationRequest> saveAndSaveAndLoad = this.repository.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
|
||||
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
|
||||
.then(this.repository.loadAuthorizationRequest(oldExchange));
|
||||
|
||||
StepVerifier.create(saveAndSaveAndLoad)
|
||||
.expectNext(oldAuthorizationRequest)
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
|
||||
.expectNext(this.authorizationRequest)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveAuthorizationRequestWhenAuthorizationRequestNullThenThrowsIllegalArgumentException() {
|
||||
this.authorizationRequest = null;
|
||||
@@ -203,80 +158,6 @@ public class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAuthorizationRequestWhenMultipleThenOnlyOneRemoved() {
|
||||
String oldState = "state0";
|
||||
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, oldState).build();
|
||||
|
||||
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(oldState)
|
||||
.build();
|
||||
|
||||
WebSessionManager sessionManager = e -> this.exchange.getSession();
|
||||
|
||||
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), sessionManager,
|
||||
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), sessionManager,
|
||||
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
|
||||
Mono<OAuth2AuthorizationRequest> saveAndSaveAndRemove = this.repository.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
|
||||
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
|
||||
.then(this.repository.removeAuthorizationRequest(this.exchange));
|
||||
|
||||
StepVerifier.create(saveAndSaveAndRemove)
|
||||
.expectNext(this.authorizationRequest)
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(oldExchange))
|
||||
.expectNext(oldAuthorizationRequest)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
// gh-7327
|
||||
@Test
|
||||
public void removeAuthorizationRequestWhenMultipleThenRemovedAndSessionAttributeUpdated() {
|
||||
String oldState = "state0";
|
||||
MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/")
|
||||
.queryParam(OAuth2ParameterNames.STATE, oldState).build();
|
||||
|
||||
OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/oauth2/authorize")
|
||||
.clientId("client-id")
|
||||
.redirectUri("http://localhost/client-1")
|
||||
.state(oldState)
|
||||
.build();
|
||||
|
||||
Map<String, Object> sessionAttrs = spy(new HashMap<>());
|
||||
WebSession session = mock(WebSession.class);
|
||||
when(session.getAttributes()).thenReturn(sessionAttrs);
|
||||
WebSessionManager sessionManager = e -> Mono.just(session);
|
||||
|
||||
this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), sessionManager,
|
||||
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), sessionManager,
|
||||
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
|
||||
|
||||
Mono<OAuth2AuthorizationRequest> saveAndSaveAndRemove = this.repository.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange)
|
||||
.then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange))
|
||||
.then(this.repository.removeAuthorizationRequest(this.exchange));
|
||||
|
||||
StepVerifier.create(saveAndSaveAndRemove)
|
||||
.expectNext(this.authorizationRequest)
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange))
|
||||
.verifyComplete();
|
||||
|
||||
verify(sessionAttrs, times(3)).put(any(), any());
|
||||
}
|
||||
|
||||
private void assertSessionStartedIs(boolean expected) {
|
||||
Mono<Boolean> isStarted = this.exchange.getSession().map(WebSession::isStarted);
|
||||
StepVerifier.create(isStarted)
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
|
||||
+17
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -45,19 +45,20 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
|
||||
private final static TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class);
|
||||
private final static TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
|
||||
private final Map<String, Converter<Object, ?>> claimTypeConverters;
|
||||
private final Converter<Map<String, Object>, Map<String, Object>> delegate;
|
||||
|
||||
/**
|
||||
* Constructs a {@link MappedJwtClaimSetConverter} with the provided arguments
|
||||
*
|
||||
* This will completely replace any set of default converters.
|
||||
*
|
||||
* A converter that returns {@code null} removes the claim from the claim set. A
|
||||
* converter that returns a non-{@code null} value adds or replaces that claim in the
|
||||
* claim set.
|
||||
* @param claimTypeConverters The {@link Map} of converters to use
|
||||
*/
|
||||
public MappedJwtClaimSetConverter(Map<String, Converter<Object, ?>> claimTypeConverters) {
|
||||
Assert.notNull(claimTypeConverters, "claimTypeConverters cannot be null");
|
||||
this.claimTypeConverters = claimTypeConverters;
|
||||
this.delegate = new ClaimTypeConverter(claimTypeConverters);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,6 +82,9 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
|
||||
*
|
||||
* To completely replace the underlying {@link Map} of converters, see {@link MappedJwtClaimSetConverter#MappedJwtClaimSetConverter(Map)}.
|
||||
*
|
||||
* A converter that returns {@code null} removes the claim from the claim set. A
|
||||
* converter that returns a non-{@code null} value adds or replaces that claim in the
|
||||
* claim set.
|
||||
* @param claimTypeConverters
|
||||
* @return An instance of {@link MappedJwtClaimSetConverter} that contains the converters provided,
|
||||
* plus any defaults that were not overridden.
|
||||
@@ -144,12 +148,16 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
|
||||
@Override
|
||||
public Map<String, Object> convert(Map<String, Object> claims) {
|
||||
Assert.notNull(claims, "claims cannot be null");
|
||||
|
||||
Map<String, Object> mappedClaims = this.delegate.convert(claims);
|
||||
|
||||
mappedClaims = removeClaims(mappedClaims);
|
||||
mappedClaims = addClaims(mappedClaims);
|
||||
|
||||
Map<String, Object> mappedClaims = new HashMap<>(claims);
|
||||
for (Map.Entry<String, Converter<Object, ?>> entry : this.claimTypeConverters.entrySet()) {
|
||||
String claimName = entry.getKey();
|
||||
Converter<Object, ?> converter = entry.getValue();
|
||||
if (converter != null) {
|
||||
Object claim = claims.get(claimName);
|
||||
Object mappedClaim = converter.convert(claim);
|
||||
mappedClaims.compute(claimName, (key, value) -> mappedClaim);
|
||||
}
|
||||
}
|
||||
Instant issuedAt = (Instant) mappedClaims.get(JwtClaimNames.IAT);
|
||||
Instant expiresAt = (Instant) mappedClaims.get(JwtClaimNames.EXP);
|
||||
if (issuedAt == null && expiresAt != null) {
|
||||
@@ -159,23 +167,4 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
|
||||
return mappedClaims;
|
||||
}
|
||||
|
||||
private Map<String, Object> removeClaims(Map<String, Object> claims) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : claims.entrySet()) {
|
||||
if (entry.getValue() != null) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Object> addClaims(Map<String, Object> claims) {
|
||||
Map<String, Object> result = new HashMap<>(claims);
|
||||
for (Map.Entry<String, Converter<Object, ?>> entry : claimTypeConverters.entrySet()) {
|
||||
if (!claims.containsKey(entry.getKey()) && entry.getValue().convert(null) != null) {
|
||||
result.put(entry.getKey(), entry.getValue().convert(null));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -140,11 +140,19 @@ public class MappedJwtClaimSetConverterTests {
|
||||
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
|
||||
}
|
||||
|
||||
// gh-10135
|
||||
@Test
|
||||
public void convertWhenConverterReturnsNullThenClaimIsRemoved() {
|
||||
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
|
||||
.withDefaults(Collections.emptyMap());
|
||||
.withDefaults(Collections.singletonMap(JwtClaimNames.NBF, (nbfClaimValue) -> null));
|
||||
Map<String, Object> source = Collections.singletonMap(JwtClaimNames.NBF, Instant.now());
|
||||
Map<String, Object> target = converter.convert(source);
|
||||
assertThat(target).doesNotContainKey(JwtClaimNames.NBF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenClaimValueIsNullThenClaimIsRemoved() {
|
||||
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());
|
||||
Map<String, Object> source = Collections.singletonMap(JwtClaimNames.ISS, null);
|
||||
Map<String, Object> target = converter.convert(source);
|
||||
|
||||
|
||||
+7
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
* Copyright 2019-2021 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.
|
||||
@@ -104,15 +104,18 @@ class PayloadInterceptorRSocket extends RSocketProxy implements ResponderRSocket
|
||||
return intercept(PayloadExchangeType.REQUEST_CHANNEL, firstPayload)
|
||||
.flatMapMany(context ->
|
||||
innerFlux
|
||||
.skip(1)
|
||||
.flatMap(p -> intercept(PayloadExchangeType.PAYLOAD, p).thenReturn(p))
|
||||
.transform(securedPayloads -> Flux.concat(Flux.just(firstPayload), securedPayloads))
|
||||
.index()
|
||||
.concatMap(tuple -> justOrIntercept(tuple.getT1(), tuple.getT2()))
|
||||
.transform(securedPayloads -> this.source.requestChannel(securedPayloads))
|
||||
.subscriberContext(context)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Payload> justOrIntercept(Long index, Payload payload) {
|
||||
return (index == 0) ? Mono.just(payload) : intercept(PayloadExchangeType.PAYLOAD, payload).thenReturn(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> metadataPush(Payload payload) {
|
||||
return intercept(PayloadExchangeType.METADATA_PUSH, payload)
|
||||
|
||||
+61
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
* Copyright 2019-2021 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.
|
||||
@@ -19,6 +19,8 @@ package org.springframework.security.rsocket.core;
|
||||
import io.rsocket.Payload;
|
||||
import io.rsocket.RSocket;
|
||||
import io.rsocket.metadata.WellKnownMimeType;
|
||||
import io.rsocket.util.ByteBufPayload;
|
||||
import io.rsocket.util.DefaultPayload;
|
||||
import io.rsocket.util.RSocketProxy;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -28,7 +30,9 @@ import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
@@ -41,6 +45,8 @@ import org.springframework.security.rsocket.core.DefaultPayloadExchange;
|
||||
import org.springframework.security.rsocket.core.PayloadInterceptorRSocket;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import reactor.util.context.Context;
|
||||
import reactor.core.CoreSubscriber;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
@@ -50,10 +56,13 @@ import reactor.test.publisher.TestPublisher;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -315,6 +324,57 @@ public class PayloadInterceptorRSocketTests {
|
||||
verify(this.delegate).requestChannel(any());
|
||||
}
|
||||
|
||||
// gh-9345
|
||||
@Test
|
||||
public void requestChannelWhenInterceptorCompletesThenAllPayloadsRetained() {
|
||||
ExecutorService executors = Executors.newSingleThreadExecutor();
|
||||
Payload payload = ByteBufPayload.create("data");
|
||||
Payload payloadTwo = ByteBufPayload.create("moredata");
|
||||
Payload payloadThree = ByteBufPayload.create("stillmoredata");
|
||||
Context ctx = Context.empty();
|
||||
Flux<Payload> payloads = this.payloadResult.flux();
|
||||
when(this.interceptor.intercept(any(), any())).thenReturn(Mono.empty())
|
||||
.thenReturn(Mono.error(() -> new AccessDeniedException("Access Denied")));
|
||||
when(this.delegate.requestChannel(any())).thenAnswer((invocation) -> {
|
||||
Flux<Payload> input = invocation.getArgument(0);
|
||||
return Flux.from(input).switchOnFirst((signal, innerFlux) -> innerFlux.map(Payload::getDataUtf8)
|
||||
.transform((data) -> Flux.<String>create((emitter) -> {
|
||||
Runnable run = () -> data.subscribe(new CoreSubscriber<String>() {
|
||||
@Override
|
||||
public void onSubscribe(Subscription s) {
|
||||
s.request(3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(String s) {
|
||||
emitter.next(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
emitter.error(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
emitter.complete();
|
||||
}
|
||||
});
|
||||
executors.execute(run);
|
||||
})).map(DefaultPayload::create));
|
||||
});
|
||||
PayloadInterceptorRSocket interceptor = new PayloadInterceptorRSocket(this.delegate,
|
||||
Arrays.asList(this.interceptor), this.metadataMimeType, this.dataMimeType, ctx);
|
||||
StepVerifier.create(interceptor.requestChannel(payloads).doOnDiscard(Payload.class, Payload::release))
|
||||
.then(() -> this.payloadResult.assertSubscribers())
|
||||
.then(() -> this.payloadResult.emit(payload, payloadTwo, payloadThree))
|
||||
.assertNext((next) -> assertThat(next.getDataUtf8()).isEqualTo(payload.getDataUtf8()))
|
||||
.verifyError(AccessDeniedException.class);
|
||||
verify(this.interceptor, times(2)).intercept(this.exchange.capture(), any());
|
||||
assertThat(this.exchange.getValue().getPayload()).isEqualTo(payloadTwo);
|
||||
verify(this.delegate).requestChannel(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestChannelWhenInterceptorErrorsThenDelegateNotSubscribed() {
|
||||
RuntimeException expected = new RuntimeException("Oops");
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['thymeleaf.version']='3.0.14.RELEASE'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-web')
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['thymeleaf.version']='3.0.14.RELEASE'
|
||||
|
||||
dependencies {
|
||||
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
compile 'org.springframework.boot:spring-boot-starter-web'
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['thymeleaf.version']='3.0.14.RELEASE'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-config')
|
||||
compile project(':spring-security-oauth2-client')
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['thymeleaf.version']='3.0.14.RELEASE'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-security-core')
|
||||
compile project(':spring-security-config')
|
||||
|
||||
@@ -18,3 +18,7 @@ dependencies {
|
||||
runtime 'ch.qos.logback:logback-classic'
|
||||
runtime 'opensymphony:sitemesh'
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -38,3 +38,7 @@ dependencies {
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -18,3 +18,7 @@ dependencies {
|
||||
|
||||
runtime 'opensymphony:sitemesh'
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -20,3 +20,7 @@ dependencies {
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -27,3 +27,7 @@ dependencies {
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -20,3 +20,7 @@ dependencies {
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -39,3 +39,7 @@ dependencies {
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -37,3 +37,7 @@ dependencies {
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -29,3 +29,7 @@ dependencies {
|
||||
|
||||
providedCompile 'javax.servlet:javax.servlet-api'
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -20,3 +20,7 @@ dependencies {
|
||||
runtime 'net.sourceforge.nekohtml:nekohtml'
|
||||
runtime 'opensymphony:sitemesh'
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -17,3 +17,7 @@ dependencies {
|
||||
|
||||
runtime 'opensymphony:sitemesh'
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -17,3 +17,7 @@ dependencies {
|
||||
|
||||
runtime 'opensymphony:sitemesh'
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -9,3 +9,7 @@ dependencies {
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -17,3 +17,7 @@ dependencies {
|
||||
|
||||
runtime 'opensymphony:sitemesh'
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ gretty {
|
||||
sslKeyStorePath = keystore
|
||||
sslKeyStorePassword = password
|
||||
jvmArgs = ["-Djavax.net.ssl.trustStore=${keystore}", "-Djavax.net.ssl.trustStorePassword=${password}"]
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
def casServer() {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -53,4 +53,5 @@ gretty {
|
||||
sslKeyStorePath = keystore
|
||||
sslKeyStorePassword = password
|
||||
jvmArgs = ["-Djavax.net.ssl.trustStore=${keystore}", "-Djavax.net.ssl.trustStorePassword=${password}"]
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -41,3 +41,7 @@ dependencies {
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ dependencies {
|
||||
|
||||
testCompile "com.google.appengine:appengine-testing:$gaeVersion"
|
||||
|
||||
testRuntime "com.google.appengine:appengine-api-labs:$gaeVersion"
|
||||
testRuntime "com.google.appengine:appengine-api-labs:1.9.88"
|
||||
}
|
||||
|
||||
appengineRun.onlyIf { !gradle.taskGraph.hasTask(appengineFunctionalTest) }
|
||||
|
||||
@@ -31,3 +31,7 @@ dependencies {
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -26,3 +26,7 @@ dependencies {
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -15,3 +15,7 @@ dependencies {
|
||||
|
||||
runtime 'opensymphony:sitemesh'
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
@@ -31,3 +31,7 @@ dependencies {
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -29,3 +29,7 @@ dependencies {
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -12,3 +12,7 @@ dependencies {
|
||||
runtime jstlDependencies
|
||||
runtime slf4jDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -14,3 +14,7 @@ dependencies {
|
||||
//jettyRun {
|
||||
// userRealms = [jettyRun.class.classLoader.loadClass('org.mortbay.jetty.security.HashUserRealm').newInstance('Preauth Realm' '$projectDir/realm.properties')]
|
||||
//}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -19,3 +19,7 @@ dependencies {
|
||||
}
|
||||
|
||||
eclipse.wtp.component.contextPath = 'servletapi'
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
@@ -14,3 +14,7 @@ dependencies {
|
||||
runtime project(':spring-security-web')
|
||||
runtime jstlDependencies
|
||||
}
|
||||
|
||||
gretty {
|
||||
logbackConfigFile = "src/main/resources/logback.xml"
|
||||
}
|
||||
|
||||
+18
-25
@@ -34,6 +34,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.authentication.AccountExpiredException;
|
||||
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
@@ -46,6 +47,7 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsChecker;
|
||||
@@ -171,8 +173,10 @@ public class SwitchUserFilter extends GenericFilterBean
|
||||
Authentication targetUser = attemptSwitchUser(request);
|
||||
|
||||
// update the current context to the new target user
|
||||
SecurityContextHolder.getContext().setAuthentication(targetUser);
|
||||
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
context.setAuthentication(targetUser);
|
||||
SecurityContextHolder.setContext(context);
|
||||
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser));
|
||||
// redirect to target url
|
||||
this.successHandler.onAuthenticationSuccess(request, response,
|
||||
targetUser);
|
||||
@@ -189,14 +193,17 @@ public class SwitchUserFilter extends GenericFilterBean
|
||||
Authentication originalUser = attemptExitUser(request);
|
||||
|
||||
// update the current context back to the original user
|
||||
SecurityContextHolder.getContext().setAuthentication(originalUser);
|
||||
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
context.setAuthentication(originalUser);
|
||||
SecurityContextHolder.setContext(context);
|
||||
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", originalUser));
|
||||
// redirect to target url
|
||||
this.successHandler.onAuthenticationSuccess(request, response, originalUser);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.trace(LogMessage.format("Did not attempt to switch user since request did not match [%s] or [%s]",
|
||||
this.switchUserMatcher, this.exitUserMatcher));
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@@ -218,25 +225,13 @@ public class SwitchUserFilter extends GenericFilterBean
|
||||
UsernamePasswordAuthenticationToken targetUserRequest;
|
||||
|
||||
String username = request.getParameter(this.usernameParameter);
|
||||
|
||||
if (username == null) {
|
||||
username = "";
|
||||
}
|
||||
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("Attempt to switch to user [" + username + "]");
|
||||
}
|
||||
|
||||
username = (username != null) ? username : "";
|
||||
this.logger.debug(LogMessage.format("Attempting to switch to user [%s]", username));
|
||||
UserDetails targetUser = this.userDetailsService.loadUserByUsername(username);
|
||||
this.userDetailsChecker.check(targetUser);
|
||||
|
||||
// OK, create the switch user token
|
||||
targetUserRequest = createSwitchUserToken(request, targetUser);
|
||||
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("Switch User Token [" + targetUserRequest + "]");
|
||||
}
|
||||
|
||||
// publish event
|
||||
if (this.eventPublisher != null) {
|
||||
this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
|
||||
@@ -273,10 +268,9 @@ public class SwitchUserFilter extends GenericFilterBean
|
||||
Authentication original = getSourceAuthentication(current);
|
||||
|
||||
if (original == null) {
|
||||
this.logger.debug("Could not find original user Authentication object!");
|
||||
throw new AuthenticationCredentialsNotFoundException(
|
||||
this.messages.getMessage("SwitchUserFilter.noOriginalAuthentication",
|
||||
"Could not find original Authentication object"));
|
||||
this.logger.debug("Failed to find original user");
|
||||
throw new AuthenticationCredentialsNotFoundException(this.messages
|
||||
.getMessage("SwitchUserFilter.noOriginalAuthentication", "Failed to find original user"));
|
||||
}
|
||||
|
||||
// get the source user details
|
||||
@@ -373,8 +367,7 @@ public class SwitchUserFilter extends GenericFilterBean
|
||||
// check for switch user type of authority
|
||||
if (auth instanceof SwitchUserGrantedAuthority) {
|
||||
original = ((SwitchUserGrantedAuthority) auth).getSource();
|
||||
this.logger.debug("Found original switch user granted authority ["
|
||||
+ original + "]");
|
||||
this.logger.debug(LogMessage.format("Found original switch user granted authority [%s]", original));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -174,17 +174,18 @@ public final class CsrfFilter extends OncePerRequestFilter {
|
||||
* @return
|
||||
*/
|
||||
private static boolean equalsConstantTime(String expected, String actual) {
|
||||
byte[] expectedBytes = bytesUtf8(expected);
|
||||
byte[] actualBytes = bytesUtf8(actual);
|
||||
if (expected == actual) {
|
||||
return true;
|
||||
}
|
||||
if (expected == null || actual == null) {
|
||||
return false;
|
||||
}
|
||||
// Encode after ensure that the string is not null
|
||||
byte[] expectedBytes = Utf8.encode(expected);
|
||||
byte[] actualBytes = Utf8.encode(actual);
|
||||
return MessageDigest.isEqual(expectedBytes, actualBytes);
|
||||
}
|
||||
|
||||
private static byte[] bytesUtf8(String s) {
|
||||
// need to check if Utf8.encode() runs in constant time (probably not).
|
||||
// This may leak length of string.
|
||||
return (s != null) ? Utf8.encode(s) : null;
|
||||
}
|
||||
|
||||
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
|
||||
|
||||
private final HashSet<String> allowedMethods = new HashSet<>(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -25,6 +25,6 @@ package org.springframework.security.web.csrf;
|
||||
public class MissingCsrfTokenException extends CsrfException {
|
||||
|
||||
public MissingCsrfTokenException(String actualToken) {
|
||||
super("Could not verify the provided CSRF token because your session was not found.");
|
||||
super("Could not verify the provided CSRF token because no token was found to compare.");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -177,17 +177,18 @@ public class CsrfWebFilter implements WebFilter {
|
||||
* @return
|
||||
*/
|
||||
private static boolean equalsConstantTime(String expected, String actual) {
|
||||
byte[] expectedBytes = bytesUtf8(expected);
|
||||
byte[] actualBytes = bytesUtf8(actual);
|
||||
if (expected == actual) {
|
||||
return true;
|
||||
}
|
||||
if (expected == null || actual == null) {
|
||||
return false;
|
||||
}
|
||||
// Encode after ensure that the string is not null
|
||||
byte[] expectedBytes = Utf8.encode(expected);
|
||||
byte[] actualBytes = Utf8.encode(actual);
|
||||
return MessageDigest.isEqual(expectedBytes, actualBytes);
|
||||
}
|
||||
|
||||
private static byte[] bytesUtf8(String s) {
|
||||
// need to check if Utf8.encode() runs in constant time (probably not).
|
||||
// This may leak length of string.
|
||||
return (s != null) ? Utf8.encode(s) : null;
|
||||
}
|
||||
|
||||
private Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
|
||||
return this.csrfTokenRepository.generateToken(exchange)
|
||||
.delayUntil((token) -> this.csrfTokenRepository.saveToken(exchange, token));
|
||||
|
||||
+12
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 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,7 +16,6 @@
|
||||
package org.springframework.security.web.server.header;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
@@ -42,11 +41,17 @@ public class StaticServerHttpHeadersWriter implements ServerHttpHeadersWriter {
|
||||
@Override
|
||||
public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) {
|
||||
HttpHeaders headers = exchange.getResponse().getHeaders();
|
||||
boolean containsOneHeaderToAdd = Collections.disjoint(headers.keySet(), this.headersToAdd.keySet());
|
||||
if (containsOneHeaderToAdd) {
|
||||
this.headersToAdd.forEach((name, values) -> {
|
||||
headers.put(name, values);
|
||||
});
|
||||
// Note: We need to ensure that the following algorithm compares headers
|
||||
// case insensitively, which should be true of headers.containsKey().
|
||||
boolean containsNoHeadersToAdd = true;
|
||||
for (String headerName : this.headersToAdd.keySet()) {
|
||||
if (headers.containsKey(headerName)) {
|
||||
containsNoHeadersToAdd = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (containsNoHeadersToAdd) {
|
||||
this.headersToAdd.forEach(headers::put);
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
+4
-2
@@ -46,8 +46,10 @@ public final class SimpleRedirectInvalidSessionStrategy implements InvalidSessio
|
||||
|
||||
public void onInvalidSessionDetected(HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException {
|
||||
logger.debug("Starting new session (if required) and redirecting to '"
|
||||
+ destinationUrl + "'");
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Starting new session (if required) and redirecting to '"
|
||||
+ destinationUrl + "'");
|
||||
}
|
||||
if (createNewSession) {
|
||||
request.getSession();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2021 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,6 +16,7 @@
|
||||
package org.springframework.security.web.csrf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
@@ -89,6 +90,18 @@ public class CsrfFilterTests {
|
||||
this.response = new MockHttpServletResponse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullConstantTimeEquals() throws Exception {
|
||||
Method method = CsrfFilter.class.getDeclaredMethod("equalsConstantTime", String.class, String.class);
|
||||
method.setAccessible(true);
|
||||
assertThat(method.invoke(CsrfFilter.class, null, null)).isEqualTo(true);
|
||||
String expectedToken = "Hello—World";
|
||||
String actualToken = new String("Hello—World");
|
||||
assertThat(method.invoke(CsrfFilter.class, expectedToken, null)).isEqualTo(false);
|
||||
assertThat(method.invoke(CsrfFilter.class, expectedToken, "hello-world")).isEqualTo(false);
|
||||
assertThat(method.invoke(CsrfFilter.class, expectedToken, actualToken)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorNullRepository() {
|
||||
new CsrfFilter(null);
|
||||
|
||||
+15
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.web.server.csrf;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
@@ -67,6 +69,18 @@ public class CsrfWebFilterTests {
|
||||
|
||||
private MockServerWebExchange post = MockServerWebExchange.from(MockServerHttpRequest.post("/"));
|
||||
|
||||
@Test
|
||||
public void nullConstantTimeEquals() throws Exception {
|
||||
Method method = CsrfWebFilter.class.getDeclaredMethod("equalsConstantTime", String.class, String.class);
|
||||
method.setAccessible(true);
|
||||
assertThat(method.invoke(CsrfWebFilter.class, null, null)).isEqualTo(true);
|
||||
String expectedToken = "Hello—World";
|
||||
String actualToken = new String("Hello—World");
|
||||
assertThat(method.invoke(CsrfWebFilter.class, expectedToken, null)).isEqualTo(false);
|
||||
assertThat(method.invoke(CsrfWebFilter.class, expectedToken, "hello-world")).isEqualTo(false);
|
||||
assertThat(method.invoke(CsrfWebFilter.class, expectedToken, actualToken)).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterWhenGetThenSessionNotCreatedAndChainContinues() {
|
||||
PublisherProbe<Void> chainResult = PublisherProbe.empty();
|
||||
|
||||
+22
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -17,10 +17,13 @@ package org.springframework.security.web.server.header;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
@@ -56,6 +59,24 @@ public class StaticServerHttpHeadersWriterTests {
|
||||
assertThat(headers.get(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)).containsOnly(headerValue);
|
||||
}
|
||||
|
||||
// gh-10557
|
||||
@Test
|
||||
public void writeHeadersWhenHeaderWrittenWithDifferentCaseThenDoesNotWriteHeaders() {
|
||||
String headerName = HttpHeaders.CACHE_CONTROL.toLowerCase(Locale.ROOT);
|
||||
String headerValue = "max-age=120";
|
||||
this.headers.set(headerName, headerValue);
|
||||
// Note: This test inverts which collection uses case sensitive headers,
|
||||
// due to the fact that gh-10557 reports NettyHeadersAdapter as the
|
||||
// response headers implementation, which is not accessible here.
|
||||
HttpHeaders caseSensitiveHeaders = new HttpHeaders(new LinkedMultiValueMap<>());
|
||||
caseSensitiveHeaders.set(HttpHeaders.CACHE_CONTROL, CacheControlServerHttpHeadersWriter.CACHE_CONTRTOL_VALUE);
|
||||
caseSensitiveHeaders.set(HttpHeaders.PRAGMA, CacheControlServerHttpHeadersWriter.PRAGMA_VALUE);
|
||||
caseSensitiveHeaders.set(HttpHeaders.EXPIRES, CacheControlServerHttpHeadersWriter.EXPIRES_VALUE);
|
||||
this.writer = new StaticServerHttpHeadersWriter(caseSensitiveHeaders);
|
||||
this.writer.writeHttpHeaders(this.exchange);
|
||||
assertThat(this.headers.get(headerName)).containsOnly(headerValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeHeadersWhenMultiHeaderThenWritesAllHeaders() {
|
||||
writer = StaticServerHttpHeadersWriter.builder()
|
||||
|
||||
Reference in New Issue
Block a user