1
0
mirror of synced 2026-07-14 15:15:11 +00:00

Compare commits

...

57 Commits

Author SHA1 Message Date
Steve Riesenberg 13e7c9f58d Release 5.5.3 2021-10-18 14:48:01 -05:00
Steve Riesenberg 14e6d8ad49 Revert "Update io.spring.javaformat to 0.0.29"
This reverts commit fafde0910f.
2021-10-18 14:23:51 -05:00
Steve Riesenberg d1a27676df Update org.springframework.data to 2021.0.6
Closes gh-10417
2021-10-18 14:20:16 -05:00
Steve Riesenberg 844879b84d Update org.springframework to 5.3.11
Closes gh-10416
2021-10-18 14:20:14 -05:00
Steve Riesenberg d5efbd34b5 Update org.jetbrains.kotlinx to 1.5.2
Closes gh-10415
2021-10-18 14:20:11 -05:00
Steve Riesenberg b9ef11d228 Update org.jetbrains.kotlin to 1.5.31
Closes gh-10414
2021-10-18 14:20:09 -05:00
Steve Riesenberg d9b1df8b65 Update org.eclipse.jetty to 9.4.44.v20210927
Closes gh-10413
2021-10-18 14:20:07 -05:00
Steve Riesenberg 020dd90566 Update io.spring.nohttp to 0.0.10
Closes gh-10412
2021-10-18 14:20:05 -05:00
Steve Riesenberg fafde0910f Update io.spring.javaformat to 0.0.29
Closes gh-10411
2021-10-18 14:20:02 -05:00
Steve Riesenberg b2db2bdb2a Update r2dbc-spi-test to 0.8.6.RELEASE
Closes gh-10410
2021-10-18 14:20:00 -05:00
Steve Riesenberg cce1c16555 Update io.projectreactor to 2020.0.12
Closes gh-10408
2021-10-18 14:19:56 -05:00
Steve Riesenberg e969c363c0 Update jackson-bom to 2.12.5
Closes gh-10405
2021-10-18 14:19:49 -05:00
Steve Riesenberg 764e696026 Update logback-classic to 1.2.6
Closes gh-10404
2021-10-18 14:19:48 -05:00
Josh Cummings 6e86fab19d Restructure SwitchUserFilter Logs
Issue gh-6311
2021-10-18 13:02:42 -05:00
Emil Sierżęga 1a3e80506c Fixed link in .editorconfig 2021-10-13 15:37:32 -06:00
Dávid Kováč eb0597154d Update JavaDoc according to implementation
Update ClaimAccessor#getClaimAsMap and ClaimAccessor#getClaimAsStringList
JavaDoc according to the current implementation

Closes gh-10117
2021-10-13 13:13:44 -06:00
Josh Cummings 97dfabe92e Polish SecurityNamespaceHandler Tests
Issue gh-8974
2021-10-13 11:37:06 -06:00
Emil Sierżęga 944463e19a SecurityNamespaceHandler: update schema version to 5.5
Closes gh-8974
2021-10-13 11:35:25 -06:00
Marcus Da Coregio 816e847af2 Allow SAML 2.0 loginProcessingURL without registrationId
Closes gh-10176
2021-10-05 12:54:39 -03:00
Josh Cummings 1f919bc791 Fix OAuth2 Error Code
Closes gh-10319
2021-09-28 14:55:37 -06:00
Rob Winch cae8990046 GitHub Actions uses spring-builds+github 2021-09-28 15:08:55 -05:00
heowc c9917b3cd0 Fix typo
Closes gh-10276
2021-09-22 16:35:32 -06:00
Anthony Lofton 1653b7848d Updated test.adoc SecurityMockServerConfigurers method references
Updated all references to SecurityMockServerConfigurers to refer to
correct methods.
Added documentation for mockJwt to include the
SecurityMockServerConfigurers class.

Issue gh-10254
2021-09-14 15:22:32 -03:00
Derek Van Blerkom c55f1f8bea Fix return type to allow further security config
Issue gh-10245
2021-09-13 15:41:04 -03:00
Marcus Da Coregio 670cf99258 Update docs to point to ACL samples
Closes gh-10110
2021-09-06 11:30:38 -03:00
Josh Cummings 41feec4766 Remove unused Sonar from Build
Closes gh-10205
2021-08-20 16:47:55 -06:00
Josh Cummings 34e04f642c Next Development Version 2021-08-16 12:48:56 -06:00
Josh Cummings bdc3feaa6d Release 5.5.2 2021-08-16 11:43:06 -06:00
Josh Cummings 134f8b0c5d Update org.springframework.data to 2021.0.4
Closes gh-10195
2021-08-16 10:02:27 -06:00
Josh Cummings 835ee559d7 Update org.springframework to 5.3.9
Closes gh-10194
2021-08-16 10:02:25 -06:00
Josh Cummings cfe48556d4 Update org.slf4j to 1.7.32
Closes gh-10193
2021-08-16 10:02:22 -06:00
Josh Cummings c7b73d7b3b Update org.jetbrains.kotlinx to 1.5.1
Closes gh-10192
2021-08-16 10:02:20 -06:00
Josh Cummings 618edc2e12 Update org.jetbrains.kotlin to 1.5.21
Closes gh-10191
2021-08-16 10:02:18 -06:00
Josh Cummings f7fef100c0 Update org.eclipse.jetty to 9.4.43.v20210629
Closes gh-10190
2021-08-16 10:02:15 -06:00
Josh Cummings 9be97e76dc Update org.aspectj to 1.9.7
Closes gh-10189
2021-08-16 10:02:13 -06:00
Josh Cummings 603e0a4140 Update io.projectreactor to 2020.0.10
Closes gh-10187
2021-08-16 10:02:08 -06:00
Josh Cummings b081627f52 Update com.nimbusds to 9.9.1
Closes gh-10186
2021-08-16 10:02:05 -06:00
Josh Cummings 10a0bda63f Update jackson-bom to 2.12.4
Closes gh-10183
2021-08-16 10:01:58 -06:00
Josh Cummings 7db5149163 Update logback-classic to 1.2.5
Closes gh-10182
2021-08-16 10:01:56 -06:00
Josh Cummings b206670245 Exclude Kotlin RC and M releases 2021-08-16 09:59:59 -06:00
Fabio Guenci b067aa4653 Preserve Null Claim Values
Prior to this commit ClaimTypeConverter returned the claims with the
original value for all the claims with a null converted value.
The changes allows ClaimTypeConverter to overwrite and return claims
with converted value of null.

Closes gh-10135
2021-08-16 08:22:31 -06:00
Abdul Al-Faraj ba16d91971 Improve OpenSAML Version Check
Closes gh-10077
2021-07-26 10:51:31 -06:00
Steve Riesenberg dfebd6d9d4 Revert "URL encode client credentials"
This reverts commit e6c268add0.

Issue gh-9610 gh-9858
Closes gh-10018
Closes gh-10121
2021-07-20 12:59:44 -05:00
Eleftheria Stein bd703ff4ac Fix release instructions for generating changelog 2021-07-19 13:42:35 +02:00
dmitrilc 7abaefda10 Update oauth2-resourceserver.adoc
fix the name of the parameter, from failure to badCredentials

Replaces AuthenticationFailureEvent

Remove AuthenticationFailureEvent Reference

