Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 974156d5fb | |||
| e2121532a2 | |||
| 7cab7b06c5 | |||
| f60daa5152 | |||
| f63b770ec5 | |||
| 44bb975f82 | |||
| 3cb6b3e5d6 | |||
| db07cea579 | |||
| 0fb60c3aa7 | |||
| 31cb29cb2d | |||
| 46bfc00db2 | |||
| 987b14d1d4 | |||
| 1f19ee04e1 | |||
| 005eca7bd9 | |||
| 68ac3ef36b | |||
| 7a5c34ca57 | |||
| 542c625d7d | |||
| 4de2dbb4cd | |||
| 98399c920a | |||
| 52ad49074d | |||
| 6df5dc4ecf | |||
| 32acb04efe | |||
| 57dfbeecbb | |||
| 1181740f79 | |||
| 628ea00ad4 | |||
| 6dc22835fd | |||
| 174b71c017 | |||
| c7b4e4a493 | |||
| d03d31c2e7 | |||
| 9976eb9e3a | |||
| 7cf1ca7ab1 | |||
| 664d9f1ba6 | |||
| f7b160d7a8 | |||
| 5b60098548 | |||
| e6b383a9c4 | |||
| 5342133494 | |||
| 7498ad95a1 | |||
| 04d382d8d8 | |||
| 83a177d327 | |||
| ebb2afa5e8 | |||
| 276a934bcd | |||
| e4b538146a | |||
| 5948f492f5 | |||
| 1bac5498dd | |||
| 07479dce6d | |||
| 05c0176b98 | |||
| 03c2cc846d | |||
| ec7deca76f | |||
| 21c7f7518f | |||
| 0462c42290 | |||
| 21f7187f40 |
Vendored
+39
-54
@@ -8,6 +8,10 @@ properties(projectProperties)
|
||||
def SUCCESS = hudson.model.Result.SUCCESS.toString()
|
||||
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: {
|
||||
stage('Check') {
|
||||
@@ -15,14 +19,15 @@ try {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
sh "./gradlew clean check --refresh-dependencies --no-daemon --stacktrace"
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,11 +39,13 @@ try {
|
||||
sh "git clean -dfx"
|
||||
withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) {
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
if ("master" == env.BRANCH_NAME) {
|
||||
sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace"
|
||||
} else {
|
||||
sh "./gradlew sonarqube -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"
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
if ("master" == env.BRANCH_NAME) {
|
||||
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 $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"
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
@@ -55,8 +62,10 @@ try {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
sh "./gradlew clean test -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
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) {
|
||||
currentBuild.result = 'FAILED: snapshots'
|
||||
@@ -65,46 +74,16 @@ try {
|
||||
}
|
||||
}
|
||||
},
|
||||
jdk9: {
|
||||
stage('JDK 9') {
|
||||
node {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk9' }"]) {
|
||||
sh "./gradlew clean test --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 {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk10' }"]) {
|
||||
sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: jdk10'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
jdk11: {
|
||||
stage('JDK 11') {
|
||||
node {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) {
|
||||
sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace"
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) {
|
||||
sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: jdk11'
|
||||
@@ -119,8 +98,10 @@ try {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
try {
|
||||
withEnv(["JAVA_HOME=${ tool 'openjdk12' }"]) {
|
||||
sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace"
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'openjdk12' }"]) {
|
||||
sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: jdk12'
|
||||
@@ -138,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: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,8 +137,10 @@ try {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
sh "./gradlew $JENKINS_USER deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,8 +152,10 @@ try {
|
||||
checkout scm
|
||||
sh "git clean -dfx"
|
||||
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
|
||||
withCredentials([ARTIFACTORY_CREDENTIALS]) {
|
||||
withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
|
||||
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.
|
||||
|
||||
+27
-3
@@ -1,12 +1,20 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.23.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 {
|
||||
maven { url 'https://repo.spring.io/plugins-snapshot' }
|
||||
maven {
|
||||
url = 'https://repo.spring.io/plugins-snapshot'
|
||||
if (project.hasProperty('artifactoryUsername')) {
|
||||
credentials {
|
||||
username "$artifactoryUsername"
|
||||
password "$artifactoryPassword"
|
||||
}
|
||||
}
|
||||
}
|
||||
maven { url 'https://plugins.gradle.org/m2/' }
|
||||
}
|
||||
}
|
||||
@@ -35,3 +43,19 @@ subprojects {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
}
|
||||
|
||||
if (project.hasProperty('artifactoryUsername')) {
|
||||
allprojects { project ->
|
||||
project.repositories { repos ->
|
||||
all { repo ->
|
||||
if (!repo.url.toString().startsWith("https://repo.spring.io/")) {
|
||||
return;
|
||||
}
|
||||
repo.credentials {
|
||||
username = artifactoryUsername
|
||||
password = artifactoryPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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) {
|
||||
+69
-21
@@ -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
|
||||
@@ -1933,9 +1941,9 @@ public final class HttpSecurity extends
|
||||
*
|
||||
* private RelyingPartyRegistration getSaml2RelyingPartyRegistration() {
|
||||
* //remote IDP entity ID
|
||||
* String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
|
||||
* String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php";
|
||||
* //remote WebSSO Endpoint - Where to Send AuthNRequests to
|
||||
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
|
||||
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php";
|
||||
* //local registration ID
|
||||
* String registrationId = "simplesamlphp";
|
||||
* //local entity ID - autogenerated based on URL
|
||||
@@ -2024,9 +2032,9 @@ public final class HttpSecurity extends
|
||||
*
|
||||
* private RelyingPartyRegistration getSaml2RelyingPartyRegistration() {
|
||||
* //remote IDP entity ID
|
||||
* String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
|
||||
* String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php";
|
||||
* //remote WebSSO Endpoint - Where to Send AuthNRequests to
|
||||
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
|
||||
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php";
|
||||
* //local registration ID
|
||||
* String registrationId = "simplesamlphp";
|
||||
* //local entity ID - autogenerated based on URL
|
||||
@@ -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)
|
||||
|
||||
+3
-3
@@ -822,19 +822,19 @@ public class MiscHttpConfigTests {
|
||||
@RestController
|
||||
static class AuthenticationController {
|
||||
@GetMapping("/password")
|
||||
public String password(@AuthenticationPrincipal Authentication authentication) {
|
||||
public String password(Authentication authentication) {
|
||||
return (String) authentication.getCredentials();
|
||||
}
|
||||
|
||||
@GetMapping("/roles")
|
||||
public String roles(@AuthenticationPrincipal Authentication authentication) {
|
||||
public String roles(Authentication authentication) {
|
||||
return authentication.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
@GetMapping("/details")
|
||||
public String details(@AuthenticationPrincipal Authentication authentication) {
|
||||
public String details(Authentication authentication) {
|
||||
return authentication.getDetails().getClass().getName();
|
||||
}
|
||||
}
|
||||
|
||||
+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,
|
||||
|
||||
+1
-1
@@ -519,7 +519,7 @@ public class OAuth2ResourceServerSpecTests {
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class PlaceholderConfig {
|
||||
@Value("${classpath:org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests-simple.pub}")
|
||||
@Value("classpath:org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests-simple.pub")
|
||||
RSAPublicKey key;
|
||||
|
||||
@Bean
|
||||
|
||||
+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
|
||||
|
||||
@@ -233,7 +233,7 @@ You cannot have two separate sessions at once.
|
||||
So if you log in again in another window or tab you are just reauthenticating in the same session.
|
||||
The server doesn't know anything about tabs, windows or browser instances.
|
||||
All it sees are HTTP requests and it ties those to a particular session according to the value of the JSESSIONID cookie that they contain.
|
||||
When a user authenticates during a session, Spring Security's concurrent session control checks the number of__other authenticated sessions__ that they have.
|
||||
When a user authenticates during a session, Spring Security's concurrent session control checks the number of __other authenticated sessions__ that they have.
|
||||
If they are already authenticated with the same session, then re-authenticating will have no effect.
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
= Appendix
|
||||
|
||||
include::database-schema.adoc[]
|
||||
include::database-schema.adoc[leveloffset=+1]
|
||||
|
||||
include::namespace.adoc[]
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -270,9 +270,9 @@ spring:
|
||||
saml2:
|
||||
login:
|
||||
relying-parties:
|
||||
- entity-id: &idp-entity-id https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php
|
||||
- entity-id: &idp-entity-id https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php
|
||||
registration-id: simplesamlphp
|
||||
web-sso-url: &idp-sso-url https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php
|
||||
web-sso-url: &idp-sso-url https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php
|
||||
signing-credentials: &service-provider-credentials
|
||||
- private-key: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
aspectjVersion=1.9.6
|
||||
gaeVersion=1.9.82
|
||||
springBootVersion=2.2.10.RELEASE
|
||||
version=5.2.7.RELEASE
|
||||
aspectjVersion=1.9.7
|
||||
gaeVersion=1.9.93
|
||||
springBootVersion=2.2.13.RELEASE
|
||||
version=5.2.14.RELEASE
|
||||
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
if (!project.hasProperty('reactorVersion')) {
|
||||
ext.reactorVersion = 'Dysprosium-SR12'
|
||||
ext.reactorVersion = 'Dysprosium-SR25'
|
||||
}
|
||||
|
||||
if (!project.hasProperty('springVersion')) {
|
||||
ext.springVersion = '5.2.9.RELEASE'
|
||||
ext.springVersion = '5.2.19.RELEASE'
|
||||
}
|
||||
|
||||
if (!project.hasProperty('springDataVersion')) {
|
||||
ext.springDataVersion = 'Moore-SR10'
|
||||
ext.springDataVersion = 'Moore-SR13'
|
||||
}
|
||||
|
||||
ext.rsocketVersion = '1.0.2'
|
||||
ext.rsocketVersion = '1.0.5'
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
@@ -24,12 +24,12 @@ dependencyManagement {
|
||||
dependency 'opensymphony:sitemesh:2.4.2'
|
||||
dependency 'org.gebish:geb-spock:0.10.0'
|
||||
dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
|
||||
dependency 'org.powermock:powermock-api-mockito2:2.0.7'
|
||||
dependency 'org.powermock:powermock-api-support:2.0.7'
|
||||
dependency 'org.powermock:powermock-core:2.0.7'
|
||||
dependency 'org.powermock:powermock-module-junit4-common:2.0.7'
|
||||
dependency 'org.powermock:powermock-module-junit4:2.0.7'
|
||||
dependency 'org.powermock:powermock-reflect:2.0.7'
|
||||
dependency 'org.powermock:powermock-api-mockito2:2.0.9'
|
||||
dependency 'org.powermock:powermock-api-support:2.0.9'
|
||||
dependency 'org.powermock:powermock-core:2.0.9'
|
||||
dependency 'org.powermock:powermock-module-junit4-common:2.0.9'
|
||||
dependency 'org.powermock:powermock-module-junit4:2.0.9'
|
||||
dependency 'org.powermock:powermock-reflect:2.0.9'
|
||||
dependency 'org.python:jython:2.5.3'
|
||||
dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
|
||||
dependency 'org.spockframework:spock-spring:1.0-groovy-2.4'
|
||||
@@ -44,11 +44,11 @@ dependencyManagement {
|
||||
dependency 'ch.qos.logback:logback-core:1.2.3'
|
||||
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'
|
||||
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'
|
||||
@@ -138,41 +138,41 @@ dependencyManagement {
|
||||
dependency 'org.apache.directory.shared:shared-cursor:0.9.15'
|
||||
dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15'
|
||||
dependency 'org.apache.directory.shared:shared-ldap:0.9.15'
|
||||
dependency 'org.apache.httpcomponents:httpclient:4.5.12'
|
||||
dependency 'org.apache.httpcomponents:httpcore:4.4.8'
|
||||
dependency 'org.apache.httpcomponents:httpmime:4.5.3'
|
||||
dependency 'org.apache.httpcomponents:httpclient:4.5.13'
|
||||
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.38'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.38'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.38'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.38'
|
||||
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.57'
|
||||
dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.38'
|
||||
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.27.v20200227'
|
||||
dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.27.v20200227'
|
||||
dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.27.v20200227'
|
||||
dependency 'org.eclipse.jetty:jetty-client:9.4.27.v20200227'
|
||||
dependency 'org.eclipse.jetty:jetty-http:9.4.27.v20200227'
|
||||
dependency 'org.eclipse.jetty:jetty-io:9.4.27.v20200227'
|
||||
dependency 'org.eclipse.jetty:jetty-security:9.4.27.v20200227'
|
||||
dependency 'org.eclipse.jetty:jetty-server:9.4.27.v20200227'
|
||||
dependency 'org.eclipse.jetty:jetty-servlet:9.4.27.v20200227'
|
||||
dependency 'org.eclipse.jetty:jetty-util:9.4.27.v20200227'
|
||||
dependency 'org.eclipse.jetty:jetty-xml:9.4.27.v20200227'
|
||||
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.22.Final'
|
||||
dependency 'org.hibernate:hibernate-validator:6.1.6.Final'
|
||||
dependency 'org.hsqldb:hsqldb:2.5.0'
|
||||
dependency 'org.hibernate:hibernate-entitymanager:5.4.33'
|
||||
dependency 'org.hibernate:hibernate-validator:6.1.7.Final'
|
||||
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.2.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.11.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);
|
||||
}
|
||||
|
||||
|
||||
+7
-9
@@ -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.
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.userinfo;
|
||||
|
||||
|
||||
@@ -22,6 +21,11 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.nimbusds.oauth2.sdk.ErrorObject;
|
||||
import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
|
||||
import net.minidev.json.JSONObject;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -41,12 +45,6 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import com.nimbusds.oauth2.sdk.ErrorObject;
|
||||
import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
|
||||
|
||||
import net.minidev.json.JSONObject;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link ReactiveOAuth2UserService} that supports standard OAuth 2.0 Provider's.
|
||||
* <p>
|
||||
@@ -119,7 +117,7 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
|
||||
}
|
||||
Mono<Map<String, Object>> userAttributes = requestHeadersSpec
|
||||
.retrieve()
|
||||
.onStatus(s -> s != HttpStatus.OK, response -> parse(response).map(userInfoErrorResponse -> {
|
||||
.onStatus(HttpStatus::isError, response -> parse(response).map(userInfoErrorResponse -> {
|
||||
String description = userInfoErrorResponse.getErrorObject().getDescription();
|
||||
OAuth2Error oauth2Error = new OAuth2Error(
|
||||
INVALID_USER_INFO_RESPONSE_ERROR_CODE, description,
|
||||
|
||||
+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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
-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.
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.userinfo;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -50,6 +49,7 @@ import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -152,6 +152,24 @@ public class DefaultReactiveOAuth2UserServiceTests {
|
||||
assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes());
|
||||
}
|
||||
|
||||
// gh-9336
|
||||
@Test
|
||||
public void loadUserWhenUserInfo201CreatedResponseThenReturnUser() {
|
||||
// @formatter:off
|
||||
String userInfoResponse = "{\n"
|
||||
+ " \"id\": \"user1\",\n"
|
||||
+ " \"first-name\": \"first\",\n"
|
||||
+ " \"last-name\": \"last\",\n"
|
||||
+ " \"middle-name\": \"middle\",\n"
|
||||
+ " \"address\": \"address\",\n"
|
||||
+ " \"email\": \"user1@example.com\"\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
this.server.enqueue(new MockResponse().setResponseCode(201)
|
||||
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(userInfoResponse));
|
||||
assertThatCode(() -> this.userService.loadUser(oauth2UserRequest()).block()).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// gh-5500
|
||||
@Test
|
||||
public void loadUserWhenAuthenticationMethodHeaderSuccessResponseThenHttpMethodGet() throws Exception {
|
||||
|
||||
+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.
|
||||
|
||||
-6
@@ -56,12 +56,6 @@ final class ObjectToListStringConverter implements ConditionalGenericConverter {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
if (source instanceof List) {
|
||||
List<?> sourceList = (List<?>) source;
|
||||
if (!sourceList.isEmpty() && sourceList.get(0) instanceof String) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
if (source instanceof Collection) {
|
||||
Collection<String> results = new ArrayList<>();
|
||||
for (Object object : ((Collection<?>) source)) {
|
||||
|
||||
-3
@@ -52,9 +52,6 @@ final class ObjectToMapStringObjectConverter implements ConditionalGenericConver
|
||||
return null;
|
||||
}
|
||||
Map<?, ?> sourceMap = (Map<?, ?>) source;
|
||||
if (!sourceMap.isEmpty() && sourceMap.keySet().iterator().next() instanceof String) {
|
||||
return source;
|
||||
}
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
sourceMap.forEach((k, v) -> result.put(k.toString(), v));
|
||||
return result;
|
||||
|
||||
+8
-4
@@ -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.
|
||||
@@ -30,6 +30,8 @@ import java.util.SortedSet;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
|
||||
/**
|
||||
* The default implementation of an {@link OAuth2User}.
|
||||
*
|
||||
@@ -59,14 +61,16 @@ public class DefaultOAuth2User implements OAuth2User, Serializable {
|
||||
* @param attributes the attributes about the user
|
||||
* @param nameAttributeKey the key used to access the user's "name" from {@link #getAttributes()}
|
||||
*/
|
||||
public DefaultOAuth2User(Collection<? extends GrantedAuthority> authorities, Map<String, Object> attributes, String nameAttributeKey) {
|
||||
Assert.notEmpty(authorities, "authorities cannot be empty");
|
||||
public DefaultOAuth2User(Collection<? extends GrantedAuthority> authorities, Map<String, Object> attributes,
|
||||
String nameAttributeKey) {
|
||||
Assert.notEmpty(attributes, "attributes cannot be empty");
|
||||
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty");
|
||||
if (!attributes.containsKey(nameAttributeKey)) {
|
||||
throw new IllegalArgumentException("Missing attribute '" + nameAttributeKey + "' in attributes");
|
||||
}
|
||||
this.authorities = Collections.unmodifiableSet(new LinkedHashSet<>(this.sortAuthorities(authorities)));
|
||||
this.authorities = (authorities != null)
|
||||
? Collections.unmodifiableSet(new LinkedHashSet<>(this.sortAuthorities(authorities)))
|
||||
: Collections.unmodifiableSet(new LinkedHashSet<>(AuthorityUtils.NO_AUTHORITIES));
|
||||
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
|
||||
this.nameAttributeKey = nameAttributeKey;
|
||||
}
|
||||
|
||||
+38
-7
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.core.converter;
|
||||
|
||||
import net.minidev.json.JSONArray;
|
||||
import net.minidev.json.JSONObject;
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
@@ -29,6 +30,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
@@ -141,9 +144,9 @@ public class ClaimConversionServiceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertCollectionStringWhenListStringThenReturnSame() {
|
||||
public void convertCollectionStringWhenListStringThenReturnNotSameButEqual() {
|
||||
List<String> list = Lists.list("1", "2", "3", "4");
|
||||
assertThat(this.conversionService.convert(list, Collection.class)).isSameAs(list);
|
||||
assertThat(this.conversionService.convert(list, Collection.class)).isNotSameAs(list).isEqualTo(list);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -152,6 +155,17 @@ public class ClaimConversionServiceTests {
|
||||
.isEqualTo(Lists.list("1", "2", "3", "4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertListStringWhenJsonArrayThenConverts() {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
jsonArray.add("1");
|
||||
jsonArray.add("2");
|
||||
jsonArray.add("3");
|
||||
jsonArray.add(null);
|
||||
assertThat(this.conversionService.convert(jsonArray, List.class)).isNotInstanceOf(JSONArray.class)
|
||||
.isEqualTo(Lists.list("1", "2", "3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertCollectionStringWhenNotConvertibleThenReturnSingletonList() {
|
||||
String string = "not-convertible-collection";
|
||||
@@ -165,9 +179,9 @@ public class ClaimConversionServiceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertListStringWhenListStringThenReturnSame() {
|
||||
public void convertListStringWhenListStringThenReturnNotSameButEqual() {
|
||||
List<String> list = Lists.list("1", "2", "3", "4");
|
||||
assertThat(this.conversionService.convert(list, List.class)).isSameAs(list);
|
||||
assertThat(this.conversionService.convert(list, List.class)).isNotSameAs(list).isEqualTo(list);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -189,7 +203,7 @@ public class ClaimConversionServiceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertMapStringObjectWhenMapStringObjectThenReturnSame() {
|
||||
public void convertMapStringObjectWhenMapStringObjectThenReturnNotSameButEqual() {
|
||||
Map<String, Object> mapStringObject = new HashMap<String, Object>() {
|
||||
{
|
||||
put("key1", "value1");
|
||||
@@ -197,7 +211,8 @@ public class ClaimConversionServiceTests {
|
||||
put("key3", "value3");
|
||||
}
|
||||
};
|
||||
assertThat(this.conversionService.convert(mapStringObject, Map.class)).isSameAs(mapStringObject);
|
||||
assertThat(this.conversionService.convert(mapStringObject, Map.class)).isNotSameAs(mapStringObject)
|
||||
.isEqualTo(mapStringObject);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -219,6 +234,22 @@ public class ClaimConversionServiceTests {
|
||||
assertThat(this.conversionService.convert(mapIntegerObject, Map.class)).isEqualTo(mapStringObject);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertMapStringObjectWhenJsonObjectThenConverts() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("1", "value1");
|
||||
jsonObject.put("2", "value2");
|
||||
|
||||
Map<String, Object> mapStringObject = new HashMap<String, Object>() {
|
||||
{
|
||||
put("1", "value1");
|
||||
put("2", "value2");
|
||||
}
|
||||
};
|
||||
assertThat(this.conversionService.convert(jsonObject, Map.class)).isNotInstanceOf(JSONObject.class)
|
||||
.isEqualTo(mapStringObject);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertMapStringObjectWhenNotConvertibleThenReturnNull() {
|
||||
List<String> notConvertibleList = Lists.list("1", "2", "3", "4");
|
||||
|
||||
+18
-5
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.core.converter;
|
||||
|
||||
import net.minidev.json.JSONArray;
|
||||
import net.minidev.json.JSONObject;
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.assertj.core.util.Maps;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
@@ -45,6 +48,8 @@ public class ClaimTypeConverterTests {
|
||||
private static final String COLLECTION_STRING_CLAIM = "collection-string-claim";
|
||||
private static final String LIST_STRING_CLAIM = "list-string-claim";
|
||||
private static final String MAP_STRING_OBJECT_CLAIM = "map-string-object-claim";
|
||||
private static final String JSON_ARRAY_CLAIM = "json-array-claim";
|
||||
private static final String JSON_OBJECT_CLAIM = "json-object-claim";
|
||||
private ClaimTypeConverter claimTypeConverter;
|
||||
|
||||
@Before
|
||||
@@ -107,7 +112,12 @@ public class ClaimTypeConverterTests {
|
||||
mapIntegerObject.put(1, "value1");
|
||||
Map<String, Object> mapStringObject = new HashMap<>();
|
||||
mapStringObject.put("1", "value1");
|
||||
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
jsonArray.add("1");
|
||||
List<String> jsonArrayListString = Lists.list("1");
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("1", "value1");
|
||||
Map<String, Object> jsonObjectMap = Maps.newHashMap("1", "value1");
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(STRING_CLAIM, Boolean.TRUE);
|
||||
claims.put(BOOLEAN_CLAIM, "true");
|
||||
@@ -116,7 +126,8 @@ public class ClaimTypeConverterTests {
|
||||
claims.put(COLLECTION_STRING_CLAIM, listNumber);
|
||||
claims.put(LIST_STRING_CLAIM, listNumber);
|
||||
claims.put(MAP_STRING_OBJECT_CLAIM, mapIntegerObject);
|
||||
|
||||
claims.put(JSON_ARRAY_CLAIM, jsonArray);
|
||||
claims.put(JSON_OBJECT_CLAIM, jsonObject);
|
||||
claims = this.claimTypeConverter.convert(claims);
|
||||
|
||||
assertThat(claims.get(STRING_CLAIM)).isEqualTo("true");
|
||||
@@ -126,6 +137,8 @@ public class ClaimTypeConverterTests {
|
||||
assertThat(claims.get(COLLECTION_STRING_CLAIM)).isEqualTo(listString);
|
||||
assertThat(claims.get(LIST_STRING_CLAIM)).isEqualTo(listString);
|
||||
assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isEqualTo(mapStringObject);
|
||||
assertThat(claims.get(JSON_ARRAY_CLAIM)).isEqualTo(jsonArrayListString);
|
||||
assertThat(claims.get(JSON_OBJECT_CLAIM)).isEqualTo(jsonObjectMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -153,9 +166,9 @@ public class ClaimTypeConverterTests {
|
||||
assertThat(claims.get(BOOLEAN_CLAIM)).isSameAs(bool);
|
||||
assertThat(claims.get(INSTANT_CLAIM)).isSameAs(instant);
|
||||
assertThat(claims.get(URL_CLAIM)).isSameAs(url);
|
||||
assertThat(claims.get(COLLECTION_STRING_CLAIM)).isSameAs(listString);
|
||||
assertThat(claims.get(LIST_STRING_CLAIM)).isSameAs(listString);
|
||||
assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isSameAs(mapStringObject);
|
||||
assertThat(claims.get(COLLECTION_STRING_CLAIM)).isNotSameAs(listString).isEqualTo(listString);
|
||||
assertThat(claims.get(LIST_STRING_CLAIM)).isNotSameAs(listString).isEqualTo(listString);
|
||||
assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isNotSameAs(mapStringObject).isEqualTo(mapStringObject);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+22
-6
@@ -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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.core.oidc.user;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
@@ -57,11 +58,6 @@ public class DefaultOidcUserTests {
|
||||
private static final OidcIdToken ID_TOKEN = new OidcIdToken("id-token-value", Instant.EPOCH, Instant.MAX, ID_TOKEN_CLAIMS);
|
||||
private static final OidcUserInfo USER_INFO = new OidcUserInfo(USER_INFO_CLAIMS);
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenAuthoritiesIsNullThenThrowIllegalArgumentException() {
|
||||
new DefaultOidcUser(null, ID_TOKEN);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenIdTokenIsNullThenThrowIllegalArgumentException() {
|
||||
new DefaultOidcUser(AUTHORITIES, null);
|
||||
@@ -72,6 +68,26 @@ public class DefaultOidcUserTests {
|
||||
new DefaultOidcUser(AUTHORITIES, ID_TOKEN, "invalid");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthoritiesIsNullThenCreatedWithEmptyAuthorities() {
|
||||
DefaultOidcUser user = new DefaultOidcUser(null, ID_TOKEN);
|
||||
assertThat(user.getClaims()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB);
|
||||
assertThat(user.getIdToken()).isEqualTo(ID_TOKEN);
|
||||
assertThat(user.getName()).isEqualTo(SUBJECT);
|
||||
assertThat(user.getAuthorities()).isEmpty();
|
||||
assertThat(user.getAttributes()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthoritiesIsEmptyThenCreated() {
|
||||
DefaultOidcUser user = new DefaultOidcUser(AuthorityUtils.NO_AUTHORITIES, ID_TOKEN);
|
||||
assertThat(user.getClaims()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB);
|
||||
assertThat(user.getIdToken()).isEqualTo(ID_TOKEN);
|
||||
assertThat(user.getName()).isEqualTo(SUBJECT);
|
||||
assertThat(user.getAuthorities()).isEmpty();
|
||||
assertThat(user.getAttributes()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthoritiesIdTokenProvidedThenCreated() {
|
||||
DefaultOidcUser user = new DefaultOidcUser(AUTHORITIES, ID_TOKEN);
|
||||
|
||||
+17
-11
@@ -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.
|
||||
@@ -41,16 +41,6 @@ public class DefaultOAuth2UserTests {
|
||||
private static final Map<String, Object> ATTRIBUTES = Collections.singletonMap(
|
||||
ATTRIBUTE_NAME_KEY, USERNAME);
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenAuthoritiesIsNullThenThrowIllegalArgumentException() {
|
||||
new DefaultOAuth2User(null, ATTRIBUTES, ATTRIBUTE_NAME_KEY);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenAuthoritiesIsEmptyThenThrowIllegalArgumentException() {
|
||||
new DefaultOAuth2User(Collections.emptySet(), ATTRIBUTES, ATTRIBUTE_NAME_KEY);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenAttributesIsNullThenThrowIllegalArgumentException() {
|
||||
new DefaultOAuth2User(AUTHORITIES, null, ATTRIBUTE_NAME_KEY);
|
||||
@@ -71,6 +61,22 @@ public class DefaultOAuth2UserTests {
|
||||
new DefaultOAuth2User(AUTHORITIES, ATTRIBUTES, "invalid");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthoritiesIsNullThenCreatedWithEmptyAuthorities() {
|
||||
DefaultOAuth2User user = new DefaultOAuth2User(null, ATTRIBUTES, ATTRIBUTE_NAME_KEY);
|
||||
assertThat(user.getName()).isEqualTo(USERNAME);
|
||||
assertThat(user.getAuthorities()).isEmpty();
|
||||
assertThat(user.getAttributes()).containsOnlyKeys(ATTRIBUTE_NAME_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthoritiesIsEmptyThenCreated() {
|
||||
DefaultOAuth2User user = new DefaultOAuth2User(Collections.emptySet(), ATTRIBUTES, ATTRIBUTE_NAME_KEY);
|
||||
assertThat(user.getName()).isEqualTo(USERNAME);
|
||||
assertThat(user.getAuthorities()).isEmpty();
|
||||
assertThat(user.getAttributes()).containsOnlyKeys(ATTRIBUTE_NAME_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAllParametersProvidedAndValidThenCreated() {
|
||||
DefaultOAuth2User user = new DefaultOAuth2User(AUTHORITIES, ATTRIBUTES, ATTRIBUTE_NAME_KEY);
|
||||
|
||||
+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");
|
||||
|
||||
+2
-2
@@ -37,9 +37,9 @@ import static org.springframework.util.Assert.notNull;
|
||||
* A fully configured registration may look like
|
||||
* <pre>
|
||||
* //remote IDP entity ID
|
||||
* String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
|
||||
* String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php";
|
||||
* //remote WebSSO Endpoint - Where to Send AuthNRequests to
|
||||
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
|
||||
* String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php";
|
||||
* //local registration ID
|
||||
* String registrationId = "simplesamlphp";
|
||||
* //local entity ID - autogenerated based on URL
|
||||
|
||||
@@ -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')
|
||||
|
||||
+6
-6
@@ -124,7 +124,7 @@ public class Saml2LoginIntegrationTests {
|
||||
public void authenticateRequestWhenUnauthenticatedThenRespondsWithRedirectAuthNRequestXML() throws Exception {
|
||||
mockMvc.perform(get("http://localhost:8080/saml2/authenticate/simplesamlphp"))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php?SAMLRequest=")));
|
||||
.andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php?SAMLRequest=")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,7 +134,7 @@ public class Saml2LoginIntegrationTests {
|
||||
.param("RelayState", "relay state value with spaces")
|
||||
)
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php?SAMLRequest=")))
|
||||
.andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php?SAMLRequest=")))
|
||||
.andExpect(header().string("Location", containsString("RelayState=relay%20state%20value%20with%20spaces")));
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ public class Saml2LoginIntegrationTests {
|
||||
String destination = authnRequest.getDestination();
|
||||
assertEquals(
|
||||
"Destination must match",
|
||||
"https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php",
|
||||
"https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php",
|
||||
destination
|
||||
);
|
||||
String acsURL = authnRequest.getAssertionConsumerServiceURL();
|
||||
@@ -317,14 +317,14 @@ public class Saml2LoginIntegrationTests {
|
||||
Response response = OpenSamlActionTestingSupport.buildResponse();
|
||||
response.setID("_" + UUID.randomUUID().toString());
|
||||
response.setDestination("http://localhost:8080/login/saml2/sso/simplesamlphp");
|
||||
response.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"));
|
||||
response.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"));
|
||||
return response;
|
||||
}
|
||||
|
||||
private Assertion buildAssertion(String username) {
|
||||
Assertion assertion = OpenSamlActionTestingSupport.buildAssertion();
|
||||
assertion.setIssueInstant(DateTime.now());
|
||||
assertion.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"));
|
||||
assertion.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"));
|
||||
assertion.setSubject(buildSubject(username));
|
||||
assertion.setConditions(buildConditions());
|
||||
|
||||
@@ -346,7 +346,7 @@ public class Saml2LoginIntegrationTests {
|
||||
final PrivateKey privateKey = KeySupport.decodePrivateKey(key.getBytes(UTF_8), new char[0]);
|
||||
BasicCredential cred = CredentialSupport.getSimpleCredential(publicKey, privateKey);
|
||||
cred.setUsageType(usageType);
|
||||
cred.setEntityId("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php");
|
||||
cred.setEntityId("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php");
|
||||
return cred;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,5 +12,5 @@ spring:
|
||||
verification:
|
||||
credentials:
|
||||
- certificate-location: "classpath:credentials/idp-certificate.crt"
|
||||
entity-id: https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php
|
||||
sso-url: https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php
|
||||
entity-id: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php
|
||||
sso-url: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<a th:href="@{/logout}">Log Out</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://simplesaml-for-spring-saml.cfapps.io/module.php/core/authenticate.php?as=example-userpass&logout">
|
||||
<a href="https://simplesaml-for-spring-saml.apps.pcfone.io/module.php/core/authenticate.php?as=example-userpass&logout">
|
||||
Log out of SimpleSAMLPhp
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -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')
|
||||
|
||||
Binary file not shown.
@@ -5,6 +5,7 @@ dependencies {
|
||||
compile project(':spring-security-config')
|
||||
compile "org.bouncycastle:bcprov-jdk15on"
|
||||
compile "org.bouncycastle:bcpkix-jdk15on"
|
||||
compile slf4jDependencies
|
||||
|
||||
testCompile project(':spring-security-test')
|
||||
}
|
||||
|
||||
+4
@@ -27,6 +27,10 @@ import org.springframework.security.web.session.HttpSessionEventPublisher;
|
||||
public class MessageSecurityWebApplicationInitializer extends
|
||||
AbstractSecurityWebApplicationInitializer {
|
||||
|
||||
public MessageSecurityWebApplicationInitializer() {
|
||||
super(SecurityConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean enableHttpSessionEventPublisher() {
|
||||
return true;
|
||||
|
||||
+10
-13
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.security.samples.config;
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@@ -23,6 +25,7 @@ import org.springframework.security.converter.RsaKeyConverters;
|
||||
import org.springframework.security.saml2.credentials.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -39,11 +42,12 @@ import static org.springframework.security.saml2.credentials.Saml2X509Credential
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
RelyingPartyRegistration getSaml2AuthenticationConfiguration() throws Exception {
|
||||
@Bean
|
||||
RelyingPartyRegistrationRepository getSaml2AuthenticationConfiguration() throws Exception {
|
||||
//remote IDP entity ID
|
||||
String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
|
||||
String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php";
|
||||
//remote WebSSO Endpoint - Where to Send AuthNRequests to
|
||||
String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
|
||||
String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php";
|
||||
//local registration ID
|
||||
String registrationId = "simplesamlphp";
|
||||
//local entity ID - autogenerated based on URL
|
||||
@@ -53,14 +57,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
//IDP certificate for verification of incoming messages
|
||||
Saml2X509Credential idpVerificationCertificate = getVerificationCertificate();
|
||||
String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
|
||||
return RelyingPartyRegistration.withRegistrationId(registrationId)
|
||||
return new InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistration.withRegistrationId(registrationId)
|
||||
.remoteIdpEntityId(idpEntityId)
|
||||
.idpWebSsoUrl(webSsoEndpoint)
|
||||
.credentials(c -> c.add(signingCredential))
|
||||
.credentials(c -> c.add(idpVerificationCertificate))
|
||||
.localEntityIdTemplate(localEntityIdTemplate)
|
||||
.assertionConsumerServiceUrlTemplate(acsUrlTemplate)
|
||||
.build();
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,14 +74,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.saml2Login()
|
||||
.relyingPartyRegistrationRepository(
|
||||
new InMemoryRelyingPartyRegistrationRepository(
|
||||
getSaml2AuthenticationConfiguration()
|
||||
)
|
||||
)
|
||||
.loginProcessingUrl("/sample/jc/saml2/sso/{registrationId}")
|
||||
;
|
||||
.saml2Login();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
+2
-2
@@ -44,7 +44,7 @@ public class SecurityConfigTests {
|
||||
public void filterWhenLoginProcessingUrlIsSetInJavaConfigThenTheFilterHasIt() {
|
||||
FilterChainProxy filterChain = context.getBean(FilterChainProxy.class);
|
||||
Assert.assertNotNull(filterChain);
|
||||
final List<Filter> filters = filterChain.getFilters("/sample/jc/saml2/sso/test-id");
|
||||
final List<Filter> filters = filterChain.getFilters("/login/saml2/sso/one");
|
||||
Assert.assertNotNull(filters);
|
||||
Saml2WebSsoAuthenticationFilter filter = (Saml2WebSsoAuthenticationFilter) filters
|
||||
.stream()
|
||||
@@ -55,6 +55,6 @@ public class SecurityConfigTests {
|
||||
.get();
|
||||
final Object matcher = ReflectionTestUtils.getField(filter, "requiresAuthenticationRequestMatcher");
|
||||
final Object pattern = ReflectionTestUtils.getField(matcher, "pattern");
|
||||
Assert.assertEquals("loginProcessingUrl mismatch", "/sample/jc/saml2/sso/{registrationId}", pattern);
|
||||
Assert.assertEquals("loginProcessingUrl mismatch", "/login/saml2/sso/{registrationId}", pattern);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
|
||||
+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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-9
@@ -142,13 +142,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
|
||||
+ response
|
||||
+ ". You must use the HttpRequestResponseHolder.response after invoking loadContext");
|
||||
}
|
||||
// saveContext() might already be called by the response wrapper
|
||||
// if something in the chain called sendError() or sendRedirect(). This ensures we
|
||||
// only call it
|
||||
// once per request.
|
||||
if (!responseWrapper.isContextSaved()) {
|
||||
responseWrapper.saveContext(context);
|
||||
}
|
||||
responseWrapper.saveContext(context);
|
||||
}
|
||||
|
||||
public boolean containsContext(HttpServletRequest request) {
|
||||
@@ -305,6 +299,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
|
||||
private final boolean httpSessionExistedAtStartOfRequest;
|
||||
private final SecurityContext contextBeforeExecution;
|
||||
private final Authentication authBeforeExecution;
|
||||
private boolean isSaveContextInvoked;
|
||||
|
||||
/**
|
||||
* Takes the parameters required to call <code>saveContext()</code> successfully
|
||||
@@ -355,6 +350,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
|
||||
// SEC-1587 A non-anonymous context may still be in the session
|
||||
// SEC-1735 remove if the contextBeforeExecution was not anonymous
|
||||
httpSession.removeAttribute(springSecurityContextKey);
|
||||
this.isSaveContextInvoked = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -371,7 +367,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
|
||||
if (contextChanged(context)
|
||||
|| httpSession.getAttribute(springSecurityContextKey) == null) {
|
||||
httpSession.setAttribute(springSecurityContextKey, context);
|
||||
|
||||
this.isSaveContextInvoked = true;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("SecurityContext '" + context
|
||||
+ "' stored to HttpSession: '" + httpSession);
|
||||
@@ -381,7 +377,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
|
||||
}
|
||||
|
||||
private boolean contextChanged(SecurityContext context) {
|
||||
return context != contextBeforeExecution
|
||||
return this.isSaveContextInvoked || context != contextBeforeExecution
|
||||
|| context.getAuthentication() != authBeforeExecution;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -13,9 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.web.csrf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
@@ -28,6 +30,9 @@ import javax.servlet.http.HttpSession;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
@@ -35,8 +40,6 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import static java.lang.Boolean.TRUE;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Applies
|
||||
@@ -58,6 +61,7 @@ import static java.lang.Boolean.TRUE;
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class CsrfFilter extends OncePerRequestFilter {
|
||||
|
||||
/**
|
||||
* The default {@link RequestMatcher} that indicates if CSRF protection is required or
|
||||
* not. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other
|
||||
@@ -66,18 +70,21 @@ public final class CsrfFilter extends OncePerRequestFilter {
|
||||
public static final RequestMatcher DEFAULT_CSRF_MATCHER = new DefaultRequiresCsrfMatcher();
|
||||
|
||||
/**
|
||||
* The attribute name to use when marking a given request as one that should not be filtered.
|
||||
* The attribute name to use when marking a given request as one that should not be
|
||||
* filtered.
|
||||
*
|
||||
* To use, set the attribute on your {@link HttpServletRequest}:
|
||||
* <pre>
|
||||
* To use, set the attribute on your {@link HttpServletRequest}: <pre>
|
||||
* CsrfFilter.skipRequest(request);
|
||||
* </pre>
|
||||
*/
|
||||
private static final String SHOULD_NOT_FILTER = "SHOULD_NOT_FILTER" + CsrfFilter.class.getName();
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final CsrfTokenRepository tokenRepository;
|
||||
|
||||
private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
|
||||
|
||||
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
|
||||
|
||||
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
|
||||
@@ -87,62 +94,46 @@ public final class CsrfFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
||||
return TRUE.equals(request.getAttribute(SHOULD_NOT_FILTER));
|
||||
return Boolean.TRUE.equals(request.getAttribute(SHOULD_NOT_FILTER));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(javax.servlet
|
||||
* .http.HttpServletRequest, javax.servlet.http.HttpServletResponse,
|
||||
* javax.servlet.FilterChain)
|
||||
*/
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
request.setAttribute(HttpServletResponse.class.getName(), response);
|
||||
|
||||
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
|
||||
final boolean missingToken = csrfToken == null;
|
||||
boolean missingToken = (csrfToken == null);
|
||||
if (missingToken) {
|
||||
csrfToken = this.tokenRepository.generateToken(request);
|
||||
this.tokenRepository.saveToken(csrfToken, request, response);
|
||||
}
|
||||
request.setAttribute(CsrfToken.class.getName(), csrfToken);
|
||||
request.setAttribute(csrfToken.getParameterName(), csrfToken);
|
||||
|
||||
if (!this.requireCsrfProtectionMatcher.matches(request)) {
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Did not protect against CSRF since request did not match "
|
||||
+ this.requireCsrfProtectionMatcher);
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String actualToken = request.getHeader(csrfToken.getHeaderName());
|
||||
if (actualToken == null) {
|
||||
actualToken = request.getParameter(csrfToken.getParameterName());
|
||||
}
|
||||
if (!csrfToken.getToken().equals(actualToken)) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("Invalid CSRF token found for "
|
||||
+ UrlUtils.buildFullRequestUrl(request));
|
||||
}
|
||||
if (missingToken) {
|
||||
this.accessDeniedHandler.handle(request, response,
|
||||
new MissingCsrfTokenException(actualToken));
|
||||
}
|
||||
else {
|
||||
this.accessDeniedHandler.handle(request, response,
|
||||
new InvalidCsrfTokenException(csrfToken, actualToken));
|
||||
}
|
||||
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
|
||||
this.logger.debug(
|
||||
LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
|
||||
AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
|
||||
: new MissingCsrfTokenException(actualToken);
|
||||
this.accessDeniedHandler.handle(request, response, exception);
|
||||
return;
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
public static void skipRequest(HttpServletRequest request) {
|
||||
request.setAttribute(SHOULD_NOT_FILTER, TRUE);
|
||||
request.setAttribute(SHOULD_NOT_FILTER, Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,14 +145,11 @@ public final class CsrfFilter extends OncePerRequestFilter {
|
||||
* The default is to apply CSRF protection for any HTTP method other than GET, HEAD,
|
||||
* TRACE, OPTIONS.
|
||||
* </p>
|
||||
*
|
||||
* @param requireCsrfProtectionMatcher the {@link RequestMatcher} used to determine if
|
||||
* CSRF protection should be applied.
|
||||
*/
|
||||
public void setRequireCsrfProtectionMatcher(
|
||||
RequestMatcher requireCsrfProtectionMatcher) {
|
||||
Assert.notNull(requireCsrfProtectionMatcher,
|
||||
"requireCsrfProtectionMatcher cannot be null");
|
||||
public void setRequireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) {
|
||||
Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null");
|
||||
this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
|
||||
}
|
||||
|
||||
@@ -172,7 +160,6 @@ public final class CsrfFilter extends OncePerRequestFilter {
|
||||
* <p>
|
||||
* The default is to use AccessDeniedHandlerImpl with no arguments.
|
||||
* </p>
|
||||
*
|
||||
* @param accessDeniedHandler the {@link AccessDeniedHandler} to use
|
||||
*/
|
||||
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
|
||||
@@ -180,20 +167,39 @@ public final class CsrfFilter extends OncePerRequestFilter {
|
||||
this.accessDeniedHandler = accessDeniedHandler;
|
||||
}
|
||||
|
||||
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
|
||||
private final HashSet<String> allowedMethods = new HashSet<>(
|
||||
Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
|
||||
/**
|
||||
* Constant time comparison to prevent against timing attacks.
|
||||
* @param expected
|
||||
* @param actual
|
||||
* @return
|
||||
*/
|
||||
private static boolean equalsConstantTime(String expected, String 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 final class DefaultRequiresCsrfMatcher implements RequestMatcher {
|
||||
|
||||
private final HashSet<String> allowedMethods = new HashSet<>(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.
|
||||
* servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
return !this.allowedMethods.contains(request.getMethod());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CsrfNotRequired " + this.allowedMethods;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+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
-1
@@ -108,7 +108,7 @@ public final class CurrentSecurityContextArgumentResolver
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setRootObject(securityContext);
|
||||
context.setVariable("this", securityContext);
|
||||
|
||||
context.setBeanResolver(this.beanResolver);
|
||||
Expression expression = this.parser.parseExpression(expressionToParse);
|
||||
securityContextResult = expression.getValue(context);
|
||||
}
|
||||
|
||||
@@ -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,26 +16,28 @@
|
||||
|
||||
package org.springframework.security.web.server.csrf;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.multipart.FormFieldPart;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
|
||||
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.lang.Boolean.TRUE;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -64,13 +66,14 @@ import static java.lang.Boolean.TRUE;
|
||||
* @since 5.0
|
||||
*/
|
||||
public class CsrfWebFilter implements WebFilter {
|
||||
|
||||
public static final ServerWebExchangeMatcher DEFAULT_CSRF_MATCHER = new DefaultRequireCsrfProtectionMatcher();
|
||||
|
||||
/**
|
||||
* The attribute name to use when marking a given request as one that should not be filtered.
|
||||
* The attribute name to use when marking a given request as one that should not be
|
||||
* filtered.
|
||||
*
|
||||
* To use, set the attribute on your {@link ServerWebExchange}:
|
||||
* <pre>
|
||||
* To use, set the attribute on your {@link ServerWebExchange}: <pre>
|
||||
* CsrfWebFilter.skipExchange(exchange);
|
||||
* </pre>
|
||||
*/
|
||||
@@ -80,32 +83,31 @@ public class CsrfWebFilter implements WebFilter {
|
||||
|
||||
private ServerCsrfTokenRepository csrfTokenRepository = new WebSessionServerCsrfTokenRepository();
|
||||
|
||||
private ServerAccessDeniedHandler accessDeniedHandler = new HttpStatusServerAccessDeniedHandler(HttpStatus.FORBIDDEN);
|
||||
private ServerAccessDeniedHandler accessDeniedHandler = new HttpStatusServerAccessDeniedHandler(
|
||||
HttpStatus.FORBIDDEN);
|
||||
|
||||
private boolean isTokenFromMultipartDataEnabled;
|
||||
|
||||
public void setAccessDeniedHandler(
|
||||
ServerAccessDeniedHandler accessDeniedHandler) {
|
||||
public void setAccessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) {
|
||||
Assert.notNull(accessDeniedHandler, "accessDeniedHandler");
|
||||
this.accessDeniedHandler = accessDeniedHandler;
|
||||
}
|
||||
|
||||
public void setCsrfTokenRepository(
|
||||
ServerCsrfTokenRepository csrfTokenRepository) {
|
||||
public void setCsrfTokenRepository(ServerCsrfTokenRepository csrfTokenRepository) {
|
||||
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
|
||||
this.csrfTokenRepository = csrfTokenRepository;
|
||||
}
|
||||
|
||||
public void setRequireCsrfProtectionMatcher(
|
||||
ServerWebExchangeMatcher requireCsrfProtectionMatcher) {
|
||||
public void setRequireCsrfProtectionMatcher(ServerWebExchangeMatcher requireCsrfProtectionMatcher) {
|
||||
Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null");
|
||||
this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies if the {@code CsrfWebFilter} should try to resolve the actual CSRF token from the body of multipart
|
||||
* data requests.
|
||||
* @param tokenFromMultipartDataEnabled true if should read from multipart form body, else false. Default is false
|
||||
* Specifies if the {@code CsrfWebFilter} should try to resolve the actual CSRF token
|
||||
* from the body of multipart data requests.
|
||||
* @param tokenFromMultipartDataEnabled true if should read from multipart form body,
|
||||
* else false. Default is false
|
||||
*/
|
||||
public void setTokenFromMultipartDataEnabled(boolean tokenFromMultipartDataEnabled) {
|
||||
this.isTokenFromMultipartDataEnabled = tokenFromMultipartDataEnabled;
|
||||
@@ -113,38 +115,33 @@ public class CsrfWebFilter implements WebFilter {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
if (TRUE.equals(exchange.getAttribute(SHOULD_NOT_FILTER))) {
|
||||
if (Boolean.TRUE.equals(exchange.getAttribute(SHOULD_NOT_FILTER))) {
|
||||
return chain.filter(exchange).then(Mono.empty());
|
||||
}
|
||||
|
||||
return this.requireCsrfProtectionMatcher.matches(exchange)
|
||||
.filter( matchResult -> matchResult.isMatch())
|
||||
.filter( matchResult -> !exchange.getAttributes().containsKey(CsrfToken.class.getName()))
|
||||
.flatMap(m -> validateToken(exchange))
|
||||
.flatMap(m -> continueFilterChain(exchange, chain))
|
||||
.switchIfEmpty(continueFilterChain(exchange, chain).then(Mono.empty()))
|
||||
.onErrorResume(CsrfException.class, e -> this.accessDeniedHandler
|
||||
.handle(exchange, e));
|
||||
return this.requireCsrfProtectionMatcher.matches(exchange).filter(MatchResult::isMatch)
|
||||
.filter((matchResult) -> !exchange.getAttributes().containsKey(CsrfToken.class.getName()))
|
||||
.flatMap((m) -> validateToken(exchange)).flatMap((m) -> continueFilterChain(exchange, chain))
|
||||
.switchIfEmpty(continueFilterChain(exchange, chain).then(Mono.empty()))
|
||||
.onErrorResume(CsrfException.class, (ex) -> this.accessDeniedHandler.handle(exchange, ex));
|
||||
}
|
||||
|
||||
public static void skipExchange(ServerWebExchange exchange) {
|
||||
exchange.getAttributes().put(SHOULD_NOT_FILTER, TRUE);
|
||||
exchange.getAttributes().put(SHOULD_NOT_FILTER, Boolean.TRUE);
|
||||
}
|
||||
|
||||
private Mono<Void> validateToken(ServerWebExchange exchange) {
|
||||
return this.csrfTokenRepository.loadToken(exchange)
|
||||
.switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("CSRF Token has been associated to this client"))))
|
||||
.filterWhen(expected -> containsValidCsrfToken(exchange, expected))
|
||||
.switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("Invalid CSRF Token"))))
|
||||
.then();
|
||||
.switchIfEmpty(
|
||||
Mono.defer(() -> Mono.error(new CsrfException("An expected CSRF token cannot be found"))))
|
||||
.filterWhen((expected) -> containsValidCsrfToken(exchange, expected))
|
||||
.switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("Invalid CSRF Token")))).then();
|
||||
}
|
||||
|
||||
private Mono<Boolean> containsValidCsrfToken(ServerWebExchange exchange, CsrfToken expected) {
|
||||
return exchange.getFormData()
|
||||
.flatMap(data -> Mono.justOrEmpty(data.getFirst(expected.getParameterName())))
|
||||
.switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(expected.getHeaderName())))
|
||||
.switchIfEmpty(tokenFromMultipartData(exchange, expected))
|
||||
.map(actual -> actual.equals(expected.getToken()));
|
||||
return exchange.getFormData().flatMap((data) -> Mono.justOrEmpty(data.getFirst(expected.getParameterName())))
|
||||
.switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(expected.getHeaderName())))
|
||||
.switchIfEmpty(tokenFromMultipartData(exchange, expected))
|
||||
.map((actual) -> equalsConstantTime(actual, expected.getToken()));
|
||||
}
|
||||
|
||||
private Mono<String> tokenFromMultipartData(ServerWebExchange exchange, CsrfToken expected) {
|
||||
@@ -157,14 +154,12 @@ public class CsrfWebFilter implements WebFilter {
|
||||
if (!contentType.includes(MediaType.MULTIPART_FORM_DATA)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return exchange.getMultipartData()
|
||||
.map(d -> d.getFirst(expected.getParameterName()))
|
||||
.cast(FormFieldPart.class)
|
||||
.map(FormFieldPart::value);
|
||||
return exchange.getMultipartData().map((d) -> d.getFirst(expected.getParameterName())).cast(FormFieldPart.class)
|
||||
.map(FormFieldPart::value);
|
||||
}
|
||||
|
||||
private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return Mono.defer(() ->{
|
||||
return Mono.defer(() -> {
|
||||
Mono<CsrfToken> csrfToken = csrfToken(exchange);
|
||||
exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken);
|
||||
return chain.filter(exchange);
|
||||
@@ -172,26 +167,45 @@ public class CsrfWebFilter implements WebFilter {
|
||||
}
|
||||
|
||||
private Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
|
||||
return this.csrfTokenRepository.loadToken(exchange)
|
||||
.switchIfEmpty(generateToken(exchange));
|
||||
return this.csrfTokenRepository.loadToken(exchange).switchIfEmpty(generateToken(exchange));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant time comparison to prevent against timing attacks.
|
||||
* @param expected
|
||||
* @param actual
|
||||
* @return
|
||||
*/
|
||||
private static boolean equalsConstantTime(String expected, String 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 Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
|
||||
return this.csrfTokenRepository.generateToken(exchange)
|
||||
.delayUntil(token -> this.csrfTokenRepository.saveToken(exchange, token));
|
||||
.delayUntil((token) -> this.csrfTokenRepository.saveToken(exchange, token));
|
||||
}
|
||||
|
||||
private static class DefaultRequireCsrfProtectionMatcher implements ServerWebExchangeMatcher {
|
||||
|
||||
private static final Set<HttpMethod> ALLOWED_METHODS = new HashSet<>(
|
||||
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS));
|
||||
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS));
|
||||
|
||||
@Override
|
||||
public Mono<MatchResult> matches(ServerWebExchange exchange) {
|
||||
return Mono.just(exchange.getRequest())
|
||||
.flatMap(r -> Mono.justOrEmpty(r.getMethod()))
|
||||
.filter(m -> ALLOWED_METHODS.contains(m))
|
||||
.flatMap(m -> MatchResult.notMatch())
|
||||
.switchIfEmpty(MatchResult.match());
|
||||
return Mono.just(exchange.getRequest()).flatMap((r) -> Mono.justOrEmpty(r.getMethod()))
|
||||
.filter(ALLOWED_METHODS::contains).flatMap((m) -> MatchResult.notMatch())
|
||||
.switchIfEmpty(MatchResult.match());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+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();
|
||||
}
|
||||
|
||||
+58
@@ -16,11 +16,15 @@
|
||||
|
||||
package org.springframework.security.web.context;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -30,6 +34,7 @@ import javax.servlet.http.HttpSession;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
@@ -37,10 +42,14 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Transient;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
@@ -174,6 +183,48 @@ public class HttpSessionSecurityContextRepositoryTests {
|
||||
verify(session).setAttribute(SPRING_SECURITY_CONTEXT_KEY, ctx);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void saveContextWhenSaveNewContextThenOriginalContextThenOriginalContextSaved() throws Exception {
|
||||
HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository();
|
||||
SecurityContextPersistenceFilter securityContextPersistenceFilter = new SecurityContextPersistenceFilter(
|
||||
repository);
|
||||
|
||||
UserDetails original = User.withUsername("user").password("password").roles("USER").build();
|
||||
SecurityContext originalContext = createSecurityContext(original);
|
||||
UserDetails impersonate = User.withUserDetails(original).username("impersonate").build();
|
||||
SecurityContext impersonateContext = createSecurityContext(impersonate);
|
||||
|
||||
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
|
||||
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
|
||||
|
||||
Filter saveImpersonateContext = (request, response, chain) -> {
|
||||
SecurityContextHolder.setContext(impersonateContext);
|
||||
// ensure the response is committed to trigger save
|
||||
response.flushBuffer();
|
||||
chain.doFilter(request, response);
|
||||
};
|
||||
Filter saveOriginalContext = (request, response, chain) -> {
|
||||
SecurityContextHolder.setContext(originalContext);
|
||||
chain.doFilter(request, response);
|
||||
};
|
||||
HttpServlet servlet = new HttpServlet() {
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
resp.getWriter().write("Hi");
|
||||
}
|
||||
};
|
||||
|
||||
SecurityContextHolder.setContext(originalContext);
|
||||
MockFilterChain chain = new MockFilterChain(servlet, saveImpersonateContext, saveOriginalContext);
|
||||
|
||||
securityContextPersistenceFilter.doFilter(mockRequest, mockResponse, chain);
|
||||
|
||||
assertThat(mockRequest.getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY))
|
||||
.isEqualTo(originalContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonSecurityContextInSessionIsIgnored() {
|
||||
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
|
||||
@@ -668,6 +719,13 @@ public class HttpSessionSecurityContextRepositoryTests {
|
||||
assertThat(session).isNull();
|
||||
}
|
||||
|
||||
private SecurityContext createSecurityContext(UserDetails userDetails) {
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails,
|
||||
userDetails.getPassword(), userDetails.getAuthorities());
|
||||
SecurityContext securityContext = new SecurityContextImpl(token);
|
||||
return securityContext;
|
||||
}
|
||||
|
||||
@Transient
|
||||
private static class SomeTransientAuthentication extends AbstractAuthenticationToken {
|
||||
SomeTransientAuthentication() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
+30
@@ -27,6 +27,7 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
@@ -35,17 +36,28 @@ import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
public class AuthenticationPrincipalArgumentResolverTests {
|
||||
|
||||
private BeanResolver beanResolver;
|
||||
|
||||
private Object expectedPrincipal;
|
||||
private AuthenticationPrincipalArgumentResolver resolver;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
beanResolver = mock(BeanResolver.class);
|
||||
resolver = new AuthenticationPrincipalArgumentResolver();
|
||||
resolver.setBeanResolver(this.beanResolver);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -128,6 +140,17 @@ public class AuthenticationPrincipalArgumentResolverTests {
|
||||
.isEqualTo(this.expectedPrincipal);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentSpelBean() throws Exception {
|
||||
CustomUserPrincipal principal = new CustomUserPrincipal();
|
||||
setAuthenticationPrincipal(principal);
|
||||
when(this.beanResolver.resolve(any(), eq("test"))).thenReturn(principal.property);
|
||||
this.expectedPrincipal = principal.property;
|
||||
assertThat(this.resolver.resolveArgument(showUserSpelBean(), null, null, null))
|
||||
.isEqualTo(this.expectedPrincipal);
|
||||
verify(this.beanResolver).resolve(any(), eq("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentSpelCopy() throws Exception {
|
||||
CopyUserPrincipal principal = new CopyUserPrincipal("property");
|
||||
@@ -198,6 +221,10 @@ public class AuthenticationPrincipalArgumentResolverTests {
|
||||
return getMethodParameter("showUserSpel", String.class);
|
||||
}
|
||||
|
||||
private MethodParameter showUserSpelBean() {
|
||||
return getMethodParameter("showUserSpelBean", String.class);
|
||||
}
|
||||
|
||||
private MethodParameter showUserSpelCopy() {
|
||||
return getMethodParameter("showUserSpelCopy", CopyUserPrincipal.class);
|
||||
}
|
||||
@@ -255,6 +282,9 @@ public class AuthenticationPrincipalArgumentResolverTests {
|
||||
@AuthenticationPrincipal(expression = "property") String user) {
|
||||
}
|
||||
|
||||
public void showUserSpelBean(@AuthenticationPrincipal(expression = "@test") String user) {
|
||||
}
|
||||
|
||||
public void showUserSpelCopy(
|
||||
@AuthenticationPrincipal(expression = "new org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolverTests$CopyUserPrincipal(#this)") CopyUserPrincipal user) {
|
||||
}
|
||||
|
||||
+28
@@ -26,6 +26,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -38,6 +39,11 @@ import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* @author Dan Zheng
|
||||
@@ -45,11 +51,16 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
*
|
||||
*/
|
||||
public class CurrentSecurityContextArgumentResolverTests {
|
||||
|
||||
private BeanResolver beanResolver;
|
||||
|
||||
private CurrentSecurityContextArgumentResolver resolver;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.beanResolver = mock(BeanResolver.class);
|
||||
this.resolver = new CurrentSecurityContextArgumentResolver();
|
||||
this.resolver.setBeanResolver(this.beanResolver);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -104,6 +115,15 @@ public class CurrentSecurityContextArgumentResolverTests {
|
||||
assertThat(auth1.getPrincipal()).isEqualTo(principal);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentWithAuthenticationWithBean() throws Exception {
|
||||
String principal = "john";
|
||||
when(this.beanResolver.resolve(any(), eq("test"))).thenReturn(principal);
|
||||
assertThat(this.resolver.resolveArgument(showSecurityContextAuthenticationWithBean(), null, null, null))
|
||||
.isEqualTo(principal);
|
||||
verify(this.beanResolver).resolve(any(), eq("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentWithNullAuthentication() {
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
@@ -217,6 +237,10 @@ public class CurrentSecurityContextArgumentResolverTests {
|
||||
return getMethodParameter("showSecurityContextAuthenticationAnnotation", Authentication.class);
|
||||
}
|
||||
|
||||
public MethodParameter showSecurityContextAuthenticationWithBean() {
|
||||
return getMethodParameter("showSecurityContextAuthenticationWithBean", String.class);
|
||||
}
|
||||
|
||||
private MethodParameter showSecurityContextAuthenticationWithOptionalPrincipal() {
|
||||
return getMethodParameter("showSecurityContextAuthenticationWithOptionalPrincipal", Object.class);
|
||||
}
|
||||
@@ -279,6 +303,10 @@ public class CurrentSecurityContextArgumentResolverTests {
|
||||
public void showSecurityContextAuthenticationAnnotation(@CurrentSecurityContext(expression = "authentication") Authentication authentication) {
|
||||
}
|
||||
|
||||
public void showSecurityContextAuthenticationWithBean(
|
||||
@CurrentSecurityContext(expression = "@test") String name) {
|
||||
}
|
||||
|
||||
public void showSecurityContextAuthenticationWithOptionalPrincipal(@CurrentSecurityContext(expression = "authentication?.principal") Object principal) {
|
||||
}
|
||||
|
||||
|
||||
+18
-3
@@ -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;
|
||||
@@ -65,8 +67,19 @@ public class CsrfWebFilterTests {
|
||||
private MockServerWebExchange get = from(
|
||||
MockServerHttpRequest.get("/"));
|
||||
|
||||
private ServerWebExchange post = from(
|
||||
MockServerHttpRequest.post("/"));
|
||||
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() {
|
||||
@@ -110,6 +123,8 @@ public class CsrfWebFilterTests {
|
||||
.verifyComplete();
|
||||
|
||||
assertThat(this.post.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||
StepVerifier.create(this.post.getResponse().getBodyAsString())
|
||||
.assertNext(b -> assertThat(b).contains("An expected CSRF token cannot be found"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+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