1
0
mirror of synced 2026-07-09 21:00:02 +00:00

Compare commits

...

65 Commits

Author SHA1 Message Date
Joe Grandja fa4c5449e7 Release 5.6.6 2022-06-20 14:50:24 -04:00
Joe Grandja 2a3845a7ed Update org.opensaml:opensaml-core4 to 4.1.1
Closes gh-11420
2022-06-20 14:50:24 -04:00
Joe Grandja ff96a7b5ad Update spring-ldap-core to 2.3.8.RELEASE
Closes gh-11419
2022-06-20 14:41:13 -04:00
Joe Grandja c37ff42234 Update org.springframework.data to 2021.1.5
Closes gh-11418
2022-06-20 14:41:10 -04:00
Joe Grandja 0a00d84838 Update org.springframework to 5.3.21
Closes gh-11417
2022-06-20 14:41:08 -04:00
Joe Grandja 96c6751a1d Update hibernate-entitymanager to 5.6.9.Final
Closes gh-11416
2022-06-20 14:41:05 -04:00
Joe Grandja 8ee9c32788 Update io.projectreactor to 2020.0.20
Closes gh-11414
2022-06-20 14:41:00 -04:00
Joe Grandja 7a5fb9eaf7 Update jackson-bom to 2.13.3
Closes gh-11411
2022-06-20 14:40:53 -04:00
Joe Grandja 8cbb972cef Add dependency update exclusion for spring-javaformat-checkstyle 2022-06-20 14:27:40 -04:00
Josh Cummings 539a11d0a4 Encode postLogoutRedirectUri query params
Closes gh-11379
2022-06-16 16:13:42 -06:00
Zhivko Delchev e97c5a533b Reverse content type check
When MultipartFormData is enabled currently the CsrfWebFilter compares
the content-type header against MULTIPART_FORM_DATA MediaType which
leads to NullPointerExecption when there is no content-type header.
This commit reverse the check to compare the MULTIPART_FORM_DATA
MediaType against the content-type which contains null check and avoids
the exception.

closes gh-11204
Closes gh-11205
2022-06-06 15:47:35 -05:00
shirohoo e0fa644b08 Fix typo in BasicLookupStrategy Javadoc
Closes gh-11336
2022-06-06 14:16:28 -05:00
Rob Winch 592db9180d Enable BackportBot on 5.6.x 2022-06-06 14:16:28 -05:00
André Luis Gomes aca3fc2412 Update opaque-token.adoc
Fixing yaml sample in Servlet and Reactive pages
2022-06-01 08:51:44 -03:00
Claudio Consolmagno 07f9afe057 Use 'md:' prefix in EntityDescriptor XML
Create the EntityDescriptor object with
EntityDescriptor.DEFAULT_ELEMENT_NAME instead of
EntityDescriptor.ELEMENT_QNAME. That ensures the EntityDescriptor tag
is marshalled to xml with the 'md:' prefix, consistent with all other
metadata tags.

Closes #11283
2022-05-31 17:11:02 -06:00
Josh Cummings d7077b441a Correct access(String) reference
Closes gh-11280
2022-05-27 15:00:15 -06:00
Josh Cummings 101f11ba94 Improve ContextConfiguration Docs
Point to updated Spring Reference

Issue gh-10934
2022-05-27 13:12:56 -06:00
Josh Cummings 18b903f6e3 Polish ExtendWith Docs
Use spring-framework-reference-url placeholder

Issue gh-10934
2022-05-27 13:12:56 -06:00
nor-ek 038266a94f Update JUnit 5 annotations in documentation
- replace Before with BeforeEach
- replace RunWith with ExtendWith

Closes gh-10934
2022-05-27 13:12:55 -06:00
Evgeniy Cheban cf559ab224 Some Security Expressions cause NPE when used within Query annotation
Added trustResolver, roleHierarchy, permissionEvaluator, defaultRolePrefix
fields to SecurityEvaluationContextExtension.

Closes gh-11196
Closes gh-11290
2022-05-26 17:44:28 -05:00
Juny Tse 649428b49a Use Base64 encoder with no CRLF in output for SAML 2.0 messages
Closes gh-11262
2022-05-25 12:06:27 -06:00
Steve Riesenberg 0355e960d7 Next development version 2022-05-18 11:52:05 -05:00
Steve Riesenberg fdad14af63 Release 5.6.5 2022-05-18 11:03:25 -05:00
Rob Winch 077c9e0b3e StrictHttpFirewall allows CJKV characters
Closes gh-11264
2022-05-18 08:56:57 -05:00
Rob Winch dc648cf79f Next Developement Version 2022-05-16 11:51:06 -05:00
Rob Winch c2d2914a4f Release 5.6.4 2022-05-16 11:36:26 -05:00
Josh Cummings a40f73521c Improve Upgrading 2022-05-16 11:36:26 -05:00
Rob Winch 70863952ae AntRegexRequestMatcher Optimization
Closes gh-11234
2022-05-16 10:17:44 -05:00
Rob Winch af95be34c6 Extract rejectNonPrintableAsciiCharactersInFieldName
Closes gh-11234
2022-05-16 10:17:44 -05:00
Rob Winch 71478c2534 Update org.springframework.data to 2021.1.4
Closes gh-11210
2022-05-13 09:28:59 -05:00
Rob Winch b289d12aad Update org.springframework to 5.3.20
Closes gh-11209
2022-05-13 09:28:56 -05:00
Rob Winch e2361c5c79 Update io.projectreactor to 2020.0.19
Closes gh-11207
2022-05-13 09:28:52 -05:00
Rob Winch 8b9c8ca96c Update mockk to 1.12.4
Closes gh-11206
2022-05-13 09:28:49 -05:00
Marcus Da Coregio 44e672d969 Use properties in the checkSamples job
Issue gh-10344
2022-05-11 16:13:34 -03:00
Marcus Da Coregio 330f0f050d Add initScripts and projectProperties to IncludeCheckRemotePlugin
Issue gh-10344
2022-05-11 16:13:34 -03:00
Marcus Da Coregio e45dcb3ab2 Update copyright headers
Issue gh-10956
2022-05-06 14:18:42 -03:00
Marcus Da Coregio d3a451fffb Fix mvcMatchers overriding previous paths
Closes gh-10956
2022-05-06 14:18:36 -03:00
Marcus Da Coregio 9d378103b0 Fix setServletContext not being called for AuthorizationManagerWebInvocationPrivilegeEvaluator
Issue gh-10908
2022-04-25 09:43:50 -03:00
Marcus Da Coregio 2031f101dc Exclude duplicate issues from changelog
Closes gh-11154
2022-04-20 09:07:16 -03:00
Marcus Da Coregio 2a2c2dd209 Next development version 2022-04-18 13:27:01 -03:00
Marcus Da Coregio fa0e06ebdc Release 5.6.3 2022-04-18 11:59:42 -03:00
Marcus Da Coregio 6724627b50 Update spring-ldap-core to 2.3.7.RELEASE
Closes gh-11127
2022-04-18 11:45:27 -03:00
Marcus Da Coregio 4aab7b06a9 Update org.springframework.data to 2021.1.3
Closes gh-11126
2022-04-18 11:45:24 -03:00
Marcus Da Coregio d0d4d58c48 Update org.springframework to 5.3.19
Closes gh-11125
2022-04-18 11:45:22 -03:00
Marcus Da Coregio 29be60d035 Update hibernate-entitymanager to 5.6.8.Final
Closes gh-11124
2022-04-18 11:45:19 -03:00
Marcus Da Coregio 5024103b07 Update org.eclipse.jetty to 9.4.46.v20220331
Closes gh-11123
2022-04-18 11:45:17 -03:00
Marcus Da Coregio d0327807a7 Update org.aspectj to 1.9.9.1
Closes gh-11122
2022-04-18 11:45:14 -03:00
Marcus Da Coregio f880f1ba26 Update io.rsocket to 1.1.2
Closes gh-11121
2022-04-18 11:45:12 -03:00
Marcus Da Coregio 71dafc0c06 Update io.projectreactor to 2020.0.18
Closes gh-11119
2022-04-18 11:45:07 -03:00
Marcus Da Coregio 7d9357b871 Update mockk to 1.12.3
Closes gh-11118
2022-04-18 11:45:05 -03:00
Marcus Da Coregio 0b02bbecd1 Update jackson-bom to 2.13.2.20220328
Closes gh-11115
2022-04-18 11:44:57 -03:00
Marcus Da Coregio 932a5cc8b4 Update logback-classic to 1.2.11
Closes gh-11114
2022-04-18 11:44:55 -03:00
Simone Giannino e4a321511a Update saganCreateRelease property referenceDocUrl
- Updated saganCreateRelease task with the new referenceDocUrl for reference documentation

Closes gh-11016
2022-04-01 13:08:18 -03:00
Johannes Graf 4ee5800ec9 use okta as registration id
looks like `ping` is some registration id used in the past.

Closes gh-11034
2022-03-30 14:41:03 -06:00
Marcus Da Coregio c67632225d Use ServletContext in AuthorizationManagerWebInvocationPrivilegeEvaluator
Closes gh-10908
2022-03-28 10:13:40 -03:00
Rob Winch 6e45a376cd Remove "Hi ... there" From Docs
Close gh-10963
2022-03-11 13:42:38 -06:00
Marcus Da Coregio 70b67cd2f1 AuthorizationManagerWebInvocationPrivilegeEvaluator grant access when AuthorizationManager abstains
Closes gh-10950
2022-03-09 15:22:21 -03:00
Josh Cummings 304e89041c Polish Formatting
Issue gh-10799
2022-03-02 16:40:13 -07:00
Sander van Schouwenburg f1a76efc2d Preserve order of RelyingPartRegistration credentials
Issue gh-10799
2022-03-02 16:40:13 -07:00
Josh Cummings 963251314b Replace Apache Commons Base64 Decoding
Issue gh-10923
2022-03-02 16:40:11 -07:00
Josh Cummings ee061f3659 Use RFC2045 Encoding for SAML 2.0 Logout
Closes gh-10923
2022-03-02 16:39:31 -07:00
Talerngpong Virojwutikul 440ffce2eb Update PasswordEncoder declaration
Closes gh-10910
2022-03-01 07:50:55 -07:00
Filip Hanik 47871562ca Change HashSet to LinkedHashSet
For various RelyingPartyRegistration.credentials to preserve order of insertion.