Closes gh-10062
2021-07-16 11:58:34 -06:00
Marcus Da Coregio 4440020217 Update SAML docs to point to correct api url
Closes gh-9953
2021-07-12 15:47:28 -03:00
Marcus Da Coregio c2b9c0856d Use springFrameworkVersion property in docs links
Closes gh-9987
2021-07-12 15:47:09 -03:00
Marcus Da Coregio a06f47fac3 Add springFrameworkVersion property in gradle
Closes gh-9954
2021-07-12 15:45:01 -03:00
Marcus Da Coregio 8c0103498a Ensure line endings for .bat are not modified
Closes gh-10039
2021-07-12 15:37:29 -03:00
Luke Quinane e269e5cb91 Fix typos
Closes gh-10050
2021-07-08 09:20:50 -03:00
Eleftheria Stein ed8b08fa24 Fix README local Maven install command
Switching from the maven to maven-publish plugin means users should use publishToMavenLocal to install jars into their local Maven cache.

Closes gh-10047
2021-07-07 13:26:28 +02:00
Marcus Da Coregio 208e327629 Update docs links to point to minor version branches
Closes gh-9986
2021-07-01 09:08:53 -03:00
Marcus Da Coregio 51dfae1b91 Add samplesBranch property
Closes gh-10019
2021-07-01 09:08:30 -03:00
Daniel Garnier-Moiroux 2fd94752c9 Fix Saml2WebSsoAuthenticationRequestFilter javadoc
Closes gh-10025
2021-06-30 13:51:29 +02:00
/usr/local/ΕΨΗΕΛΩΝ 61284ce22d Improve AuthenticationManagerBeanDefinitionParser XML parsing
Closes gh-7282
2021-06-28 11:55:05 +02:00
Eleftheria Stein fdd017d935 Apply DefaultLoginPageConfigurer before logout
If they are not applied in this order, then the LogoutConfigurer cannot
set the logoutSuccessUrl, because the DefaultLoginPageGeneratingFilter
does not exist yet.
This impacts users that inject the default HttpSecurity bean.

