Showing with 500 additions and 45 deletions.
  1. +5 −1 .github/workflows/continuous-integration-workflow.yml
  2. +4 −0 build.gradle
  3. +1 −1 buildSrc/build.gradle
  4. +17 −2 buildSrc/src/main/groovy/io/spring/gradle/convention/IncludeCheckRemotePlugin.groovy
  5. +40 −3 buildSrc/src/test/java/io/spring/gradle/convention/IncludeCheckRemotePluginTest.java
  6. +14 −8 config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
  7. +4 −1 config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java
  8. +80 −1 ...rg/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurerTests.java
  9. +132 −1 .../springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java
  10. +77 −1 ...g/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java
  11. +34 −13 crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java
  12. +14 −0 crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoderTests.java
  13. +7 −0 crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptTests.java
  14. +3 −3 dependencies/spring-security-dependencies.gradle
  15. +1 −1 docs/antora.yml
  16. +2 −2 gradle.properties
  17. +3 −0 scripts/release/release-notes-sections.yml
  18. +14 −5 web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java
  19. +4 −2 web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java
  20. +28 −0 web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java
  21. +16 −0 web/src/test/java/org/springframework/security/web/util/matcher/RegexRequestMatcherTests.java
@@ -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 ]
@@ -154,6 +154,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
}
@@ -80,7 +80,7 @@ dependencies {
implementation localGroovy()

implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
implementation 'io.projectreactor:reactor-core:3.4.17'
implementation 'io.projectreactor:reactor-core:3.4.18'
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.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 @@ void applyWhenExtensionPropertiesTasksThenCreateCheckRemoteWithProvidedTasks() {
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();
@@ -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 @@ private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSec
*/
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 class RequestMatcherConfigurer extends AbstractRequestMatcherRegistry<Req
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 @@ private RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> getRequestMat
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.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 @@
*
* @author Rob Winch
* @author Eleftheria Stein
* @author Onur Kagan Ozcan
*/
@ExtendWith(SpringTestContextExtension.class)
public class ChannelSecurityConfigurerTests {
@@ -93,6 +99,24 @@ public void requestWhenRequiresChannelConfiguredInLambdaThenRedirectsToHttps() t
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 @@ protected void configure(HttpSecurity http) throws Exception {

}

@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();
}

}

}