Issue gh-10799
2022-02-28 15:02:03 -07:00
Josh Cummings f0c548cee7 Invert Log Messages
Closes gh-10909
2022-02-28 13:17:01 -07:00
Marcus Da Coregio 1a32b399e6 Next development version 2022-02-21 10:49:06 -03:00
56 changed files with 1090 additions and 144 deletions
+26
View File
@@ -0,0 +1,26 @@
name: Backport Bot
on:
issues:
types: [labeled]
pull_request:
types: [labeled]
push:
branches:
- '*.x'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Download BackportBot
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT: ${{ toJSON(github.event) }}
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
@@ -95,11 +95,15 @@ jobs:
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Check samples project
env:
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
SAMPLES_INIT_SCRIPT: ${{ github.workspace }}/build/includeRepo/spring-security-ci.gradle
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 checkSamples --stacktrace
./gradlew publishMavenJavaPublicationToLocalRepository
./gradlew checkSamples -PsamplesInitScript="$SAMPLES_INIT_SCRIPT" -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" --stacktrace
check_tangles:
name: Check for Package Tangles
needs: [ prerequisites ]
@@ -73,8 +73,8 @@ import org.springframework.util.Assert;
* one in <tt>lookupObjectIdentities</tt>. These are built from the same select and "order
* by" clause, using a different where clause in each case. In order to use custom schema
* or column names, each of these SQL clauses can be customized, but they must be
* consistent with each other and with the expected result set generated by the the
* default values.
* consistent with each other and with the expected result set generated by the default
* values.
*
* @author Ben Alex
*/
+17 -1
View File
@@ -35,7 +35,7 @@ repositories {
}
tasks.named("saganCreateRelease") {
referenceDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/"
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
}
@@ -95,6 +95,18 @@ updateDependenciesSettings {
selection.reject("org.opensaml maintains two different versions, so it must be updated manually");
}
}
components.withModule("io.spring.javaformat:spring-javaformat-gradle-plugin") { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("spring-javaformat-gradle-plugin updates break checkstyle");
}
}
components.withModule("io.spring.javaformat:spring-javaformat-checkstyle") { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("spring-javaformat-checkstyle updates break checkstyle");
}
}
}
}
}
@@ -154,6 +166,10 @@ tasks.register('checkSamples') {
includeCheckRemote {
repository = 'spring-projects/spring-security-samples'
ref = samplesBranch
if (project.hasProperty("samplesInitScript")) {
initScripts = [samplesInitScript]
projectProperties = ["localRepositoryPath": localRepositoryPath, "springSecurityVersion": project.version]
}
}
dependsOn checkRemote
}
+1 -1
View File
@@ -80,7 +80,7 @@ dependencies {
implementation localGroovy()
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
implementation 'io.projectreactor:reactor-core:3.4.15'
implementation 'io.projectreactor:reactor-core:3.4.19'
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'
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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
@@ -19,7 +19,6 @@ package io.spring.gradle.convention
import io.spring.gradle.IncludeRepoTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.api.tasks.GradleBuild
import org.gradle.api.tasks.TaskProvider
@@ -40,6 +39,12 @@ class IncludeCheckRemotePlugin implements Plugin<Project> {
it.dependsOn 'includeRepo'
it.dir = includeRepoTask.get().outputDirectory
it.tasks = extension.getTasks()
extension.getInitScripts().forEach {script ->
it.startParameter.addInitScript(new File(script))
}
extension.getProjectProperties().entrySet().forEach { entry ->
it.startParameter.projectProperties.put(entry.getKey(), entry.getValue())
}
}
}
@@ -60,6 +65,16 @@ class IncludeCheckRemotePlugin implements Plugin<Project> {
*/
List<String> tasks = ['check']
/**
* Init scripts for the build
*/
List<String> initScripts = []
/**
* Map of properties for the build
*/
Map<String, String> projectProperties = [:]
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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
@@ -16,6 +16,11 @@
package io.spring.gradle.convention;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import io.spring.gradle.IncludeRepoTask;
import org.apache.commons.io.FileUtils;
import org.gradle.api.Project;
@@ -24,8 +29,6 @@ import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
class IncludeCheckRemotePluginTest {
@@ -68,6 +71,40 @@ class IncludeCheckRemotePluginTest {
assertThat(checkRemote.getTasks()).containsExactly("clean", "build", "test");
}
@Test
void applyWhenExtensionPropertiesInitScriptsThenCreateCheckRemoteWithProvidedTasks() {
this.rootProject = ProjectBuilder.builder().build();
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
(includeCheckRemoteExtension) -> {
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
includeCheckRemoteExtension.setProperty("ref", "main");
includeCheckRemoteExtension.setProperty("initScripts", Arrays.asList("spring-security-ci.gradle"));
});
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
assertThat(checkRemote.getStartParameter().getAllInitScripts()).extracting(File::getName).containsExactly("spring-security-ci.gradle");
}
@Test
void applyWhenExtensionPropertiesBuildPropertiesThenCreateCheckRemoteWithProvidedTasks() {
Map<String, String> projectProperties = new HashMap<>();
projectProperties.put("localRepositoryPath", "~/local/repository");
projectProperties.put("anotherProperty", "some_value");
this.rootProject = ProjectBuilder.builder().build();
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
(includeCheckRemoteExtension) -> {
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
includeCheckRemoteExtension.setProperty("ref", "main");
includeCheckRemoteExtension.setProperty("projectProperties", projectProperties);
});
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
assertThat(checkRemote.getStartParameter().getProjectProperties()).containsEntry("localRepositoryPath", "~/local/repository")
.containsEntry("anotherProperty", "some_value");
}
@Test
void applyWhenExtensionPropertiesThenRegisterIncludeRepoTaskWithExtensionProperties() {
this.rootProject = ProjectBuilder.builder().build();
@@ -58,7 +58,7 @@ public class SaganApiTests {
Release release = new Release();
release.setVersion("5.6.0-SNAPSHOT");
release.setApiDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/api/");
release.setReferenceDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/");
release.setReferenceDocUrl("https://docs.spring.io/spring-security/reference/{version}/index.html");
this.sagan.createReleaseForProject(release, "spring-security");
RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases");
@@ -67,7 +67,7 @@ public class SaganApiTests {
assertThat(request.getBody().readString(Charset.defaultCharset())).isEqualToIgnoringWhitespace("{\n" +
" \"version\":\"5.6.0-SNAPSHOT\",\n" +
" \"current\":false,\n" +
" \"referenceDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/\",\n" +
" \"referenceDocUrl\":\"https://docs.spring.io/spring-security/reference/{version}/index.html\",\n" +
" \"apiDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/api/\"\n" +
"}");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -3283,20 +3283,26 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
*/
public final class MvcMatchersRequestMatcherConfigurer extends RequestMatcherConfigurer {
private final List<MvcRequestMatcher> mvcMatchers;
/**
* Creates a new instance
* @param context the {@link ApplicationContext} to use
* @param matchers the {@link MvcRequestMatcher} instances to set the servlet path
* on if {@link #servletPath(String)} is set.
* @param mvcMatchers the {@link MvcRequestMatcher} instances to set the servlet
* path on if {@link #servletPath(String)} is set.
* @param allMatchers the {@link RequestMatcher} instances to continue the
* configuration
*/
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context, List<MvcRequestMatcher> matchers) {
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context, List<MvcRequestMatcher> mvcMatchers,
List<RequestMatcher> allMatchers) {
super(context);
this.matchers = new ArrayList<>(matchers);
this.mvcMatchers = new ArrayList<>(mvcMatchers);
this.matchers = allMatchers;
}
public RequestMatcherConfigurer servletPath(String servletPath) {
for (RequestMatcher matcher : this.matchers) {
((MvcRequestMatcher) matcher).setServletPath(servletPath);
for (MvcRequestMatcher matcher : this.mvcMatchers) {
matcher.setServletPath(servletPath);
}
return this;
}
@@ -3321,7 +3327,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
public MvcMatchersRequestMatcherConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
setMatchers(mvcMatchers);
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers);
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers, this.matchers);
}
@Override
@@ -344,7 +344,10 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
if (filter instanceof AuthorizationFilter) {
AuthorizationManager<HttpServletRequest> authorizationManager = ((AuthorizationFilter) filter)
.getAuthorizationManager();
privilegeEvaluators.add(new AuthorizationManagerWebInvocationPrivilegeEvaluator(authorizationManager));
AuthorizationManagerWebInvocationPrivilegeEvaluator evaluator = new AuthorizationManagerWebInvocationPrivilegeEvaluator(
authorizationManager);
evaluator.setServletContext(this.servletContext);
privilegeEvaluators.add(evaluator);
}
}
return new RequestMatcherEntry<>(securityFilterChain::matches, privilegeEvaluators);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -21,17 +21,22 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
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;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.PortMapperImpl;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.channel.InsecureChannelProcessor;
import org.springframework.security.web.access.channel.SecureChannelProcessor;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
@@ -44,6 +49,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Rob Winch
* @author Eleftheria Stein
* @author Onur Kagan Ozcan
*/
@ExtendWith(SpringTestContextExtension.class)
public class ChannelSecurityConfigurerTests {
@@ -93,6 +99,24 @@ public class ChannelSecurityConfigurerTests {
this.mvc.perform(get("/")).andExpect(redirectedUrl("https://localhost/"));
}
// gh-10956
@Test
public void requestWhenRequiresChannelWithMultiMvcMatchersThenRedirectsToHttps() throws Exception {
this.spring.register(RequiresChannelMultiMvcMatchersConfig.class).autowire();
this.mvc.perform(get("/test-1")).andExpect(redirectedUrl("https://localhost/test-1"));
this.mvc.perform(get("/test-2")).andExpect(redirectedUrl("https://localhost/test-2"));
this.mvc.perform(get("/test-3")).andExpect(redirectedUrl("https://localhost/test-3"));
}
// gh-10956
@Test
public void requestWhenRequiresChannelWithMultiMvcMatchersInLambdaThenRedirectsToHttps() throws Exception {
this.spring.register(RequiresChannelMultiMvcMatchersInLambdaConfig.class).autowire();
this.mvc.perform(get("/test-1")).andExpect(redirectedUrl("https://localhost/test-1"));
this.mvc.perform(get("/test-2")).andExpect(redirectedUrl("https://localhost/test-2"));
this.mvc.perform(get("/test-3")).andExpect(redirectedUrl("https://localhost/test-3"));
}
@EnableWebSecurity
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
@@ -155,4 +179,59 @@ public class ChannelSecurityConfigurerTests {
}
@EnableWebSecurity
@EnableWebMvc
static class RequiresChannelMultiMvcMatchersConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.portMapper()
.portMapper(new PortMapperImpl())
.and()
.requiresChannel()
.mvcMatchers("/test-1")
.requiresSecure()
.mvcMatchers("/test-2")
.requiresSecure()
.mvcMatchers("/test-3")
.requiresSecure()
.anyRequest()
.requiresInsecure();
// @formatter:on
return http.build();
}
}
@EnableWebSecurity
@EnableWebMvc
static class RequiresChannelMultiMvcMatchersInLambdaConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.portMapper((port) -> port
.portMapper(new PortMapperImpl())
)
.requiresChannel((channel) -> channel
.mvcMatchers("/test-1")
.requiresSecure()
.mvcMatchers("/test-2")
.requiresSecure()
.mvcMatchers("/test-3")
.requiresSecure()
.anyRequest()
.requiresInsecure()
);
// @formatter:on
return http.build();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -23,7 +23,10 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -33,6 +36,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
@@ -167,6 +171,38 @@ public class HttpSecurityRequestMatchersTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test
public void requestMatcherWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception {
loadConfig(MultiMvcMatcherInLambdaConfig.class);
this.request.setRequestURI("/test-1");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-2");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-3");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void requestMatcherWhenMultiMvcMatcherThenAllPathsAreDenied() throws Exception {
loadConfig(MultiMvcMatcherConfig.class);
this.request.setRequestURI("/test-1");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-2");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/test-3");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
@@ -175,6 +211,101 @@ public class HttpSecurityRequestMatchersTests {
this.context.getAutowireCapableBeanFactory().autowireBean(this);
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class MultiMvcMatcherInLambdaConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain first(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests
.mvcMatchers("/test-1")
.mvcMatchers("/test-2")
.mvcMatchers("/test-3")
)
.authorizeRequests((authorize) -> authorize.anyRequest().denyAll())
.httpBasic(withDefaults());
// @formatter:on
return http.build();
}
@Bean
SecurityFilterChain second(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests
.mvcMatchers("/test-1")
)
.authorizeRequests((authorize) -> authorize
.anyRequest().permitAll()
);
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping({ "/test-1", "/test-2", "/test-3" })
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class MultiMvcMatcherConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain first(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers()
.mvcMatchers("/test-1")
.mvcMatchers("/test-2")
.mvcMatchers("/test-3")
.and()
.authorizeRequests()
.anyRequest().denyAll()
.and()
.httpBasic(withDefaults());
// @formatter:on
return http.build();
}
@Bean
SecurityFilterChain second(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers()
.mvcMatchers("/test-1")
.and()
.authorizeRequests()
.anyRequest().permitAll();
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping({ "/test-1", "/test-2", "/test-3" })
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2022 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.config.annotation.web.configurers;
import java.util.Base64;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
@@ -23,16 +25,24 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
@@ -125,6 +135,35 @@ public class UrlAuthorizationConfigurerTests {
loadConfig(AnonymousUrlAuthorizationConfig.class);
}
// gh-10956
@Test
public void multiMvcMatchersConfig() throws Exception {
loadConfig(MultiMvcMatcherConfig.class);
this.request.addHeader("Authorization",
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
this.request.setRequestURI("/test-1");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
setup();
this.request.addHeader("Authorization",
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
this.request.setRequestURI("/test-2");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
setup();
this.request.addHeader("Authorization",
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
this.request.setRequestURI("/test-3");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
setup();
this.request.addHeader("Authorization",
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
this.request.setRequestURI("/test-x");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
@@ -228,4 +267,41 @@ public class UrlAuthorizationConfigurerTests {
}
@EnableWebSecurity
@EnableWebMvc
static class MultiMvcMatcherConfig {
@Bean
SecurityFilterChain security(HttpSecurity http, ApplicationContext context) throws Exception {
// @formatter:off
http
.httpBasic(Customizer.withDefaults())
.apply(new UrlAuthorizationConfigurer<>(context)).getRegistry()
.mvcMatchers("/test-1").hasRole("ADMIN")
.mvcMatchers("/test-2").hasRole("ADMIN")
.mvcMatchers("/test-3").hasRole("ADMIN")
.anyRequest().hasRole("USER");
// @formatter:on
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@RestController
static class PathController {
@RequestMapping({ "/test-1", "/test-2", "/test-3", "/test-x" })
String path() {
return "path";
}
}
}
}
@@ -526,35 +526,47 @@ public class BCrypt {
* @param safety bit 16 is set when the safety measure is requested
* @return an array containing the binary hashed password
*/
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, boolean sign_ext_bug, int safety) {
int rounds, i, j;
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, boolean sign_ext_bug, int safety,
boolean for_check) {
int cdata[] = bf_crypt_ciphertext.clone();
int clen = cdata.length;
byte ret[];
long rounds;
if (log_rounds < 4 || log_rounds > 31) {
throw new IllegalArgumentException("Bad number of rounds");
if (!for_check) {
throw new IllegalArgumentException("Bad number of rounds");
}
if (log_rounds != 0) {
throw new IllegalArgumentException("Bad number of rounds");
}
rounds = 0;
}
rounds = 1 << log_rounds;
else {
rounds = roundsForLogRounds(log_rounds);
if (rounds < 16 || rounds > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Bad number of rounds");
}
}
if (salt.length != BCRYPT_SALT_LEN) {
throw new IllegalArgumentException("Bad salt length");
}
init_key();
ekskey(salt, password, sign_ext_bug, safety);
for (i = 0; i < rounds; i++) {
for (int i = 0; i < rounds; i++) {
key(password, sign_ext_bug, safety);
key(salt, false, safety);
}
for (i = 0; i < 64; i++) {
for (j = 0; j < (clen >> 1); j++) {
for (int i = 0; i < 64; i++) {
for (int j = 0; j < (clen >> 1); j++) {
encipher(cdata, j << 1);
}
}
ret = new byte[clen * 4];
for (i = 0, j = 0; i < clen; i++) {
byte[] ret = new byte[clen * 4];
for (int i = 0, j = 0; i < clen; i++) {
ret[j++] = (byte) ((cdata[i] >> 24) & 0xff);
ret[j++] = (byte) ((cdata[i] >> 16) & 0xff);
ret[j++] = (byte) ((cdata[i] >> 8) & 0xff);
@@ -563,6 +575,10 @@ public class BCrypt {
return ret;
}
private static String hashpwforcheck(byte[] passwordb, String salt) {
return hashpw(passwordb, salt, true);
}
/**
* Hash a password using the OpenBSD bcrypt scheme
* @param password the password to hash
@@ -584,6 +600,10 @@ public class BCrypt {
* @return the hashed password
*/
public static String hashpw(byte passwordb[], String salt) {
return hashpw(passwordb, salt, false);
}
private static String hashpw(byte passwordb[], String salt, boolean for_check) {
BCrypt B;
String real_salt;
byte saltb[], hashed[];
@@ -633,7 +653,7 @@ public class BCrypt {
}
B = new BCrypt();
hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0);
hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0, for_check);
rs.append("$2");
if (minor >= 'a') {
@@ -740,7 +760,8 @@ public class BCrypt {
* @return true if the passwords match, false otherwise
*/
public static boolean checkpw(String plaintext, String hashed) {
return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));
byte[] passwordb = plaintext.getBytes(StandardCharsets.UTF_8);
return equalsNoEarlyReturn(hashed, hashpwforcheck(passwordb, hashed));
}
/**
@@ -751,7 +772,7 @@ public class BCrypt {
* @since 5.3
*/
public static boolean checkpw(byte[] passwordb, String hashed) {
return equalsNoEarlyReturn(hashed, hashpw(passwordb, hashed));
return equalsNoEarlyReturn(hashed, hashpwforcheck(passwordb, hashed));
}
static boolean equalsNoEarlyReturn(String a, String b) {
@@ -208,4 +208,18 @@ public class BCryptPasswordEncoderTests {
assertThatIllegalArgumentException().isThrownBy(() -> encoder.matches(null, "does-not-matter"));
}
@Test
public void upgradeWhenNoRoundsThenTrue() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
assertThat(encoder.upgradeEncoding("$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue")).isTrue();
}
@Test
public void checkWhenNoRoundsThenTrue() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
assertThat(encoder.matches("password", "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue"))
.isTrue();
assertThat(encoder.matches("wrong", "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue")).isFalse();
}
}
@@ -456,4 +456,11 @@ public class BCryptTests {
assertThat(BCrypt.equalsNoEarlyReturn("test", "pass")).isFalse();
}
@Test
public void checkpwWhenZeroRoundsThenMatches() {
String password = "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue";
assertThat(BCrypt.checkpw("password", password)).isTrue();
assertThat(BCrypt.checkpw("wrong", password)).isFalse();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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,7 +17,13 @@
package org.springframework.security.data.repository.query;
import org.springframework.data.spel.spi.EvaluationContextExtension;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -77,12 +83,21 @@ import org.springframework.security.core.context.SecurityContextHolder;
* it.
*
* @author Rob Winch
* @author Evgeniy Cheban
* @since 4.0
*/
public class SecurityEvaluationContextExtension implements EvaluationContextExtension {
private Authentication authentication;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
private String defaultRolePrefix = "ROLE_";
/**
* Creates a new instance that uses the current {@link Authentication} found on the
* {@link org.springframework.security.core.context.SecurityContextHolder}.
@@ -106,8 +121,13 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
@Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = getAuthentication();
return new SecurityExpressionRoot(authentication) {
SecurityExpressionRoot root = new SecurityExpressionRoot(authentication) {
};
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(this.roleHierarchy);
root.setPermissionEvaluator(this.permissionEvaluator);
root.setDefaultRolePrefix(this.defaultRolePrefix);
return root;
}
private Authentication getAuthentication() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 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,7 +20,10 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -69,6 +72,17 @@ public class SecurityEvaluationContextExtensionTests {
assertThat(getRoot().getAuthentication()).isSameAs(explicit);
}
@Test
public void getRootObjectWhenAdditionalFieldsNotSetThenVerifyDefaults() {
TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
this.securityExtension = new SecurityEvaluationContextExtension(explicit);
SecurityExpressionRoot root = getRoot();
assertThat(root).extracting("trustResolver").isInstanceOf(AuthenticationTrustResolverImpl.class);
assertThat(root).extracting("roleHierarchy").isInstanceOf(NullRoleHierarchy.class);
assertThat(root).extracting("permissionEvaluator").isInstanceOf(DenyAllPermissionEvaluator.class);
assertThat(root).extracting("defaultRolePrefix").isEqualTo("ROLE_");
}
private SecurityExpressionRoot getRoot() {
return this.securityExtension.getRootObject();
}
+10 -10
View File
@@ -8,15 +8,15 @@ javaPlatform {
dependencies {
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("io.projectreactor:reactor-bom:2020.0.16")
api platform("io.rsocket:rsocket-bom:1.1.1")
api platform("io.projectreactor:reactor-bom:2020.0.20")
api platform("io.rsocket:rsocket-bom:1.1.2")
api platform("org.junit:junit-bom:5.8.2")
api platform("org.springframework.data:spring-data-bom:2021.1.2")
api platform("org.springframework.data:spring-data-bom:2021.1.5")
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2")
api platform("com.fasterxml.jackson:jackson-bom:2.13.1")
api platform("com.fasterxml.jackson:jackson-bom:2.13.3")
constraints {
api "ch.qos.logback:logback-classic:1.2.10"
api "ch.qos.logback:logback-classic:1.2.11"
api "com.google.inject:guice:3.0"
api "com.nimbusds:nimbus-jose-jwt:9.14"
api "com.nimbusds:oauth2-oidc-sdk:9.19"
@@ -25,7 +25,7 @@ dependencies {
api "com.unboundid:unboundid-ldapsdk:4.0.14"
api "commons-codec:commons-codec:1.15"
api "commons-collections:commons-collections:3.2.2"
api "io.mockk:mockk:1.12.2"
api "io.mockk:mockk:1.12.4"
api "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
api "jakarta.inject:jakarta.inject-api:1.0.5"
api "jakarta.annotation:jakarta.annotation-api:1.3.5"
@@ -50,11 +50,11 @@ dependencies {
api "org.assertj:assertj-core:3.21.0"
api "org.bouncycastle:bcpkix-jdk15on:1.69"
api "org.bouncycastle:bcprov-jdk15on:1.69"
api "org.eclipse.jetty:jetty-server:9.4.45.v20220203"
api "org.eclipse.jetty:jetty-servlet:9.4.45.v20220203"
api "org.eclipse.jetty:jetty-server:9.4.46.v20220331"
api "org.eclipse.jetty:jetty-servlet:9.4.46.v20220331"
api "org.eclipse.persistence:javax.persistence:2.2.1"
api "org.hamcrest:hamcrest:2.2"
api "org.hibernate:hibernate-entitymanager:5.6.5.Final"
api "org.hibernate:hibernate-entitymanager:5.6.9.Final"
api "org.hsqldb:hsqldb:2.6.1"
api "org.jasig.cas.client:cas-client-core:3.6.4"
api "org.mockito:mockito-core:3.12.4"
@@ -71,7 +71,7 @@ dependencies {
api "org.skyscreamer:jsonassert:1.5.0"
api "org.slf4j:log4j-over-slf4j:1.7.36"
api "org.slf4j:slf4j-api:1.7.36"
api "org.springframework.ldap:spring-ldap-core:2.3.6.RELEASE"
api "org.springframework.ldap:spring-ldap-core:2.3.8.RELEASE"
api "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
name: ROOT
version: '5.6.2'
version: '5.6.6'
@@ -198,10 +198,13 @@ The password package of the spring-security-crypto module provides support for e
[source,java]
----
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
String encode(String rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
boolean matches(String rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
----
@@ -23,13 +23,14 @@ To specify where the introspection endpoint is, simply do:
[source,yaml]
----
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
----
Where `https://idp.example.com/introspect` is the introspection endpoint hosted by your authorization server and `client-id` and `client-secret` are the credentials needed to hit that endpoint.
@@ -8,7 +8,7 @@ Here is a minimal sample of what we can do:
.Java
[source,java,role="primary"]
----
@RunWith(SpringRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
public class HelloWorldMessageServiceTests {
@Autowired
@@ -42,7 +42,7 @@ public class HelloWorldMessageServiceTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = [HelloWebfluxMethodApplication::class])
class HelloWorldMessageServiceTests {
@Autowired
@@ -4,7 +4,7 @@ The basic setup looks like this:
[source,java]
----
@RunWith(SpringRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
public class HelloWebfluxMethodApplicationTests {
@Autowired
@@ -12,7 +12,7 @@ public class HelloWebfluxMethodApplicationTests {
WebTestClient rest;
@Before
@BeforeEach
public void setup() {
this.rest = WebTestClient
.bindToApplicationContext(this.context)
@@ -19,8 +19,6 @@ This also gives a good idea of the high level flow of authentication and how pie
[[servlet-authentication-securitycontextholder]]
== SecurityContextHolder
Hi {figures} there
At the heart of Spring Security's authentication model is the `SecurityContextHolder`.
It contains the <<servlet-authentication-securitycontext>>.
@@ -69,7 +69,11 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
.authorizeHttpRequests(authorize -> authorize // <1>
.mvcMatchers("/resources/**", "/signup", "/about").permitAll() // <2>
.mvcMatchers("/admin/**").hasRole("ADMIN") // <3>
.mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // <4>
.mvcMatchers("/db/**").access((authentication, request) ->
Optional.of(hasRole("ADMIN").check(authentication, request))
.filter((decision) -> !decision.isGranted())
.orElseGet(() -> hasRole("DBA").check(authentication, request));
) // <4>
.anyRequest().denyAll() // <5>
);
@@ -24,13 +24,14 @@ To specify where the introspection endpoint is, simply do:
[source,yaml]
----
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: https://idp.example.com/introspect
client-id: client
client-secret: secret
----
Where `https://idp.example.com/introspect` is the introspection endpoint hosted by your authorization server and `client-id` and `client-secret` are the credentials needed to hit that endpoint.
@@ -8,7 +8,7 @@ This filter by default responds to endpoint `+/saml2/authenticate/{registrationI
For example, if you were deployed to `https://rp.example.com` and you gave your registration an ID of `okta`, you could navigate to:
`https://rp.example.org/saml2/authenticate/ping`
`https://rp.example.org/saml2/authenticate/okta`
and the result would be a redirect that included a `SAMLRequest` parameter containing the signed, deflated, and encoded `<saml2:AuthnRequest>`.
@@ -49,7 +49,7 @@ Before we can use Spring Security Test support, we must perform some setup. An e
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class) // <1>
@ExtendWith(SpringExtension.class) // <1>
@ContextConfiguration // <2>
public class WithMockUserTests {
----
@@ -57,16 +57,15 @@ public class WithMockUserTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration
class WithMockUserTests {
----
====
This is a basic example of how to setup Spring Security Test. The highlights are:
<1> `@RunWith` instructs the spring-test module that it should create an `ApplicationContext`. This is no different than using the existing Spring Test support. For additional information, refer to the https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#integration-testing-annotations-standard[Spring Reference]
<2> `@ContextConfiguration` instructs the spring-test the configuration to use to create the `ApplicationContext`. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#testcontext-ctx-management[Spring Reference]
<1> `@ExtendWith` instructs the spring-test module that it should create an `ApplicationContext`. For additional information, refer to the {spring-framework-reference-url}testing.html#testcontext-junit-jupiter-extension[Spring reference].
<2> `@ContextConfiguration` instructs the spring-test the configuration to use to create the `ApplicationContext`. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the {spring-framework-reference-url}testing.html#spring-testing-annotation-contextconfiguration[Spring Reference]
NOTE: Spring Security hooks into Spring Test support using the `WithSecurityContextTestExecutionListener` which will ensure our tests are ran with the correct user.
It does this by populating the `SecurityContextHolder` prior to running our tests.
@@ -225,7 +224,7 @@ For example, the following would run every test with a user with the username "a
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public class WithMockUserTests {
@@ -234,7 +233,7 @@ public class WithMockUserTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WithMockUser(username="admin",roles=["USER","ADMIN"])
class WithMockUserTests {
@@ -304,7 +303,7 @@ For example, the following will run withMockUser1 and withMockUser2 using <<test
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@ExtendWith(SpringExtension.class)
@WithMockUser
public class WithUserClassLevelAuthenticationTests {
@@ -327,7 +326,7 @@ public class WithUserClassLevelAuthenticationTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@WithMockUser
class WithUserClassLevelAuthenticationTests {
@Test
@@ -15,7 +15,7 @@ NOTE: Spring Security's testing support requires spring-test-4.1.3.RELEASE or gr
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SecurityConfig.class)
@WebAppConfiguration
public class CsrfShowcaseTests {
@@ -25,7 +25,7 @@ public class CsrfShowcaseTests {
private MockMvc mvc;
@Before
@BeforeEach
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
@@ -39,7 +39,7 @@ public class CsrfShowcaseTests {
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = [SecurityConfig::class])
@WebAppConfiguration
class CsrfShowcaseTests {
@@ -49,7 +49,7 @@ class CsrfShowcaseTests {
private var mvc: MockMvc? = null
@Before
@BeforeEach
fun setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
+3 -3
View File
@@ -1,9 +1,9 @@
aspectjVersion=1.9.8
aspectjVersion=1.9.9.1
springJavaformatVersion=0.0.31
springBootVersion=2.4.2
springFrameworkVersion=5.3.16
springFrameworkVersion=5.3.21
openSamlVersion=3.4.6
version=5.6.2
version=5.6.6
kotlinVersion=1.5.32
samplesBranch=5.6.x
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
@@ -85,13 +85,13 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
return Mono.empty();
}
String idToken = idToken(authentication);
URI postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest());
String postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest());
return Mono.just(endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri));
})
.switchIfEmpty(
this.serverLogoutSuccessHandler.onLogoutSuccess(exchange, authentication).then(Mono.empty())
)
.flatMap((endpointUri) -> this.redirectStrategy.sendRedirect(exchange.getExchange(), endpointUri));
.flatMap((endpointUri) -> this.redirectStrategy.sendRedirect(exchange.getExchange(), URI.create(endpointUri)));
// @formatter:on
}
@@ -106,20 +106,20 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
return null;
}
private URI endpointUri(URI endSessionEndpoint, String idToken, URI postLogoutRedirectUri) {
private String endpointUri(URI endSessionEndpoint, String idToken, String postLogoutRedirectUri) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(endSessionEndpoint);
builder.queryParam("id_token_hint", idToken);
if (postLogoutRedirectUri != null) {
builder.queryParam("post_logout_redirect_uri", postLogoutRedirectUri);
}
return builder.encode(StandardCharsets.UTF_8).build().toUri();
return builder.encode(StandardCharsets.UTF_8).build().toUriString();
}
private String idToken(Authentication authentication) {
return ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue();
}
private URI postLogoutRedirectUri(ServerHttpRequest request) {
private String postLogoutRedirectUri(ServerHttpRequest request) {
if (this.postLogoutRedirectUri == null) {
return null;
}
@@ -131,7 +131,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
.build();
return UriComponentsBuilder.fromUriString(this.postLogoutRedirectUri)
.buildAndExpand(Collections.singletonMap("baseUrl", uriComponents.toUriString()))
.toUri();
.toUriString();
// @formatter:on
}
@@ -150,6 +150,19 @@ public class OidcClientInitiatedServerLogoutSuccessHandlerTests {
"https://endpoint?" + "id_token_hint=id-token&" + "post_logout_redirect_uri=https://rp.example.org");
}
// gh-11379
@Test
public void logoutWhenUsingPostLogoutRedirectUriWithQueryParametersThenBuildsItForRedirect() {
OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(),
AuthorityUtils.NO_AUTHORITIES, this.registration.getRegistrationId());
given(this.exchange.getPrincipal()).willReturn(Mono.just(token));
this.handler.setPostLogoutRedirectUri("https://rp.example.org/context?forwardUrl=secured%3Fparam%3Dtrue");
WebFilterExchange f = new WebFilterExchange(this.exchange, this.chain);
this.handler.onLogoutSuccess(f, token).block();
assertThat(redirectedUrl(this.exchange)).isEqualTo("https://endpoint?id_token_hint=id-token&"
+ "post_logout_redirect_uri=https://rp.example.org/context?forwardUrl%3Dsecured%253Fparam%253Dtrue");
}
@Test
public void setPostLogoutRedirectUriWhenGivenNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setPostLogoutRedirectUri((URI) null));
@@ -46,9 +46,9 @@ dependencies {
api "org.opensaml:opensaml-core"
api "org.opensaml:opensaml-saml-api"
api "org.opensaml:opensaml-saml-impl"
opensaml4MainImplementation "org.opensaml:opensaml-core:4.1.0"
opensaml4MainImplementation "org.opensaml:opensaml-saml-api:4.1.0"
opensaml4MainImplementation "org.opensaml:opensaml-saml-impl:4.1.0"
opensaml4MainImplementation "org.opensaml:opensaml-core:4.1.1"
opensaml4MainImplementation "org.opensaml:opensaml-saml-api:4.1.1"
opensaml4MainImplementation "org.opensaml:opensaml-saml-impl:4.1.1"
provided 'jakarta.servlet:jakarta.servlet-api'
@@ -19,13 +19,12 @@ package org.springframework.security.saml2.provider.service.authentication;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterOutputStream;
import org.apache.commons.codec.binary.Base64;
import org.springframework.security.saml2.Saml2Exception;
/**
@@ -33,17 +32,15 @@ import org.springframework.security.saml2.Saml2Exception;
*/
final class Saml2Utils {
private static Base64 BASE64 = new Base64(0, new byte[] { '\n' });
private Saml2Utils() {
}
static String samlEncode(byte[] b) {
return BASE64.encodeAsString(b);
return Base64.getEncoder().encodeToString(b);
}
static byte[] samlDecode(String s) {
return BASE64.decode(s);
return Base64.getMimeDecoder().decode(s);
}
static byte[] samlDeflate(String s) {
@@ -44,7 +44,7 @@ final class Saml2Utils {
}
static byte[] samlDecode(String s) {
return Base64.getDecoder().decode(s);
return Base64.getMimeDecoder().decode(s);
}
static byte[] samlDeflate(String s) {
@@ -70,7 +70,7 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
@Override
public String resolve(RelyingPartyRegistration relyingPartyRegistration) {
EntityDescriptor entityDescriptor = build(EntityDescriptor.ELEMENT_QNAME);
EntityDescriptor entityDescriptor = build(EntityDescriptor.DEFAULT_ELEMENT_NAME);
entityDescriptor.setEntityID(relyingPartyRegistration.getEntityId());
SPSSODescriptor spSsoDescriptor = buildSpSsoDescriptor(relyingPartyRegistration);
entityDescriptor.getRoleDescriptors(SPSSODescriptor.DEFAULT_ELEMENT_NAME).add(spSsoDescriptor);
@@ -21,7 +21,7 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -451,7 +451,7 @@ public final class RelyingPartyRegistration {
org.springframework.security.saml2.credentials.Saml2X509Credential credential) {
PrivateKey privateKey = credential.getPrivateKey();
X509Certificate certificate = credential.getCertificate();
Set<Saml2X509Credential.Saml2X509CredentialType> credentialTypes = new HashSet<>();
Set<Saml2X509Credential.Saml2X509CredentialType> credentialTypes = new LinkedHashSet<>();
if (credential.isSigningCredential()) {
credentialTypes.add(Saml2X509Credential.Saml2X509CredentialType.SIGNING);
}
@@ -471,7 +471,7 @@ public final class RelyingPartyRegistration {
Saml2X509Credential credential) {
PrivateKey privateKey = credential.getPrivateKey();
X509Certificate certificate = credential.getCertificate();
Set<org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType> credentialTypes = new HashSet<>();
Set<org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType> credentialTypes = new LinkedHashSet<>();
if (credential.isSigningCredential()) {
credentialTypes.add(
org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING);
@@ -696,9 +696,9 @@ public final class RelyingPartyRegistration {
private List<String> signingAlgorithms = new ArrayList<>();
private Collection<Saml2X509Credential> verificationX509Credentials = new HashSet<>();
private Collection<Saml2X509Credential> verificationX509Credentials = new LinkedHashSet<>();
private Collection<Saml2X509Credential> encryptionX509Credentials = new HashSet<>();
private Collection<Saml2X509Credential> encryptionX509Credentials = new LinkedHashSet<>();
private String singleSignOnServiceLocation;
@@ -1006,9 +1006,9 @@ public final class RelyingPartyRegistration {
private String entityId = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
private Collection<Saml2X509Credential> signingX509Credentials = new HashSet<>();
private Collection<Saml2X509Credential> signingX509Credentials = new LinkedHashSet<>();
private Collection<Saml2X509Credential> decryptionX509Credentials = new HashSet<>();
private Collection<Saml2X509Credential> decryptionX509Credentials = new LinkedHashSet<>();
private String assertionConsumerServiceLocation = "{baseUrl}/login/saml2/sso/{registrationId}";
@@ -1022,7 +1022,7 @@ public final class RelyingPartyRegistration {
private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder();
private Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials = new HashSet<>();
private Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials = new LinkedHashSet<>();
private Builder(String registrationId) {
this.registrationId = registrationId;
@@ -44,7 +44,7 @@ final class Saml2Utils {
}
static byte[] samlDecode(String s) {
return Base64.getDecoder().decode(s);
return Base64.getMimeDecoder().decode(s);
}
static byte[] samlDeflate(String s) {
@@ -19,28 +19,25 @@ package org.springframework.security.saml2.core;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterOutputStream;
import org.apache.commons.codec.binary.Base64;
import org.springframework.security.saml2.Saml2Exception;
public final class Saml2Utils {
private static Base64 BASE64 = new Base64(0, new byte[] { '\n' });
private Saml2Utils() {
}
public static String samlEncode(byte[] b) {
return BASE64.encodeAsString(b);
return Base64.getEncoder().encodeToString(b);
}
public static byte[] samlDecode(String s) {
return BASE64.decode(s);
return Base64.getMimeDecoder().decode(s);
}
public static byte[] samlDeflate(String s) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,6 +51,10 @@ public final class TestSaml2X509Credentials {
return new Saml2X509Credential(idpCertificate(), Saml2X509CredentialType.VERIFICATION);
}
public static Saml2X509Credential relyingPartyEncryptingCredential() {
return new Saml2X509Credential(idpCertificate(), Saml2X509CredentialType.ENCRYPTION);
}
public static Saml2X509Credential relyingPartySigningCredential() {
return new Saml2X509Credential(spPrivateKey(), spCertificate(), Saml2X509CredentialType.SIGNING);
}
@@ -59,6 +63,16 @@ public final class TestSaml2X509Credentials {
return new Saml2X509Credential(spPrivateKey(), spCertificate(), Saml2X509CredentialType.DECRYPTION);
}
public static Saml2X509Credential altPublicCredential() {
return new Saml2X509Credential(altCertificate(), Saml2X509CredentialType.VERIFICATION,
Saml2X509CredentialType.ENCRYPTION);
}
public static Saml2X509Credential altPrivateCredential() {
return new Saml2X509Credential(altPrivateKey(), altCertificate(), Saml2X509CredentialType.SIGNING,
Saml2X509CredentialType.DECRYPTION);
}
private static X509Certificate certificate(String cert) {
ByteArrayInputStream certBytes = new ByteArrayInputStream(cert.getBytes());
try {
@@ -170,4 +184,40 @@ public final class TestSaml2X509Credentials {
+ "-----END PRIVATE KEY-----");
}
private static X509Certificate altCertificate() {
return certificate(
"-----BEGIN CERTIFICATE-----\n" + "MIICkDCCAfkCFEstVfmWSFQp/j88GaMUwqVK72adMA0GCSqGSIb3DQEBCwUAMIGG\n"
+ "MQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjESMBAGA1UEBwwJVmFu\n"
+ "Y291dmVyMR0wGwYDVQQKDBRTcHJpbmcgU2VjdXJpdHkgU0FNTDEMMAoGA1UECwwD\n"
+ "YWx0MSEwHwYDVQQDDBhhbHQuc3ByaW5nLnNlY3VyaXR5LnNhbWwwHhcNMjIwMjEw\n"
+ "MTY1ODA4WhcNMzIwMjEwMTY1ODA4WjCBhjELMAkGA1UEBhMCVVMxEzARBgNVBAgM\n"
+ "Cldhc2hpbmd0b24xEjAQBgNVBAcMCVZhbmNvdXZlcjEdMBsGA1UECgwUU3ByaW5n\n"
+ "IFNlY3VyaXR5IFNBTUwxDDAKBgNVBAsMA2FsdDEhMB8GA1UEAwwYYWx0LnNwcmlu\n"
+ "Zy5zZWN1cml0eS5zYW1sMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9ZGWj\n"
+ "TPDsymQCJL044py4xLsBI/S9RvzNeR9oD/tHyoxCE+YZzjf0PyBtwqKzkKWqCPf4\n"
+ "XGUYHfEpkM5kJYwCW8TsOx5fnwLIQweiPqjYrBr/O0IjHMqYG9HlR/ros7iBt4ab\n"
+ "EGUu/B9yYg1YRYPxKQ6TNP3AD+9tBT8TsFFyjwIDAQABMA0GCSqGSIb3DQEBCwUA\n"
+ "A4GBAKJf2VHLjkCHRxlbWn63jGiquq3ENYgd1JS0DZ3ggFmuc6zQiqxzRGtArIDZ\n"
+ "0jH5nrG0jcvO0fqDqBQh0iT8thfUnkViAQvACZ9a+0x0NzUicJ+Ra51c8Z2enqbg\n"
+ "pXy+ga67HcAXrDekm1MCGCgiEb/Cgl41lsideqhC8Efl7PRN\n" + "-----END CERTIFICATE-----");
}
private static PrivateKey altPrivateKey() {
return privateKey(
"-----BEGIN PRIVATE KEY-----\n" + "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAL1kZaNM8OzKZAIk\n"
+ "vTjinLjEuwEj9L1G/M15H2gP+0fKjEIT5hnON/Q/IG3CorOQpaoI9/hcZRgd8SmQ\n"
+ "zmQljAJbxOw7Hl+fAshDB6I+qNisGv87QiMcypgb0eVH+uizuIG3hpsQZS78H3Ji\n"
+ "DVhFg/EpDpM0/cAP720FPxOwUXKPAgMBAAECgYEApYKslAZ0cer5dSoYNzNLFOnQ\n"
+ "J1H92r/Dw+k6+h0lUvr+keyD5T9jhM76DxHOUDBzpmIKGoDcVDQugk2rILfzXsQA\n"
+ "JtwvDRJk32Z02Vt0jb7t/WUOOQhjKCjQuv9/tOx90GCl0VxYG69UOjaMRWrlg/i9\n"
+ "6/zcTRIahIn5XxF0psECQQD7ivJCpDbOLJGsc8gNJR4cvjZ1q0mHIOrbKqJC0y1n\n"
+ "5DrzGEflPeyCUwnOKNp9HJQP8gmZzXfj0JM9KsjpiUChAkEAwL+FmhDoTiqStIrH\n"
+ "h9Kdnsev//imMmRHxjwDhntYvqavUsISRmY3imd8inoYq5dzWQMzBtoTyMRmqeLT\n"
+ "DHV1LwJAW4xaV37Eo4z9B7Kr4Hzd1MA1ueW5QQDt+Q4vN/r7z4/1FHyFzh0Xcucd\n"
+ "7nZX7qj0CkmgzOVG+Rb0P5LOxJA7gQJBAK1KQ2qNct375qPM9bEGSVGchH6k5X7+\n"
+ "q4ztHdpFgTb/EzdbZiTG935GpjC1rwJuinTnrHOnkwv4j7iDRm24GF8CQQDqPvrQ\n"
+ "GcItR6UUy0q/B8UxLzlE6t+HiznfiJKfyGgCHU56Y4/ZhzSQz2MZHz9SK4DsUL9s\n" + "bOYrWq8VY2fyjV1t\n"
+ "-----END PRIVATE KEY-----");
}
}
@@ -129,6 +129,23 @@ public class OpenSamlLogoutRequestValidatorTests {
assertThat(result.getErrors().iterator().next().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_DESTINATION);
}
// gh-10923
@Test
public void handleWhenLogoutResponseHasLineBreaksThenHandles() {
RelyingPartyRegistration registration = registration().build();
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
sign(logoutRequest, registration);
String encoded = new StringBuffer(
Saml2Utils.samlEncode(serialize(logoutRequest).getBytes(StandardCharsets.UTF_8))).insert(10, "\r\n")
.toString();
Saml2LogoutRequest request = Saml2LogoutRequest.withRelyingPartyRegistration(registration).samlRequest(encoded)
.build();
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
registration, authentication(registration));
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
private RelyingPartyRegistration.Builder registration() {
return signing(verifying(TestRelyingPartyRegistrations.noCredentials()))
.assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST));
@@ -119,6 +119,24 @@ public class OpenSamlLogoutResponseValidatorTests {
assertThat(result.getErrors().iterator().next().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE);
}
// gh-10923
@Test
public void handleWhenLogoutResponseHasLineBreaksThenHandles() {
RelyingPartyRegistration registration = signing(verifying(registration())).build();
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration).id("id")
.build();
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
sign(logoutResponse, registration);
String encoded = new StringBuilder(
Saml2Utils.samlEncode(serialize(logoutResponse).getBytes(StandardCharsets.UTF_8))).insert(10, "\r\n")
.toString();
Saml2LogoutResponse response = Saml2LogoutResponse.withRelyingPartyRegistration(registration)
.samlResponse(encoded).build();
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
}
private RelyingPartyRegistration.Builder registration() {
return signing(verifying(TestRelyingPartyRegistrations.noCredentials()))
.assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST));
@@ -36,7 +36,7 @@ public class OpenSamlMetadataResolverTests {
.assertionConsumerServiceBinding(Saml2MessageBinding.REDIRECT).build();
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
assertThat(metadata).contains("<EntityDescriptor").contains("entityID=\"rp-entity-id\"")
assertThat(metadata).contains("<md:EntityDescriptor").contains("entityID=\"rp-entity-id\"")
.contains("WantAssertionsSigned=\"true\"").contains("<md:KeyDescriptor use=\"signing\">")
.contains("<md:KeyDescriptor use=\"encryption\">")
.contains("<ds:X509Certificate>MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBh")
@@ -53,7 +53,7 @@ public class OpenSamlMetadataResolverTests {
.build();
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
assertThat(metadata).contains("<EntityDescriptor").contains("entityID=\"rp-entity-id\"")
assertThat(metadata).contains("<md:EntityDescriptor").contains("entityID=\"rp-entity-id\"")
.contains("WantAssertionsSigned=\"true\"").doesNotContain("<md:KeyDescriptor use=\"signing\">")
.doesNotContain("<md:KeyDescriptor use=\"encryption\">")
.contains("Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"")
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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,7 +18,8 @@ package org.springframework.security.saml2.provider.service.registration;
import org.junit.jupiter.api.Test;
import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
import static org.assertj.core.api.Assertions.assertThat;
@@ -81,9 +82,68 @@ public class RelyingPartyRegistrationTests {
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("id")
.entityId("entity-id").assertionConsumerServiceLocation("location")
.assertingPartyDetails((assertingParty) -> assertingParty.entityId("entity-id")
.singleSignOnServiceLocation("location"))
.credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())).build();
.singleSignOnServiceLocation("location").verificationX509Credentials(
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
assertThat(relyingPartyRegistration.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.POST);
}
@Test
public void buildPreservesCredentialsOrder() {
Saml2X509Credential altRpCredential = TestSaml2X509Credentials.altPrivateCredential();
Saml2X509Credential altApCredential = TestSaml2X509Credentials.altPublicCredential();
Saml2X509Credential verifyingCredential = TestSaml2X509Credentials.relyingPartyVerifyingCredential();
Saml2X509Credential encryptingCredential = TestSaml2X509Credentials.relyingPartyEncryptingCredential();
Saml2X509Credential signingCredential = TestSaml2X509Credentials.relyingPartySigningCredential();
Saml2X509Credential decryptionCredential = TestSaml2X509Credentials.relyingPartyDecryptingCredential();
// Test with the alt credentials first
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
.assertingPartyDetails((assertingParty) -> assertingParty.verificationX509Credentials((c) -> {
c.add(altApCredential);
c.add(verifyingCredential);
}).encryptionX509Credentials((c) -> {
c.add(altApCredential);
c.add(encryptingCredential);
})).signingX509Credentials((c) -> {
c.add(altRpCredential);
c.add(signingCredential);
}).decryptionX509Credentials((c) -> {
c.add(altRpCredential);
c.add(decryptionCredential);
}).build();
assertThat(relyingPartyRegistration.getSigningX509Credentials()).containsExactly(altRpCredential,
signingCredential);
assertThat(relyingPartyRegistration.getDecryptionX509Credentials()).containsExactly(altRpCredential,
decryptionCredential);
assertThat(relyingPartyRegistration.getAssertingPartyDetails().getVerificationX509Credentials())
.containsExactly(altApCredential, verifyingCredential);
assertThat(relyingPartyRegistration.getAssertingPartyDetails().getEncryptionX509Credentials())
.containsExactly(altApCredential, encryptingCredential);
// Test with the alt credentials last
relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
.assertingPartyDetails((assertingParty) -> assertingParty.verificationX509Credentials((c) -> {
c.add(verifyingCredential);
c.add(altApCredential);
}).encryptionX509Credentials((c) -> {
c.add(encryptingCredential);
c.add(altApCredential);
})).signingX509Credentials((c) -> {
c.add(signingCredential);
c.add(altRpCredential);
}).decryptionX509Credentials((c) -> {
c.add(decryptionCredential);
c.add(altRpCredential);
}).build();
assertThat(relyingPartyRegistration.getSigningX509Credentials()).containsExactly(signingCredential,
altRpCredential);
assertThat(relyingPartyRegistration.getDecryptionX509Credentials()).containsExactly(decryptionCredential,
altRpCredential);
assertThat(relyingPartyRegistration.getAssertingPartyDetails().getVerificationX509Credentials())
.containsExactly(verifyingCredential, altApCredential);
assertThat(relyingPartyRegistration.getAssertingPartyDetails().getEncryptionX509Credentials())
.containsExactly(encryptingCredential, altApCredential);
}
}
@@ -98,6 +98,29 @@ public class OpenSamlLogoutResponseResolverTests {
assertThat(logoutResponse.getStatus().getStatusCode().getValue()).isEqualTo(StatusCode.SUCCESS);
}
// gh-10923
@Test
public void resolvePostWithLineBreaksWhenAuthenticatedThenSuccess() {
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full()
.assertingPartyDetails((party) -> party.singleLogoutServiceBinding(Saml2MessageBinding.POST)).build();
MockHttpServletRequest request = new MockHttpServletRequest();
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
String encoded = new StringBuffer(
Saml2Utils.samlEncode(OpenSamlSigningUtils.serialize(logoutRequest).getBytes())).insert(10, "\r\n")
.toString();
request.setParameter(Saml2ParameterNames.SAML_REQUEST, encoded);
request.setParameter(Saml2ParameterNames.RELAY_STATE, "abcd");
Authentication authentication = authentication(registration);
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
Saml2LogoutResponse saml2LogoutResponse = this.logoutResponseResolver.resolve(request, authentication);
assertThat(saml2LogoutResponse.getParameter(Saml2ParameterNames.SIG_ALG)).isNull();
assertThat(saml2LogoutResponse.getParameter(Saml2ParameterNames.SIGNATURE)).isNull();
assertThat(saml2LogoutResponse.getParameter(Saml2ParameterNames.RELAY_STATE)).isSameAs("abcd");
Saml2MessageBinding binding = registration.getAssertingPartyDetails().getSingleLogoutServiceBinding();
LogoutResponse logoutResponse = getLogoutResponse(saml2LogoutResponse.getSamlResponse(), binding);
assertThat(logoutResponse.getStatus().getStatusCode().getValue()).isEqualTo(StatusCode.SUCCESS);
}
private Saml2Authentication authentication(RelyingPartyRegistration registration) {
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", new HashMap<>());
principal.setRelyingPartyRegistrationId(registration.getRegistrationId());
@@ -14,3 +14,6 @@ changelog:
emoji: ":hammer:"
labels: ["type: dependency-upgrade"]
sort: "title"
issues:
exclude:
labels: ["status: duplicate"]
@@ -48,7 +48,7 @@ public final class DefaultSecurityFilterChain implements SecurityFilterChain {
}
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
if (!filters.isEmpty()) {
if (filters.isEmpty()) {
logger.info(LogMessage.format("Will not secure %s", requestMatcher));
}
else {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.access;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authorization.AuthorizationDecision;
@@ -23,6 +24,7 @@ import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
/**
* An implementation of {@link WebInvocationPrivilegeEvaluator} which delegates the checks
@@ -31,10 +33,13 @@ import org.springframework.util.Assert;
* @author Marcus Da Coregio
* @since 5.5.5
*/
public final class AuthorizationManagerWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator {
public final class AuthorizationManagerWebInvocationPrivilegeEvaluator
implements WebInvocationPrivilegeEvaluator, ServletContextAware {
private final AuthorizationManager<HttpServletRequest> authorizationManager;
private ServletContext servletContext;
public AuthorizationManagerWebInvocationPrivilegeEvaluator(
AuthorizationManager<HttpServletRequest> authorizationManager) {
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
@@ -48,10 +53,15 @@ public final class AuthorizationManagerWebInvocationPrivilegeEvaluator implement
@Override
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method);
FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method, this.servletContext);
AuthorizationDecision decision = this.authorizationManager.check(() -> authentication,
filterInvocation.getHttpRequest());
return decision != null && decision.isGranted();
return decision == null || decision.isGranted();
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
@@ -107,6 +107,15 @@ public class StrictHttpFirewall implements HttpFirewall {
private static final List<String> FORBIDDEN_NULL = Collections.unmodifiableList(Arrays.asList("\0", "%00"));
private static final List<String> FORBIDDEN_LF = Collections.unmodifiableList(Arrays.asList("\n", "%0a", "%0A"));
private static final List<String> FORBIDDEN_CR = Collections.unmodifiableList(Arrays.asList("\r", "%0d", "%0D"));
private static final List<String> FORBIDDEN_LINE_SEPARATOR = Collections.unmodifiableList(Arrays.asList("\u2028"));
private static final List<String> FORBIDDEN_PARAGRAPH_SEPARATOR = Collections
.unmodifiableList(Arrays.asList("\u2029"));
private Set<String> encodedUrlBlocklist = new HashSet<>();
private Set<String> decodedUrlBlocklist = new HashSet<>();
@@ -135,10 +144,14 @@ public class StrictHttpFirewall implements HttpFirewall {
urlBlocklistsAddAll(FORBIDDEN_DOUBLE_FORWARDSLASH);
urlBlocklistsAddAll(FORBIDDEN_BACKSLASH);
urlBlocklistsAddAll(FORBIDDEN_NULL);
urlBlocklistsAddAll(FORBIDDEN_LF);
urlBlocklistsAddAll(FORBIDDEN_CR);
this.encodedUrlBlocklist.add(ENCODED_PERCENT);
this.encodedUrlBlocklist.addAll(FORBIDDEN_ENCODED_PERIOD);
this.decodedUrlBlocklist.add(PERCENT);
this.decodedUrlBlocklist.addAll(FORBIDDEN_LINE_SEPARATOR);
this.decodedUrlBlocklist.addAll(FORBIDDEN_PARAGRAPH_SEPARATOR);
}
/**
@@ -345,6 +358,69 @@ public class StrictHttpFirewall implements HttpFirewall {
}
}
/**
* Determines if a URL encoded Carriage Return is allowed in the path or not. The
* default is not to allow this behavior because it is a frequent source of security
* exploits.
* @param allowUrlEncodedCarriageReturn if URL encoded Carriage Return is allowed in
* the URL or not. Default is false.
*/
public void setAllowUrlEncodedCarriageReturn(boolean allowUrlEncodedCarriageReturn) {
if (allowUrlEncodedCarriageReturn) {
urlBlocklistsRemoveAll(FORBIDDEN_CR);
}
else {
urlBlocklistsAddAll(FORBIDDEN_CR);
}
}
/**
* Determines if a URL encoded Line Feed is allowed in the path or not. The default is
* not to allow this behavior because it is a frequent source of security exploits.
* @param allowUrlEncodedLineFeed if URL encoded Line Feed is allowed in the URL or
* not. Default is false.
*/
public void setAllowUrlEncodedLineFeed(boolean allowUrlEncodedLineFeed) {
if (allowUrlEncodedLineFeed) {
urlBlocklistsRemoveAll(FORBIDDEN_LF);
}
else {
urlBlocklistsAddAll(FORBIDDEN_LF);
}
}
/**
* Determines if a URL encoded paragraph separator is allowed in the path or not. The
* default is not to allow this behavior because it is a frequent source of security
* exploits.
* @param allowUrlEncodedParagraphSeparator if URL encoded paragraph separator is
* allowed in the URL or not. Default is false.
*/
public void setAllowUrlEncodedParagraphSeparator(boolean allowUrlEncodedParagraphSeparator) {
if (allowUrlEncodedParagraphSeparator) {
this.decodedUrlBlocklist.removeAll(FORBIDDEN_PARAGRAPH_SEPARATOR);
}
else {
this.decodedUrlBlocklist.addAll(FORBIDDEN_PARAGRAPH_SEPARATOR);
}
}
/**
* Determines if a URL encoded line separator is allowed in the path or not. The
* default is not to allow this behavior because it is a frequent source of security
* exploits.
* @param allowUrlEncodedLineSeparator if URL encoded line separator is allowed in the
* URL or not. Default is false.
*/
public void setAllowUrlEncodedLineSeparator(boolean allowUrlEncodedLineSeparator) {
if (allowUrlEncodedLineSeparator) {
this.decodedUrlBlocklist.removeAll(FORBIDDEN_LINE_SEPARATOR);
}
else {
this.decodedUrlBlocklist.addAll(FORBIDDEN_LINE_SEPARATOR);
}
}
/**
* <p>
* Determines which header names should be allowed. The default is to reject header
@@ -431,14 +507,17 @@ public class StrictHttpFirewall implements HttpFirewall {
if (!isNormalized(request)) {
throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
}
String requestUri = request.getRequestURI();
if (!containsOnlyPrintableAsciiCharacters(requestUri)) {
throw new RequestRejectedException(
"The requestURI was rejected because it can only contain printable ASCII characters.");
}
rejectNonPrintableAsciiCharactersInFieldName(request.getRequestURI(), "requestURI");
return new StrictFirewalledRequest(request);
}
private void rejectNonPrintableAsciiCharactersInFieldName(String toCheck, String propertyName) {
if (!containsOnlyPrintableAsciiCharacters(toCheck)) {
throw new RequestRejectedException(String.format(
"The %s was rejected because it can only contain printable ASCII characters.", propertyName));
}
}
private void rejectForbiddenHttpMethod(HttpServletRequest request) {
if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {
return;
@@ -526,6 +605,9 @@ public class StrictHttpFirewall implements HttpFirewall {
}
private static boolean containsOnlyPrintableAsciiCharacters(String uri) {
if (uri == null) {
return true;
}
int length = uri.length();
for (int i = 0; i < length; i++) {
char ch = uri.charAt(i);
@@ -151,7 +151,7 @@ public class CsrfWebFilter implements WebFilter {
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
MediaType contentType = headers.getContentType();
if (!contentType.includes(MediaType.MULTIPART_FORM_DATA)) {
if (!MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
return Mono.empty();
}
return exchange.getMultipartData().map((d) -> d.getFirst(expected.getParameterName())).cast(FormFieldPart.class)
@@ -43,7 +43,9 @@ import org.springframework.util.StringUtils;
*/
public final class RegexRequestMatcher implements RequestMatcher {
private static final int DEFAULT = 0;
private static final int DEFAULT = Pattern.DOTALL;
private static final int CASE_INSENSITIVE = DEFAULT | Pattern.CASE_INSENSITIVE;
private static final Log logger = LogFactory.getLog(RegexRequestMatcher.class);
@@ -68,7 +70,7 @@ public final class RegexRequestMatcher implements RequestMatcher {
* {@link Pattern#CASE_INSENSITIVE} flag set.
*/
public RegexRequestMatcher(String pattern, String httpMethod, boolean caseInsensitive) {
this.pattern = Pattern.compile(pattern, caseInsensitive ? Pattern.CASE_INSENSITIVE : DEFAULT);
this.pattern = Pattern.compile(pattern, caseInsensitive ? CASE_INSENSITIVE : DEFAULT);
this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod.valueOf(httpMethod) : null;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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,14 +16,17 @@
package org.springframework.security.web.access;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
@@ -65,4 +68,21 @@ class AuthorizationManagerWebInvocationPrivilegeEvaluatorTests {
assertThat(allowed).isFalse();
}
@Test
void isAllowedWhenAuthorizationManagerAbstainsThenAllowedTrue() {
given(this.authorizationManager.check(any(), any())).willReturn(null);
boolean allowed = this.privilegeEvaluator.isAllowed("/test", TestAuthentication.authenticatedUser());
assertThat(allowed).isTrue();
}
@Test
void isAllowedWhenServletContextExistsThenFilterInvocationHasServletContext() {
ServletContext servletContext = new MockServletContext();
this.privilegeEvaluator.setServletContext(servletContext);
this.privilegeEvaluator.isAllowed("/test", TestAuthentication.authenticatedUser());
ArgumentCaptor<HttpServletRequest> captor = ArgumentCaptor.forClass(HttpServletRequest.class);
verify(this.authorizationManager).check(any(), captor.capture());
assertThat(captor.getValue().getServletContext()).isSameAs(servletContext);
}
}
@@ -343,6 +343,12 @@ public class StrictHttpFirewallTests {
this.firewall.getFirewalledRequest(this.request);
}
@Test
public void getFirewalledRequestWhenJapaneseCharacterThenNoException() {
this.request.setServletPath("/\u3042");
this.firewall.getFirewalledRequest(this.request);
}
@Test
public void getFirewalledRequestWhenExceedsUpperboundAsciiThenException() {
this.request.setRequestURI("/\u007f");
@@ -364,6 +370,152 @@ public class StrictHttpFirewallTests {
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenContainsLowercaseEncodedLineFeedThenException() {
this.request.setRequestURI("/something%0a/");
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenContainsUppercaseEncodedLineFeedThenException() {
this.request.setRequestURI("/something%0A/");
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenContainsLineFeedThenException() {
this.request.setRequestURI("/something\n/");
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenServletPathContainsLineFeedThenException() {
this.request.setServletPath("/something\n/");
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenContainsLowercaseEncodedCarriageReturnThenException() {
this.request.setRequestURI("/something%0d/");
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenContainsUppercaseEncodedCarriageReturnThenException() {
this.request.setRequestURI("/something%0D/");
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenContainsCarriageReturnThenException() {
this.request.setRequestURI("/something\r/");
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenServletPathContainsCarriageReturnThenException() {
this.request.setServletPath("/something\r/");
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenServletPathContainsLineSeparatorThenException() {
this.request.setServletPath("/something\u2028/");
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenServletPathContainsParagraphSeparatorThenException() {
this.request.setServletPath("/something\u2029/");
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenContainsLowercaseEncodedLineFeedAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedLineFeed(true);
this.request.setRequestURI("/something%0a/");
this.firewall.getFirewalledRequest(this.request);
}
@Test
public void getFirewalledRequestWhenContainsUppercaseEncodedLineFeedAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedLineFeed(true);
this.request.setRequestURI("/something%0A/");
this.firewall.getFirewalledRequest(this.request);
}
@Test
public void getFirewalledRequestWhenContainsLineFeedAndAllowedThenException() {
this.firewall.setAllowUrlEncodedLineFeed(true);
this.request.setRequestURI("/something\n/");
// Expected an error because the line feed is decoded in an encoded part of the
// URL
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenServletPathContainsLineFeedAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedLineFeed(true);
this.request.setServletPath("/something\n/");
this.firewall.getFirewalledRequest(this.request);
}
@Test
public void getFirewalledRequestWhenContainsLowercaseEncodedCarriageReturnAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedCarriageReturn(true);
this.request.setRequestURI("/something%0d/");
this.firewall.getFirewalledRequest(this.request);
}
@Test
public void getFirewalledRequestWhenContainsUppercaseEncodedCarriageReturnAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedCarriageReturn(true);
this.request.setRequestURI("/something%0D/");
this.firewall.getFirewalledRequest(this.request);
}
@Test
public void getFirewalledRequestWhenContainsCarriageReturnAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedCarriageReturn(true);
this.request.setRequestURI("/something\r/");
// Expected an error because the carriage return is decoded in an encoded part of
// the URL
assertThatExceptionOfType(RequestRejectedException.class)
.isThrownBy(() -> this.firewall.getFirewalledRequest(this.request));
}
@Test
public void getFirewalledRequestWhenServletPathContainsCarriageReturnAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedCarriageReturn(true);
this.request.setServletPath("/something\r/");
this.firewall.getFirewalledRequest(this.request);
}
@Test
public void getFirewalledRequestWhenServletPathContainsLineSeparatorAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedLineSeparator(true);
this.request.setServletPath("/something\u2028/");
this.firewall.getFirewalledRequest(this.request);
}
@Test
public void getFirewalledRequestWhenServletPathContainsParagraphSeparatorAndAllowedThenNoException() {
this.firewall.setAllowUrlEncodedParagraphSeparator(true);
this.request.setServletPath("/something\u2029/");
this.firewall.getFirewalledRequest(this.request);
}
/**
* On WebSphere 8.5 a URL like /context-root/a/b;%2f1/c can bypass a rule on /a/b/c
* because the pathInfo is /a/b;/1/c which ends up being /a/b/1/c while Spring MVC
@@ -189,6 +189,17 @@ public class CsrfWebFilterTests {
.expectStatus().is2xxSuccessful();
}
@Test
public void filterWhenPostAndMultipartFormDataEnabledAndNoBodyProvided() {
this.csrfFilter.setCsrfTokenRepository(this.repository);
this.csrfFilter.setTokenFromMultipartDataEnabled(true);
given(this.repository.loadToken(any())).willReturn(Mono.just(this.token));
given(this.repository.generateToken(any())).willReturn(Mono.just(this.token));
WebTestClient client = WebTestClient.bindToController(new OkController()).webFilter(this.csrfFilter).build();
client.post().uri("/").header(this.token.getHeaderName(), this.token.getToken()).exchange().expectStatus()
.is2xxSuccessful();
}
@Test
public void filterWhenFormDataAndEnabledThenGranted() {
this.csrfFilter.setCsrfTokenRepository(this.repository);
@@ -101,6 +101,22 @@ public class RegexRequestMatcherTests {
assertThat(matcher.matches(request)).isFalse();
}
@Test
public void matchesWithCarriageReturn() {
RegexRequestMatcher matcher = new RegexRequestMatcher(".*", null);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/blah%0a");
request.setServletPath("/blah\n");
assertThat(matcher.matches(request)).isTrue();
}
@Test
public void matchesWithLineFeed() {
RegexRequestMatcher matcher = new RegexRequestMatcher(".*", null);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/blah%0d");
request.setServletPath("/blah\r");
assertThat(matcher.matches(request)).isTrue();
}
@Test
public void toStringThenFormatted() {
RegexRequestMatcher matcher = new RegexRequestMatcher("/blah", "GET");