Closes gh-9973
2021-06-24 10:28:19 +02:00
Josh Cummings bfeb6bd756 Next Development Version 2021-06-21 12:59:19 -06:00
48 changed files with 408 additions and 292 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# EditorConfig for Spring Security
# see https://github.com/spring-projects/spring-security/blob/master/CONTRIBUTING.md#mind-the-whitespace
# see https://github.com/spring-projects/spring-security/blob/master/CONTRIBUTING.adoc#mind-the-whitespace
root = true
+3
View File
@@ -1,6 +1,9 @@
# Normalize line endings to auto.
* text auto
# Ensure that line endings for DOS batch files are not modified.
*.bat -text
# Ensure the following are treated as binary.
*.cer binary
*.graffle binary
@@ -45,7 +45,7 @@ jobs:
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Cache Gradle packages
uses: actions/cache@v2
with:
@@ -71,49 +71,16 @@ jobs:
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Snapshot Tests
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PspringVersion='5.+' -PreactorVersion='20+' -PspringDataVersion='Neumann-BUILD-SNAPSHOT' -PrsocketVersion=1.1.0-SNAPSHOT -PspringBootVersion=2.4.0-SNAPSHOT -PlocksDisabled --stacktrace
sonar_analysis:
name: Static Code Analysis
needs: [prerequisites]
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
env:
SONAR_URL: ${{ secrets.SONAR_URL }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: '11'
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
- name: Run Sonar on given (non-main) branch
if: ${{ github.ref != 'refs/heads/main' }}
run: |
export BRANCH=${GITHUB_REF#refs/heads/}
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew sonarqube -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PexcludeProjects='**/samples/**' -Dsonar.projectKey="spring-security-${GITHUB_REF#refs/heads/}" -Dsonar.projectName="spring-security-${GITHUB_REF#refs/heads/}" -Dsonar.host.url="$SONAR_URL" -Dsonar.login="$SONAR_TOKEN" --stacktrace
- name: Run Sonar on main
if: ${{ github.ref == 'refs/heads/main' }}
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew sonarqube -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PexcludeProjects='**/samples/**' -Dsonar.host.url="$SONAR_URL" -Dsonar.login="$SONAR_TOKEN" --stacktrace
deploy_artifacts:
name: Deploy Artifacts
needs: [build_jdk_11, snapshot_tests, sonar_analysis]
needs: [build_jdk_11, snapshot_tests]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -124,7 +91,7 @@ jobs:
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Deploy artifacts
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -140,7 +107,7 @@ jobs:
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
deploy_docs:
name: Deploy Docs
needs: [build_jdk_11, snapshot_tests, sonar_analysis]
needs: [build_jdk_11, snapshot_tests]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -151,7 +118,7 @@ jobs:
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Deploy Docs
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -164,7 +131,7 @@ jobs:
DOCS_HOST: ${{ secrets.DOCS_HOST }}
deploy_schema:
name: Deploy Schema
needs: [build_jdk_11, snapshot_tests, sonar_analysis]
needs: [build_jdk_11, snapshot_tests]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -175,7 +142,7 @@ jobs:
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds' >> ~/.gradle/gradle.properties
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Deploy Schema
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -188,7 +155,7 @@ jobs:
DOCS_HOST: ${{ secrets.DOCS_HOST }}
notify_result:
name: Check for failures
needs: [build_jdk_11, snapshot_tests, sonar_analysis, deploy_artifacts, deploy_docs, deploy_schema]
needs: [build_jdk_11, snapshot_tests, deploy_artifacts, deploy_docs, deploy_schema]
if: failure()
runs-on: ubuntu-latest
steps:
+1 -1
View File
@@ -43,7 +43,7 @@ git clone git@github.com:spring-projects/spring-security.git
=== Install all spring-\* jars into your local Maven cache
[indent=0]
----
./gradlew install
./gradlew publishToMavenLocal
----
=== Compile and test; build all jars, distribution zips, and docs
+1 -1
View File
@@ -143,7 +143,7 @@ Generate the Release Notes replacing:
* <next-version> - Replace with the milestone you are releasing now (i.e. 5.5.0-RC1)
----
$ ./gradlew generateChangelog -P<next-version>
$ ./gradlew generateChangelog -PnextVersion=<next-version>
----
* Copy the release notes to your clipboard (your mileage may vary with
+9 -1
View File
@@ -1,7 +1,7 @@
buildscript {
dependencies {
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.8'
classpath 'io.spring.nohttp:nohttp-gradle:0.0.10'
classpath "io.freefair.gradle:aspectj-plugin:5.3.3.3"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.netflix.nebula:nebula-project-plugin:8.0.0"
@@ -87,6 +87,14 @@ updateDependenciesSettings {
if (candidate.getGroup().equals("org.opensaml")) {
selection.reject("org.opensaml maintains two different versions, so it must be updated manually");
}
if (candidate.getGroup().equals("org.jetbrains.kotlin")) {
if (candidate.getVersion().endsWith("-RC")) {
selection.reject("On a maintenance branch, we should not take RC versions")
}
if (candidate.getVersion().contains("-M")) {
selection.reject("On a maintenance branch, we should not take milestone versions")
}
}
}
}
}
+2 -2
View File
@@ -72,14 +72,14 @@ dependencies {
implementation localGroovy()
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
implementation 'io.projectreactor:reactor-core:3.4.6'
implementation 'io.projectreactor:reactor-core:3.4.11'
implementation 'gradle.plugin.org.gretty:gretty:3.0.1'
implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
implementation 'io.spring.gradle:docbook-reference-plugin:0.3.1'
implementation 'io.spring.gradle:propdeps-plugin:0.0.10.RELEASE'
implementation 'io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.15'
implementation 'io.spring.nohttp:nohttp-gradle:0.0.8'
implementation 'io.spring.nohttp:nohttp-gradle:0.0.10'
implementation 'org.aim42:htmlSanityCheck:1.1.6'
implementation 'org.asciidoctor:asciidoctor-gradle-jvm:3.1.0'
implementation 'org.asciidoctor:asciidoctor-gradle-jvm-pdf:3.1.0'
@@ -36,7 +36,7 @@ class CheckstylePlugin implements Plugin<Project> {
if (checkstyleDir.exists() && checkstyleDir.directory) {
project.getPluginManager().apply('checkstyle')
project.dependencies.add('checkstyle', 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.15')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.8')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.10')
project.checkstyle {
configDirectory = checkstyleDir
@@ -93,7 +93,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
if (!namespaceMatchesVersion(element)) {
pc.getReaderContext().fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or "
+ "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
+ "with Spring Security 5.4. Please update your schema declarations to the 5.4 schema.", element);
+ "with Spring Security 5.5. Please update your schema declarations to the 5.5 schema.", element);
}
String name = pc.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(name);
@@ -213,7 +213,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
private boolean matchesVersionInternal(Element element) {
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
return schemaLocation.matches("(?m).*spring-security-5\\.4.*.xsd.*")
return schemaLocation.matches("(?m).*spring-security-5\\.5.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*");
}
@@ -94,8 +94,8 @@ class HttpSecurityConfiguration {
.requestCache(withDefaults())
.anonymous(withDefaults())
.servletApi(withDefaults())
.logout(withDefaults())
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults());
// @formatter:on
return http;
}
@@ -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.
@@ -160,7 +160,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
* @param repo the repository of relying parties
* @return the {@link Saml2LoginConfigurer} for further configuration
*/
public Saml2LoginConfigurer relyingPartyRegistrationRepository(RelyingPartyRegistrationRepository repo) {
public Saml2LoginConfigurer<B> relyingPartyRegistrationRepository(RelyingPartyRegistrationRepository repo) {
this.relyingPartyRegistrationRepository = repo;
return this;
}
@@ -172,10 +172,19 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
return this;
}
/**
* Specifies the URL to validate the credentials. If specified a custom URL, consider
* specifying a custom {@link AuthenticationConverter} via
* {@link #authenticationConverter(AuthenticationConverter)}, since the default
* {@link AuthenticationConverter} implementation relies on the
* <code>{registrationId}</code> path variable to be present in the URL
* @param loginProcessingUrl the URL to validate the credentials
* @return the {@link Saml2LoginConfigurer} for additional customization
* @see Saml2WebSsoAuthenticationFilter#DEFAULT_FILTER_PROCESSES_URI
*/
@Override
public Saml2LoginConfigurer<B> loginProcessingUrl(String loginProcessingUrl) {
Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be empty");
Assert.state(loginProcessingUrl.contains("{registrationId}"), "{registrationId} path variable is required");
this.loginProcessingUrl = loginProcessingUrl;
return this;
}
@@ -254,14 +263,25 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
private AuthenticationConverter getAuthenticationConverter(B http) {
if (this.authenticationConverter == null) {
Assert.state(this.loginProcessingUrl.contains("{registrationId}"),
"loginProcessingUrl must contain {registrationId} path variable");
return new Saml2AuthenticationTokenConverter(
new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository));
}
return this.authenticationConverter;
}
private String version() {
String version = Version.getVersion();
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
}
private void registerDefaultAuthenticationProvider(B http) {
if (Version.getVersion().startsWith("4")) {
if (version().startsWith("4")) {
http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
}
else {
@@ -346,7 +366,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
private Saml2AuthenticationRequestFactory getResolver(B http) {
Saml2AuthenticationRequestFactory resolver = getSharedOrBean(http, Saml2AuthenticationRequestFactory.class);
if (resolver == null) {
if (Version.getVersion().startsWith("4")) {
if (version().startsWith("4")) {
return new OpenSaml4AuthenticationRequestFactory();
}
return new OpenSamlAuthenticationRequestFactory();
@@ -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.
@@ -57,6 +57,8 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition
private static final String ATT_ERASE_CREDENTIALS = "erase-credentials";
private static final String AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME = "defaultAuthenticationEventPublisher";
@Override
public BeanDefinition parse(Element element, ParserContext pc) {
String id = element.getAttribute("id");
@@ -86,11 +88,15 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition
if ("false".equals(element.getAttribute(ATT_ERASE_CREDENTIALS))) {
providerManagerBldr.addPropertyValue("eraseCredentialsAfterAuthentication", 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));
if (StringUtils.hasText(alias)) {
pc.getRegistry().registerAlias(id, alias);
@@ -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.
@@ -27,6 +27,7 @@ import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.messaging.Message;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.security.config.util.SpringSecurityVersions;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ClassUtils;
@@ -147,4 +148,11 @@ public class SecurityNamespaceHandlerTests {
// should load just fine since no websocket block
}
@Test
public void configureWhenOldVersionThenErrorMessageContainsCorrectVersion() {
assertThatExceptionOfType(BeanDefinitionParsingException.class)
.isThrownBy(() -> new InMemoryXmlApplicationContext(XML_AUTHENTICATION_MANAGER, "3.0", null))
.withMessageContaining(SpringSecurityVersions.getCurrentXsdVersionFromSpringSchemas());
}
}
@@ -187,6 +187,18 @@ public class HttpSecurityConfigurationTests {
this.mockMvc.perform(get("/login")).andExpect(status().isOk());
}
@Test
public void loginWhenUsingDefaultsThenDefaultLoginFailurePageGenerated() throws Exception {
this.spring.register(SecurityEnabledConfig.class).autowire();
this.mockMvc.perform(get("/login?error")).andExpect(status().isOk());
}
@Test
public void loginWhenUsingDefaultsThenDefaultLogoutSuccessPageGenerated() throws Exception {
this.spring.register(SecurityEnabledConfig.class).autowire();
this.mockMvc.perform(get("/login?logout")).andExpect(status().isOk());
}
@RestController
static class NameController {
@@ -37,6 +37,7 @@ import org.mockito.ArgumentCaptor;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -49,6 +50,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -77,7 +79,9 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.context.HttpRequestResponseHolder;
@@ -91,6 +95,7 @@ import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
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.anyString;
import static org.mockito.BDDMockito.given;
@@ -117,6 +122,8 @@ public class Saml2LoginConfigurerTests {
private static final String SIGNED_RESPONSE = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9ycC5leGFtcGxlLm9yZy9hY3MiIElEPSJfYzE3MzM2YTAtNTM1My00MTQ5LWI3MmMtMDNkOWY5YWYzMDdlIiBJc3N1ZUluc3RhbnQ9IjIwMjAtMDgtMDRUMjI6MDQ6NDUuMDE2WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4KPGRzOlNpZ25lZEluZm8+CjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CjxkczpSZWZlcmVuY2UgVVJJPSIjX2MxNzMzNmEwLTUzNTMtNDE0OS1iNzJjLTAzZDlmOWFmMzA3ZSI+CjxkczpUcmFuc2Zvcm1zPgo8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPgo8L2RzOlRyYW5zZm9ybXM+CjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz4KPGRzOkRpZ2VzdFZhbHVlPjYzTmlyenFzaDVVa0h1a3NuRWUrM0hWWU5aYWFsQW1OQXFMc1lGMlRuRDA9PC9kczpEaWdlc3RWYWx1ZT4KPC9kczpSZWZlcmVuY2U+CjwvZHM6U2lnbmVkSW5mbz4KPGRzOlNpZ25hdHVyZVZhbHVlPgpLMVlvWWJVUjBTclY4RTdVMkhxTTIvZUNTOTNoV25mOExnNnozeGZWMUlyalgzSXhWYkNvMVlYcnRBSGRwRVdvYTJKKzVOMmFNbFBHJiMxMzsKN2VpbDBZRC9xdUVRamRYbTNwQTBjZmEvY25pa2RuKzVhbnM0ZWQwanU1amo2dkpvZ2w2Smt4Q25LWUpwTU9HNzhtampmb0phengrWCYjMTM7CkM2NktQVStBYUdxeGVwUEQ1ZlhRdTFKSy9Jb3lBaitaa3k4Z2Jwc3VyZHFCSEJLRWxjdnVOWS92UGY0OGtBeFZBKzdtRGhNNUMvL1AmIzEzOwp0L084Y3NZYXB2UjZjdjZrdk45QXZ1N3FRdm9qVk1McHVxZWNJZDJwTUVYb0NSSnE2Nkd4MStNTUVPeHVpMWZZQlRoMEhhYjRmK3JyJiMxMzsKOEY2V1NFRC8xZllVeHliRkJqZ1Q4d2lEWHFBRU8wSVY4ZWRQeEE9PQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8L2RzOlNpZ25hdHVyZT48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iQWUzZjQ5OGI4LTliMTctNDA3OC05ZDM1LTg2YTA4NDA4NDk5NSIgSXNzdWVJbnN0YW50PSIyMDIwLTA4LTA0VDIyOjA0OjQ1LjA3N1oiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3Vlcj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEPnRlc3RAc2FtbC51c2VyPC9zYW1sMjpOYW1lSUQ+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90QmVmb3JlPSIyMDIwLTA4LTA0VDIxOjU5OjQ1LjA5MFoiIE5vdE9uT3JBZnRlcj0iMjA0MC0wNy0zMFQyMjowNTowNi4wODhaIiBSZWNpcGllbnQ9Imh0dHBzOi8vcnAuZXhhbXBsZS5vcmcvYWNzIi8+PC9zYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDI6U3ViamVjdD48c2FtbDI6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMjAtMDgtMDRUMjE6NTk6NDUuMDgwWiIgTm90T25PckFmdGVyPSIyMDQwLTA3LTMwVDIyOjA1OjA2LjA4N1oiLz48L3NhbWwyOkFzc2VydGlvbj48L3NhbWwycDpSZXNwb25zZT4=";
private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock(AuthenticationConverter.class);
@Autowired
private ConfigurableApplicationContext context;
@@ -230,6 +237,33 @@ public class Saml2LoginConfigurerTests {
assertThat(exception.getCause()).isInstanceOf(IOException.class);
}
@Test
public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenValidates() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(CustomLoginProcessingUrlDefaultAuthenticationConverter.class)
.autowire())
.havingRootCause().isInstanceOf(IllegalStateException.class)
.withMessage("loginProcessingUrl must contain {registrationId} path variable");
}
@Test
public void authenticateWhenCustomLoginProcessingUrlAndCustomAuthenticationConverterThenAuthenticate()
throws Exception {
this.spring.register(CustomLoginProcessingUrlCustomAuthenticationConverter.class).autowire();
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
.assertingPartyDetails((party) -> party.verificationX509Credentials(
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE));
given(AUTHENTICATION_CONVERTER.convert(any(HttpServletRequest.class)))
.willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response));
// @formatter:off
MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE);
// @formatter:on
this.mvc.perform(request).andExpect(redirectedUrl("/"));
verify(AUTHENTICATION_CONVERTER).convert(any(HttpServletRequest.class));
}
private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
// get the OpenSamlAuthenticationProvider
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
@@ -337,10 +371,10 @@ public class Saml2LoginConfigurerTests {
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authz) -> authz
.anyRequest().authenticated()
)
.saml2Login(withDefaults());
.authorizeRequests((authz) -> authz
.anyRequest().authenticated()
)
.saml2Login(withDefaults());
// @formatter:on
}
@@ -359,11 +393,11 @@ public class Saml2LoginConfigurerTests {
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authz) -> authz
.anyRequest().authenticated()
)
.saml2Login((saml2) -> {
});
.authorizeRequests((authz) -> authz
.anyRequest().authenticated()
)
.saml2Login((saml2) -> {
});
// @formatter:on
}
@@ -395,6 +429,62 @@ public class Saml2LoginConfigurerTests {
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomAuthenticationConverterBean {
private final Saml2AuthenticationTokenConverter authenticationConverter = mock(
Saml2AuthenticationTokenConverter.class);
@Bean
SecurityFilterChain app(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
.saml2Login(Customizer.withDefaults());
return http.build();
}
@Bean
Saml2AuthenticationTokenConverter authenticationConverter() {
return this.authenticationConverter;
}
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomLoginProcessingUrlDefaultAuthenticationConverter {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.saml2Login((saml2) -> saml2.loginProcessingUrl("/my/custom/url"));
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class CustomLoginProcessingUrlCustomAuthenticationConverter {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests((authz) -> authz.anyRequest().authenticated())
.saml2Login((saml2) -> saml2
.loginProcessingUrl("/my/custom/url")
.authenticationConverter(AUTHENTICATION_CONVERTER)
);
// @formatter:on
return http.build();
}
}
static class Saml2LoginConfigBeans {
@Bean
@@ -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.
@@ -25,6 +25,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;
@@ -54,6 +55,17 @@ public class AuthenticationManagerBeanDefinitionParserTests {
+ "</authentication-manager>";
// @formatter:on
// 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();
@@ -64,6 +76,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).getContext();
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2020 the original author or authors.
* Copyright 2009-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,7 @@ import org.springframework.security.util.InMemoryResource;
/**
* @author Luke Taylor
* @author Eddú Meléndez
* @author Emil Sierżęga
*/
public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext {
@@ -42,7 +43,7 @@ public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext
+ "http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-2.5.xsd\n"
+ "http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security-";
static final String BEANS_CLOSE = "</b:beans>\n";
static final String SPRING_SECURITY_VERSION = "5.4";
static final String SPRING_SECURITY_VERSION = SpringSecurityVersions.getCurrentXsdVersionFromSpringSchemas();
Resource inMemoryXml;
@@ -0,0 +1,57 @@
/*
* 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.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.security.core.SpringSecurityCoreVersion;
/**
* For computing different Spring Security versions
*/
public final class SpringSecurityVersions {
static final Pattern SCHEMA_VERSION_PATTERN = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?");
public static String getCurrentXsdVersionFromSpringSchemas() {
Properties properties = new Properties();
try (InputStream is = SpringSecurityCoreVersion.class.getClassLoader()
.getResourceAsStream("META-INF/spring.schemas")) {
properties.load(is);
}
catch (IOException ex) {
throw new RuntimeException("Could not read 'META-INF/spring.schemas'", ex);
}
String inPackageLocation = properties
.getProperty("https://www.springframework.org/schema/security/spring-security.xsd");
Matcher matcher = SCHEMA_VERSION_PATTERN.matcher(inPackageLocation);
if (matcher.find()) {
return matcher.group(0);
}
throw new IllegalStateException("Failed to find version");
}
private SpringSecurityVersions() {
}
}
@@ -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.
@@ -266,7 +266,7 @@ public abstract class AbstractJaasAuthenticationProvider implements Authenticati
* Publishes the {@link JaasAuthenticationFailedEvent}. Can be overridden by
* 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) {
if (this.applicationEventPublisher != null) {
@@ -224,7 +224,7 @@ public class JaasAuthenticationProvider extends AbstractJaasAuthenticationProvid
* Publishes the {@link JaasAuthenticationFailedEvent}. Can be overridden by
* 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, AuthenticationException ase) {
@@ -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.
@@ -129,7 +129,7 @@ public class DelegatingPasswordEncoderTests {
}
@Test
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentExcetion() {
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.passwordEncoder.matches(this.rawPassword, "{bcrypt" + this.rawPassword))
.withMessage("There is no PasswordEncoder mapped for the id \"null\"");
+13 -13
View File
@@ -7,18 +7,18 @@ javaPlatform {
}
dependencies {
api platform("org.springframework:spring-framework-bom:5.3.8")
api platform("io.projectreactor:reactor-bom:2020.0.7")
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("io.projectreactor:reactor-bom:2020.0.12")
api platform("io.rsocket:rsocket-bom:1.1.1")
api platform("org.springframework.data:spring-data-bom:2021.0.1")
api platform("org.springframework.data:spring-data-bom:2021.0.6")
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.0")
api platform("com.fasterxml.jackson:jackson-bom:2.12.3")
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2")
api platform("com.fasterxml.jackson:jackson-bom:2.12.5")
constraints {
api "ch.qos.logback:logback-classic:1.2.3"
api "ch.qos.logback:logback-classic:1.2.6"
api "com.google.inject:guice:3.0"
api "com.nimbusds:nimbus-jose-jwt:9.10"
api "com.nimbusds:oauth2-oidc-sdk:9.9"
api "com.nimbusds:nimbus-jose-jwt:9.10.1"
api "com.nimbusds:oauth2-oidc-sdk:9.9.1"
api "com.squareup.okhttp3:mockwebserver:3.14.9"
api "com.squareup.okhttp3:okhttp:3.14.9"
api "com.unboundid:unboundid-ldapsdk:4.0.14"
@@ -49,8 +49,8 @@ dependencies {
api "org.assertj:assertj-core:3.19.0"
api "org.bouncycastle:bcpkix-jdk15on:1.68"
api "org.bouncycastle:bcprov-jdk15on:1.68"
api "org.eclipse.jetty:jetty-server:9.4.42.v20210604"
api "org.eclipse.jetty:jetty-servlet:9.4.42.v20210604"
api "org.eclipse.jetty:jetty-server:9.4.44.v20210927"
api "org.eclipse.jetty:jetty-servlet:9.4.44.v20210927"
api "org.eclipse.persistence:javax.persistence:2.2.1"
api "org.hibernate:hibernate-entitymanager:5.4.32.Final"
api "org.hsqldb:hsqldb:2.6.0"
@@ -71,9 +71,9 @@ dependencies {
api "org.seleniumhq.selenium:selenium-java:3.141.59"
api "org.seleniumhq.selenium:selenium-support:3.141.59"
api "org.skyscreamer:jsonassert:1.5.0"
api "org.slf4j:jcl-over-slf4j:1.7.31"
api "org.slf4j:log4j-over-slf4j:1.7.31"
api "org.slf4j:slf4j-api:1.7.31"
api "org.slf4j:jcl-over-slf4j:1.7.32"
api "org.slf4j:log4j-over-slf4j:1.7.32"
api "org.slf4j:slf4j-api:1.7.32"
api "org.springframework.ldap:spring-ldap-core:2.3.4.RELEASE"
api "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
}
@@ -5,8 +5,7 @@ asciidoctor {
baseDir = file('src/docs/asciidoc')
options eruby: 'erubis'
def ghTag = snapshotBuild ? 'main' : project.version
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$ghTag"
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$samplesBranch"
attributes copycss : '',
icons : 'font',
'source-highlighter': 'prettify',
@@ -18,23 +18,22 @@ asciidoctorj {
def ghTag = snapshotBuild ? 'main' : project.version
def ghUrl = "https://github.com/spring-projects/spring-security/tree/$ghTag"
def ghOldSamplesUrl = "https://github.com/spring-projects/spring-security/tree/5.4.x/samples"
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$ghTag"
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$samplesBranch"
def securityDocsUrl = "https://docs.spring.io/spring-security/site/docs/$docsTag"
def oldSecurityApiUrl = "https://docs.spring.io/spring-security/site/docs/5.4.x/api/"
def springVersion = "5.3.x"
def securityApiUrl = "$securityDocsUrl/api/"
def securityReferenceUrl = "$securityDocsUrl/reference/html5/"
def springFrameworkApiUrl = "https://docs.spring.io/spring-framework/docs/$springVersion/javadoc-api/"
def springFrameworkApiUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/javadoc-api/"
def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/reference/html/"
attributes 'spring-security-version' : project.version,
'spring-boot-version' : springBootVersion,
revnumber : project.version,
'gh-url': ghUrl,
'gh-samples-url': ghSamplesUrl,
'gh-old-samples-url': ghOldSamplesUrl,
'old-security-api-url': oldSecurityApiUrl,
'security-api-url': securityApiUrl,
'security-reference-url': securityReferenceUrl,
'spring-framework-api-url': springFrameworkApiUrl
'spring-framework-api-url': springFrameworkApiUrl,
'spring-framework-reference-url': springFrameworkReferenceUrl
attributeProvider resolvedVersions(project.configurations.testRuntimeClasspath)
}
@@ -184,7 +184,7 @@ then Spring Security's test support can come in handy.
Testing the method above with `WebTestClient` would require simulating some kind of grant flow with an authorization server.
Certainly this would be a daunting task, which is why Spring Security ships with support for removing this boilerplate.
For example, we can tell Spring Security to include a default `OidcUser` using the `SecurityMockServerConfigurers#oidcLogin` method, like so:
For example, we can tell Spring Security to include a default `OidcUser` using the `SecurityMockServerConfigurers#mockOidcLogin` method, like so:
[source,java]
----
@@ -311,7 +311,7 @@ public Mono<String> foo(@AuthenticationPrincipal OAuth2User oauth2User) {
}
----
In that case, we can tell Spring Security to include a default `OAuth2User` using the `SecurityMockServerConfigurers#oauth2User` method, like so:
In that case, we can tell Spring Security to include a default `OAuth2User` using the `SecurityMockServerConfigurers#mockOAuth2Login` method, like so:
[source,java]
----
@@ -431,7 +431,7 @@ public Mono<String> foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2Author
----
Simulating this handshake with the authorization server could be cumbersome.
Instead, you can use `SecurityMockServerConfigurers#oauth2Client` to add a `OAuth2AuthorizedClient` into a mock `ServerOAuth2AuthorizedClientRepository`:
Instead, you can use `SecurityMockServerConfigurers#mockOAuth2Client` to add a `OAuth2AuthorizedClient` into a mock `ServerOAuth2AuthorizedClientRepository`:
[source,java]
----
@@ -541,7 +541,7 @@ We'll look at two of them now:
==== `mockJwt() WebTestClientConfigurer`
The first way is via a `WebTestClientConfigurer`.
The simplest of these would look something like this:
The simplest of these would be to use the `SecurityMockServerConfigurers#mockJwt` method like the following:
[source,java]
----
@@ -664,7 +664,7 @@ public Mono<String> foo(BearerTokenAuthentication authentication) {
}
----
In that case, we can tell Spring Security to include a default `BearerTokenAuthentication` using the `SecurityMockServerConfigurers#opaqueToken` method, like so:
In that case, we can tell Spring Security to include a default `BearerTokenAuthentication` using the `SecurityMockServerConfigurers#mockOpaqueToken` method, like so:
[source,java]
----
@@ -9,7 +9,7 @@ The picture below shows the typical layering of the handlers for a single HTTP r
image::{figures}/filterchain.png[]
The client sends a request to the application, and the container creates a `FilterChain` which contains the ``Filter``s and `Servlet` that should process the `HttpServletRequest` based on the path of the request URI.
In a Spring MVC application the `Servlet` is an instance of https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet[`DispatcherServlet`].
In a Spring MVC application the `Servlet` is an instance of {spring-framework-reference-url}web.html#mvc-servlet[`DispatcherServlet`].
At most one `Servlet` can handle a single `HttpServletRequest` and `HttpServletResponse`.
However, more than one `Filter` can be used to:
@@ -1,7 +1,7 @@
[[servlet-events]]
== Authentication Events
For each authentication that succeeds or fails, a `AuthenticationSuccessEvent` or `AuthenticationFailureEvent` is fired, respectively.
For each authentication that succeeds or fails, a `AuthenticationSuccessEvent` or `AbstractAuthenticationFailureEvent` is fired, respectively.
To listen for these events, you must first publish an `AuthenticationEventPublisher`.
Spring Security's `DefaultAuthenticationEventPublisher` will probably do fine:
@@ -42,7 +42,7 @@ public class AuthenticationEvents {
}
@EventListener
public void onFailure(AuthenticationFailureEvent failures) {
public void onFailure(AbstractAuthenticationFailureEvent failures) {
// ...
}
}
@@ -70,7 +70,7 @@ While similar to `AuthenticationSuccessHandler` and `AuthenticationFailureHandle
=== Adding Exception Mappings
`DefaultAuthenticationEventPublisher` by default will publish an `AuthenticationFailureEvent` for the following events:
`DefaultAuthenticationEventPublisher` by default will publish an `AbstractAuthenticationFailureEvent` for the following events:
|============
| Exception | Event
@@ -97,7 +97,7 @@ To that end, you may want to supply additional mappings to the publisher via the
public AuthenticationEventPublisher authenticationEventPublisher
(ApplicationEventPublisher applicationEventPublisher) {
Map<Class<? extends AuthenticationException>,
Class<? extends AuthenticationFailureEvent>> mapping =
Class<? extends AbstractAuthenticationFailureEvent>> mapping =
Collections.singletonMap(FooException.class, FooEvent.class);
AuthenticationEventPublisher authenticationEventPublisher =
new DefaultAuthenticationEventPublisher(applicationEventPublisher);
@@ -125,7 +125,7 @@ This should therefore work with all major databases.
At the time of writing, the system had been successfully tested using Hypersonic SQL, PostgreSQL, Microsoft SQL Server and Oracle.
Two samples ship with Spring Security that demonstrate the ACL module.
The first is the Contacts Sample, and the other is the Document Management System (DMS) Sample.
The first is the {gh-samples-url}/servlet/xml/java/contacts[Contacts Sample], and the other is the {gh-samples-url}/servlet/xml/java/dms[Document Management System (DMS) Sample].
We suggest taking a look over these for examples.
@@ -258,7 +258,7 @@ Their use is enabled through the `global-method-security` namespace element:
===== Access Control using @PreAuthorize and @PostAuthorize
The most obviously useful annotation is `@PreAuthorize` which decides whether a method can actually be invoked or not.
For example (from the "Contacts" sample application)
For example (from the {gh-samples-url}/servlet/xml/java/contacts[Contacts] sample application)
====
.Java
@@ -492,7 +492,7 @@ This would look something like this:
Where `myPermissionEvaluator` is the bean which implements `PermissionEvaluator`.
Usually this will be the implementation from the ACL module which is called `AclPermissionEvaluator`.
See the "Contacts" sample application configuration for more details.
See the {gh-samples-url}/servlet/xml/java/contacts[Contacts] sample application configuration for more details.
===== Method Security Meta Annotations
@@ -2537,7 +2537,7 @@ The issuer should be one that the code can verify from a trusted source like a l
You may have observed that this strategy, while simple, comes with the trade-off that the JWT is parsed once by the `AuthenticationManagerResolver` and then again by the <<oauth2resourceserver-jwt-architecture-jwtdecoder,`JwtDecoder`>> later on in the request.
This extra parsing can be alleviated by configuring the <<oauth2resourceserver-jwt-architecture-jwtdecoder,`JwtDecoder`>> directly with a `JWTClaimSetAwareJWSKeySelector` from Nimbus:
This extra parsing can be alleviated by configuring the <<oauth2resourceserver-jwt-architecture-jwtdecoder,`JwtDecoder`>> directly with a `JWTClaimsSetAwareJWSKeySelector` from Nimbus:
====
.Java
@@ -2545,7 +2545,7 @@ This extra parsing can be alleviated by configuring the <<oauth2resourceserver-j
----
@Component
public class TenantJWSKeySelector
implements JWTClaimSetAwareJWSKeySelector<SecurityContext> {
implements JWTClaimsSetAwareJWSKeySelector<SecurityContext> {
private final TenantRepository tenants; <1>
private final Map<String, JWSKeySelector<SecurityContext>> selectors = new ConcurrentHashMap<>(); <2>
@@ -2586,7 +2586,7 @@ public class TenantJWSKeySelector
[source,kotlin,role="secondary"]
----
@Component
class TenantJWSKeySelector(tenants: TenantRepository) : JWTClaimSetAwareJWSKeySelector<SecurityContext> {
class TenantJWSKeySelector(tenants: TenantRepository) : JWTClaimsSetAwareJWSKeySelector<SecurityContext> {
private val tenants: TenantRepository <1>
private val selectors: MutableMap<String, JWSKeySelector<SecurityContext>> = ConcurrentHashMap() <2>
@@ -3028,7 +3028,7 @@ Additionally, it is published as an `AuthenticationFailureBadCredentialsEvent`,
@Component
public class FailureEvents {
@EventListener
public void onFailure(AuthenticationFailureEvent failure) {
public void onFailure(AuthenticationFailureBadCredentialsEvent badCredentials) {
if (badCredentials.getAuthentication() instanceof BearerTokenAuthenticationToken) {
// ... handle
}
@@ -319,7 +319,7 @@ class MyCustomSecurityConfiguration : WebSecurityConfigurerAdapter() {
The above requires the role of `USER` for any URL that starts with `/messages/`.
[[servlet-saml2login-relyingpartyregistrationrepository]]
The second `@Bean` Spring Boot creates is a {old-security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.html[`RelyingPartyRegistrationRepository`], which represents the asserting party and relying party metadata.
The second `@Bean` Spring Boot creates is a {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.html[`RelyingPartyRegistrationRepository`], which represents the asserting party and relying party metadata.
This includes things like the location of the SSO endpoint the relying party should use when requesting authentication from the asserting party.
You can override the default by publishing your own `RelyingPartyRegistrationRepository` bean.
@@ -422,7 +422,7 @@ A relying party can be multi-tenant by registering more than one relying party i
[[servlet-saml2login-relyingpartyregistration]]
=== RelyingPartyRegistration
A {old-security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[`RelyingPartyRegistration`]
A {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[`RelyingPartyRegistration`]
instance represents a link between an relying party and assering party's metadata.
In a `RelyingPartyRegistration`, you can provide relying party metadata like its `Issuer` value, where it expects SAML Responses to be sent to, and any credentials that it owns for the purposes of signing or decrypting payloads.
+5 -3
View File
@@ -1,9 +1,11 @@
aspectjVersion=1.9.6
aspectjVersion=1.9.7
springJavaformatVersion=0.0.28
springBootVersion=2.4.2
springFrameworkVersion=5.3.11
openSamlVersion=3.4.6
version=5.5.1
kotlinVersion=1.5.10
version=5.5.3
kotlinVersion=1.5.31
samplesBranch=5.5.x
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
@@ -25,7 +25,7 @@ dependencies {
testImplementation 'io.projectreactor.tools:blockhound'
testImplementation 'org.skyscreamer:jsonassert'
testImplementation 'io.r2dbc:r2dbc-h2:0.8.4.RELEASE'
testImplementation 'io.r2dbc:r2dbc-spi-test:0.8.5.RELEASE'
testImplementation 'io.r2dbc:r2dbc-spi-test:0.8.6.RELEASE'
testRuntimeOnly 'org.hsqldb:hsqldb'
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,6 @@
package org.springframework.security.oauth2.client.endpoint;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Set;
@@ -100,19 +97,7 @@ public abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientRegistration.getClientAuthenticationMethod())
|| ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
String clientId = encodeClientCredential(clientRegistration.getClientId());
String clientSecret = encodeClientCredential(clientRegistration.getClientSecret());
headers.setBasicAuth(clientId, clientSecret);
}
}
private static String encodeClientCredential(String clientCredential) {
try {
return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString());
}
catch (UnsupportedEncodingException ex) {
// Will not happen since UTF-8 is a standard charset
throw new IllegalArgumentException(ex);
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,6 @@
package org.springframework.security.oauth2.client.endpoint;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import org.springframework.core.convert.converter.Converter;
@@ -51,23 +48,11 @@ final class OAuth2AuthorizationGrantRequestEntityUtils {
headers.addAll(DEFAULT_TOKEN_REQUEST_HEADERS);
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientRegistration.getClientAuthenticationMethod())
|| ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
String clientId = encodeClientCredential(clientRegistration.getClientId());
String clientSecret = encodeClientCredential(clientRegistration.getClientSecret());
headers.setBasicAuth(clientId, clientSecret);
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
}
return headers;
}
private static String encodeClientCredential(String clientCredential) {
try {
return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString());
}
catch (UnsupportedEncodingException ex) {
// Will not happen since UTF-8 is a standard charset
throw new IllegalArgumentException(ex);
}
}
private static HttpHeaders getDefaultTokenRequestHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
@@ -16,11 +16,6 @@
package org.springframework.security.oauth2.client.endpoint;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
@@ -133,37 +128,4 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverterTests {
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes());
}
// gh-9610
@SuppressWarnings("unchecked")
@Test
public void convertWhenSpecialCharactersThenConvertsWithEncodedClientCredentials()
throws UnsupportedEncodingException {
String clientCredentialWithAnsiKeyboardSpecialCharacters = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;',./ ";
// @formatter:off
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials()
.clientId(clientCredentialWithAnsiKeyboardSpecialCharacters)
.clientSecret(clientCredentialWithAnsiKeyboardSpecialCharacters)
.build();
// @formatter:on
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest(
clientRegistration);
RequestEntity<?> requestEntity = this.converter.convert(clientCredentialsGrantRequest);
assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST);
assertThat(requestEntity.getUrl().toASCIIString())
.isEqualTo(clientRegistration.getProviderDetails().getTokenUri());
HttpHeaders headers = requestEntity.getHeaders();
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8);
assertThat(headers.getContentType())
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
String urlEncodedClientCredential = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters,
StandardCharsets.UTF_8.toString());
String clientCredentials = Base64.getEncoder().encodeToString(
(urlEncodedClientCredential + ":" + urlEncodedClientCredential).getBytes(StandardCharsets.UTF_8));
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic " + clientCredentials);
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody();
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE))
.isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue());
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,6 @@
package org.springframework.security.oauth2.client.endpoint;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
@@ -93,35 +89,6 @@ public class WebClientReactiveClientCredentialsTokenResponseClientTests {
assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser");
}
// gh-9610
@Test
public void getTokenResponseWhenSpecialCharactersThenSuccessWithEncodedClientCredentials() throws Exception {
// @formatter:off
enqueueJson("{\n"
+ " \"access_token\":\"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3\",\n"
+ " \"token_type\":\"bearer\",\n"
+ " \"expires_in\":3600,\n"
+ " \"refresh_token\":\"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk\",\n"
+ " \"scope\":\"create\"\n"
+ "}");
// @formatter:on
String clientCredentialWithAnsiKeyboardSpecialCharacters = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;',./ ";
OAuth2ClientCredentialsGrantRequest request = new OAuth2ClientCredentialsGrantRequest(
this.clientRegistration.clientId(clientCredentialWithAnsiKeyboardSpecialCharacters)
.clientSecret(clientCredentialWithAnsiKeyboardSpecialCharacters).build());
OAuth2AccessTokenResponse response = this.client.getTokenResponse(request).block();
RecordedRequest actualRequest = this.server.takeRequest();
String body = actualRequest.getBody().readUtf8();
assertThat(response.getAccessToken()).isNotNull();
String urlEncodedClientCredentialecret = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters,
StandardCharsets.UTF_8.toString());
String clientCredentials = Base64.getEncoder()
.encodeToString((urlEncodedClientCredentialecret + ":" + urlEncodedClientCredentialecret)
.getBytes(StandardCharsets.UTF_8));
assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic " + clientCredentials);
assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser");
}
@Test
public void getTokenResponseWhenPostThenSuccess() throws Exception {
ClientRegistration registration = this.clientRegistration
@@ -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.
@@ -133,11 +133,13 @@ public interface ClaimAccessor {
}
/**
* Returns the claim value as a {@code Map<String, Object>} or {@code null} if it does
* not exist or cannot be assigned to a {@code Map}.
* Returns the claim value as a {@code Map<String, Object>} or {@code null} if the
* claim does not exist.
* @param claim the name of the claim
* @return the claim value or {@code null} if it does not exist or cannot be assigned
* to a {@code Map}
* @return the claim value or {@code null} if the claim does not exist
* @throws IllegalArgumentException if the claim value cannot be converted to a
* {@code List}
* @throws NullPointerException if the claim value is {@code null}
*/
@SuppressWarnings("unchecked")
default Map<String, Object> getClaimAsMap(String claim) {
@@ -156,11 +158,13 @@ public interface ClaimAccessor {
}
/**
* Returns the claim value as a {@code List<String>} or {@code null} if it does not
* exist or cannot be assigned to a {@code List}.
* Returns the claim value as a {@code List<String>} or {@code null} if the claim does
* not exist.
* @param claim the name of the claim
* @return the claim value or {@code null} if it does not exist or cannot be assigned
* to a {@code List}
* @return the claim value or {@code null} if the claim does not exist
* @throws IllegalArgumentException if the claim value cannot be converted to a
* {@code List}
* @throws NullPointerException if the claim value is {@code null}
*/
@SuppressWarnings("unchecked")
default List<String> getClaimAsStringList(String claim) {
@@ -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.
@@ -90,7 +90,7 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> {
private OAuth2Error createOAuth2Error(String reason) {
this.logger.debug(reason);
return new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, reason,
return new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, reason,
"https://tools.ietf.org/html/rfc6750#section-3.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.
@@ -52,18 +52,19 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
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);
}
/**
@@ -87,6 +88,10 @@ 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.
@@ -143,9 +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) {
@@ -154,24 +166,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 : this.claimTypeConverters.entrySet()) {
if (!claims.containsKey(entry.getKey()) && entry.getValue().convert(null) != null) {
result.put(entry.getKey(), entry.getValue().convert(null));
}
}
return result;
}
}
@@ -28,6 +28,7 @@ import java.util.stream.Collectors;
import org.junit.Test;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
@@ -109,6 +110,7 @@ public class JwtTimestampValidatorTests {
.collect(Collectors.toList());
// @formatter:on
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors().iterator().next().getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
assertThat(messages).contains("Jwt used before " + justOverOneDayFromNow);
}
@@ -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.
@@ -123,8 +123,18 @@ public class MappedJwtClaimSetConverterTests {
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
}
// gh-10135
@Test
public void convertWhenConverterReturnsNullThenClaimIsRemoved() {
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
.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);
@@ -63,23 +63,21 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce
String filterProcessesUrl) {
this(new Saml2AuthenticationTokenConverter(
new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository)), filterProcessesUrl);
Assert.isTrue(filterProcessesUrl.contains("{registrationId}"),
"filterProcessesUrl must contain a {registrationId} match variable");
}
/**
* Creates a {@link Saml2WebSsoAuthenticationFilter} given the provided parameters
* @param authenticationConverter the strategy for converting an
* {@link HttpServletRequest} into an {@link Authentication}
* @param filterProcessingUrl the processing URL, must contain a {registrationId}
* variable
* @param filterProcessesUrl the processing URL
* @since 5.4
*/
public Saml2WebSsoAuthenticationFilter(AuthenticationConverter authenticationConverter,
String filterProcessingUrl) {
super(filterProcessingUrl);
public Saml2WebSsoAuthenticationFilter(AuthenticationConverter authenticationConverter, String filterProcessesUrl) {
super(filterProcessesUrl);
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
Assert.hasText(filterProcessingUrl, "filterProcessesUrl must contain a URL pattern");
Assert.isTrue(filterProcessingUrl.contains("{registrationId}"),
"filterProcessesUrl must contain a {registrationId} match variable");
Assert.hasText(filterProcessesUrl, "filterProcessesUrl must contain a URL pattern");
this.authenticationConverter = authenticationConverter;
setAllowSessionCreation(true);
setSessionAuthenticationStrategy(new ChangeSessionIdAuthenticationStrategy());
@@ -62,7 +62,7 @@ import org.springframework.web.util.UriUtils;
*
* <p>
* By default, this {@code Filter} responds to authentication requests at the {@code URI}
* {@code /oauth2/authorization/{registrationId}}. The {@code URI} template variable
* {@code /saml2/authenticate/{registrationId}}. The {@code URI} template variable
* {@code {registrationId}} represents the
* {@link RelyingPartyRegistration#getRegistrationId() registration identifier} of the
* relying party that is used for initiating the authentication request.
@@ -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.
@@ -26,6 +26,7 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.web.authentication.AuthenticationConverter;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
@@ -60,6 +61,12 @@ public class Saml2WebSsoAuthenticationFilterTests {
this.filter = new Saml2WebSsoAuthenticationFilter(this.repository, "/url/variable/is/present/{registrationId}");
}
@Test
public void constructingFilterWithMissingRegistrationIdVariableAndCustomAuthenticationConverterThenSucceeds() {
AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class);
this.filter = new Saml2WebSsoAuthenticationFilter(authenticationConverter, "/url/missing/variable");
}
@Test
public void requiresAuthenticationWhenHappyPathThenReturnsTrue() {
Assert.assertTrue(this.filter.requiresAuthentication(this.request, this.response));
@@ -175,6 +175,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
Authentication targetUser = attemptSwitchUser(request);
// update the current context to the new target user
SecurityContextHolder.getContext().setAuthentication(targetUser);
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser));
// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, targetUser);
}
@@ -189,10 +190,13 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
Authentication originalUser = attemptExitUser(request);
// update the current context back to the original user
SecurityContextHolder.getContext().setAuthentication(originalUser);
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);
}
@@ -211,12 +215,11 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
UsernamePasswordAuthenticationToken targetUserRequest;
String username = request.getParameter(this.usernameParameter);
username = (username != null) ? username : "";
this.logger.debug(LogMessage.format("Attempt to switch to user [%s]", 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);
this.logger.debug(LogMessage.format("Switch User Token [%s]", targetUserRequest));
// publish event
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
@@ -245,9 +248,9 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
// if so, get the original source user so we can switch back
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
UserDetails originalUser = null;
@@ -322,7 +325,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv
// 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));
}
}
return original;
@@ -158,8 +158,12 @@ public class SwitchUserWebFilter implements WebFilter {
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
final WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
return switchUser(webFilterExchange).switchIfEmpty(Mono.defer(() -> exitSwitchUser(webFilterExchange)))
.switchIfEmpty(Mono.defer(() -> chain.filter(exchange).then(Mono.empty())))
.flatMap((authentication) -> onAuthenticationSuccess(authentication, webFilterExchange))
.switchIfEmpty(Mono.defer(() -> {
this.logger.trace(
LogMessage.format("Did not attempt to switch user since request did not match [%s] or [%s]",
this.switchUserMatcher, this.exitUserMatcher));
return chain.filter(exchange).then(Mono.empty());
})).flatMap((authentication) -> onAuthenticationSuccess(authentication, webFilterExchange))
.onErrorResume(SwitchUserAuthenticationException.class, (exception) -> Mono.empty());
}
@@ -211,7 +215,7 @@ public class SwitchUserWebFilter implements WebFilter {
@NonNull
private Mono<Authentication> attemptSwitchUser(Authentication currentAuthentication, String userName) {
Assert.notNull(userName, "The userName can not be null.");
this.logger.debug(LogMessage.format("Attempt to switch to user [%s]", userName));
this.logger.debug(LogMessage.format("Attempting to switch to user [%s]", userName));
return this.userDetailsService.findByUsername(userName)
.switchIfEmpty(Mono.error(this::noTargetAuthenticationException))
.doOnNext(this.userDetailsChecker::check)
@@ -222,7 +226,7 @@ public class SwitchUserWebFilter implements WebFilter {
private Authentication attemptExitUser(Authentication currentAuthentication) {
Optional<Authentication> sourceAuthentication = extractSourceAuthentication(currentAuthentication);
if (!sourceAuthentication.isPresent()) {
this.logger.debug("Could not find original user Authentication object!");
this.logger.debug("Failed to find original user");
throw noOriginalAuthenticationException();
}
return sourceAuthentication.get();
@@ -232,13 +236,14 @@ public class SwitchUserWebFilter implements WebFilter {
ServerWebExchange exchange = webFilterExchange.getExchange();
SecurityContextImpl securityContext = new SecurityContextImpl(authentication);
return this.securityContextRepository.save(exchange, securityContext)
.doOnSuccess((v) -> this.logger.debug(LogMessage.format("Switched user to %s", authentication)))
.then(this.successHandler.onAuthenticationSuccess(webFilterExchange, authentication))
.subscriberContext(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)));
}
private Mono<Void> onAuthenticationFailure(AuthenticationException exception, WebFilterExchange webFilterExchange) {
return Mono.justOrEmpty(this.failureHandler).switchIfEmpty(Mono.defer(() -> {
this.logger.error("Switch User failed", exception);
this.logger.debug("Failed to switch user", exception);
return Mono.error(exception);
})).flatMap((failureHandler) -> failureHandler.onAuthenticationFailure(webFilterExchange, exception));
}
@@ -247,7 +252,7 @@ public class SwitchUserWebFilter implements WebFilter {
Optional<Authentication> sourceAuthentication = extractSourceAuthentication(currentAuthentication);
if (sourceAuthentication.isPresent()) {
// SEC-1763. Check first if we are already switched.
this.logger.info(
this.logger.debug(
LogMessage.format("Found original switch user granted authority [%s]", sourceAuthentication.get()));
currentAuthentication = sourceAuthentication.get();
}