Compare commits

...

71 Commits

Author SHA1 Message Date
Rob Winch
be64c67af5
Enable Null checking in spring-security-web via JSpecify
Closes gh-16882
2025-08-29 16:17:49 -05:00
Rob Winch
a58f3282d9
Fix config/src/test/kotlin nullability for web
Issue gh-17535
2025-08-29 15:46:08 -05:00
Rob Winch
c2ba662b91
Enable Null checking in spring-security-web via JSpecify
Closes gh-17535
2025-08-29 15:06:48 -05:00
Rob Winch
49f308adb0
Use Supplier<? extends @Nullable Authentication>
Previously Supplier<@Nullable Authentication> was used. This prevented
Supplier<Authentication> from being used. The code now uses
Supplier<? extends @Nullable Authentication> which allows for both
Supplier<@Nullable Authentication> and Supplier<Authentication>.

Closes gh-17814
2025-08-29 09:46:58 -05:00
Josh Cummings
4cbe8de7ea Polish RSocket Anonymous Support
Changed the DSL method name to anonymous to align with jwt.
Since basicAuthenication is deprecated, we don't need to
align with its naming convention.

Also added a since attribute to the method.

Issue gh-17132
2025-08-26 17:33:40 -06:00
Andrey Litvitski
559b73b39f Add Disabling Anonymous Authentication in RSocketSecurity
Closes: gh-17132

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>

1

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>

1

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2025-08-26 17:33:40 -06:00
Andrey Litvitski
3278f3a410 Add discoverJwsAlgorithms() in NimbusJwtDecoder
Closes: gh-17785
Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2025-08-26 17:07:47 -06:00
Josh Cummings
36f1de945f
Add OneTimeTokenAuthentication
Closes gh-17799
2025-08-22 15:46:54 -06:00
Josh Cummings
6663eea65f
Polish OTT Tests
Improve tests so that they do not rely on OneTimeTokenAuthenticationToken
as the concrete type.

Issue gh-17799
2025-08-22 15:46:53 -06:00
Josh Cummings
89b2f9cf54
Improve Test Runnability in IDE
In some configurations, Configuration classes with static elements
may cause a test to hang. This commit changes JeeConfigurerTests
test configuration classes to use mock beans instead of referencing
them as static fields.
2025-08-22 15:46:53 -06:00
Josh Cummings
0e39685b9c Merge branch '6.5.x' 2025-08-22 12:40:41 -06:00
Josh Cummings
9d64880ea9 Merge branch '6.4.x' into 6.5.x 2025-08-22 12:40:12 -06:00
Josh Cummings
8b2a453301 Advise Favoring PostAuthorize on Reads
Closes gh-17797
2025-08-22 12:39:51 -06:00
Josh Cummings
d1962201b5 Merge branch '6.5.x' 2025-08-22 11:07:59 -06:00
Josh Cummings
857ca9c412 Merge remote-tracking branch 'origin/6.4.x' into 6.5.x 2025-08-22 11:07:37 -06:00
Nikita Konev
894105aab5 Fix traceId discrepancy in case error in servlet web
Signed-off-by: Nikita Konev <nikit.cpp@yandex.ru>
2025-08-22 11:06:37 -06:00
Rob Winch
f7f41ba6c4
Add missing @NullMarked to spring-data package-info
Issue gh-17789
2025-08-22 12:03:16 -05:00
Rob Winch
f496ded4e5
AuthorizationManager allows null Authentication
It is possible to have a null Authentication and so the
AuthorizationManager APIs should allow for passing it in.

Closes gh-17795
2025-08-22 12:03:16 -05:00
Josh Cummings
583e668c6b Remove opensaml5Test Task
Issue gh-17707
2025-08-22 09:19:20 -06:00
Rob Winch
d6a0e3bf78
Fix Nullability Imports
Issue gh-17789
2025-08-22 09:00:15 -05:00
Rob Winch
29bb4919ca
Add Nullability to spring-security-data
Closes gh-17789
2025-08-21 13:42:27 -05:00
Rob Winch
d9210c6596
Fix Nullability 2025-08-21 13:41:02 -05:00
Rob Winch
b8b1a92ad4
Revert "Apply Nullability to spring-security-data"
This reverts commit bbcdb236984960416489b4f9d923f83d3a4cba39.
2025-08-21 13:35:39 -05:00
Rob Winch
bbcdb23698
Apply Nullability to spring-security-data 2025-08-21 13:27:47 -05:00
Rob Winch
9bbf837c7c
Merge branch '6.5.x' 2025-08-21 12:44:42 -05:00
Rob Winch
8a1e2a22f9
Merge branch 'gh-16226-servlet-test-method' into 6.5.x 2025-08-21 12:44:27 -05:00
Rob Winch
0404996f87 import Assertions.assertThat
This adds a static import for assertThat in the Kotlin docs code
2025-08-21 12:35:13 -05:00
Rob Winch
0f63d98c84 Use @EnableMethodSecurity in docs tests
Previously parameters were passed in unnecessarily. This removes
the unnecessary paramaters.
2025-08-21 12:35:13 -05:00
Rob Winch
fbfbb1e571 Use 2004-present for Copyright
Spring Security migrated the copyright to use -present to simplify
the headers. This commit aligns the header.
2025-08-21 12:35:13 -05:00
Joe Kuhel
d002e68231 Update servlet test method docs to use include-code
References gh-16226

Signed-off-by: Joe Kuhel <4983938+jkuhel@users.noreply.github.com>
2025-08-21 12:35:13 -05:00
Yanming Zhou
41162aa7e3 Polish WebFluxSecurityConfiguration
Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-08-21 11:16:12 -06:00
Yanming Zhou
d86f2c957d Change @Bean method signature to return RsaKeyConversionServicePostProcessor instead of BeanFactoryPostProcessor
It's friendly for Spring Boot's `@ConditionalOnMissingBean`, and:

>> When defining a Spring `@Bean` method, it is generally recommended to declare the most specific type possible as the method's return type. This means returning the concrete class of the bean, or the most specific interface that the bean implements and through which it will be referenced in the application.

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-08-21 11:16:12 -06:00
Rob Winch
62b5b1a77c
import Assertions.assertThat
This adds a static import for assertThat in the Kotlin docs code
2025-08-21 11:19:05 -05:00
Rob Winch
523222c24d
Use @EnableMethodSecurity in docs tests
Previously parameters were passed in unnecessarily. This removes
the unnecessary paramaters.
2025-08-21 11:15:42 -05:00
Rob Winch
69f38d4933
Use 2004-present for Copyright
Spring Security migrated the copyright to use -present to simplify
the headers. This commit aligns the header.
2025-08-21 11:13:45 -05:00
Joe Kuhel
0179a811c7
Update servlet test method docs to use include-code
References gh-16226

Signed-off-by: Joe Kuhel <4983938+jkuhel@users.noreply.github.com>
2025-08-21 11:12:42 -05:00
Rob Winch
7ce2bdd701
Merge branch '6.5.x' 2025-08-21 08:55:57 -05:00
Rob Winch
de4ceffc4f
Merge branch '6.4.x' into 6.5.x 2025-08-21 08:55:48 -05:00
Rob Winch
8c920a7ee7
Bump org.springframework.data:spring-data-bom from 2024.1.8 to 2024.1.9 2025-08-21 08:55:15 -05:00
Rob Winch
b9653346a1
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final 2025-08-21 08:54:19 -05:00
Rob Winch
9e6bcbd1d0
Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10 2025-08-21 08:54:18 -05:00
dependabot[bot]
8d888edc71 Bump io.spring.nullability:io.spring.nullability.gradle.plugin
Bumps [io.spring.nullability:io.spring.nullability.gradle.plugin](https://github.com/spring-gradle-plugins/nullability-plugin) from 0.0.3 to 0.0.4.
- [Release notes](https://github.com/spring-gradle-plugins/nullability-plugin/releases)
- [Commits](https://github.com/spring-gradle-plugins/nullability-plugin/compare/v0.0.3...v0.0.4)

---
updated-dependencies:
- dependency-name: io.spring.nullability:io.spring.nullability.gradle.plugin
  dependency-version: 0.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 17:10:31 -05:00
Rob Winch
f82fe9c8c6
Remove stray modular from the documentation
Issue gh-16258
2025-08-20 12:24:33 -05:00
Rob Winch
a8f045eb50
Add Modular Spring Security Configuration
Closes gh-16258
2025-08-20 12:16:08 -05:00
Rob Winch
5c5efc9092
SpringTestContext registers WebTestClient Bean
Closes gh-17780
2025-08-20 12:15:58 -05:00
dependabot[bot]
5453224377
Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.9 to 1.14.10.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.9...v1.14.10)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-version: 1.14.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 06:23:02 +00:00
dependabot[bot]
a14ad770ab
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.23.Final to 6.6.26.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.26/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.23...6.6.26)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 6.6.26.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 04:41:59 +00:00
Jaehwan Lee
806297da23 Fix misleading variable name in authentication filter
Rename DEFAULT_ANT_PATH_REQUEST_MATCHER to DEFAULT_PATH_REQUEST_MATCHER
to reflect PathPatternRequestMatcher usage instead of legacy Ant
pattern terminology.

Signed-off-by: Jaehwan Lee <jhrick0129@gmail.com>
2025-08-19 22:21:35 -05:00
dependabot[bot]
8846b44b81
Bump org.springframework.data:spring-data-bom from 2024.1.8 to 2024.1.9
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2024.1.8 to 2024.1.9.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2024.1.8...2024.1.9)

---
updated-dependencies:
- dependency-name: org.springframework.data:spring-data-bom
  dependency-version: 2024.1.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 03:21:13 +00:00
Rob Winch
7f103b2d0a
Merge branch '6.5.x' 2025-08-19 22:19:46 -05:00
dependabot[bot]
c06ff59747
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.23.Final to 6.6.26.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.26/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.23...6.6.26)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 6.6.26.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 22:19:01 -05:00
dependabot[bot]
f1194de45e
Bump org.assertj:assertj-core from 3.27.3 to 3.27.4
Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.3 to 3.27.4.
- [Release notes](https://github.com/assertj/assertj/releases)
- [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.3...assertj-build-3.27.4)

---
updated-dependencies:
- dependency-name: org.assertj:assertj-core
  dependency-version: 3.27.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 22:19:01 -05:00
dependabot[bot]
1f16009c8d
Bump org.springframework.ldap:spring-ldap-core from 3.2.13 to 3.2.14
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.13 to 3.2.14.
- [Release notes](https://github.com/spring-projects/spring-ldap/releases)
- [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt)
- [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.13...3.2.14)

---
updated-dependencies:
- dependency-name: org.springframework.ldap:spring-ldap-core
  dependency-version: 3.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 22:19:01 -05:00
Rob Winch
3e13437b1a
Merge branch '6.4.x' into 6.5.x 2025-08-19 22:16:16 -05:00
Rob Winch
20a6863e07
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final 2025-08-19 22:15:40 -05:00
Rob Winch
e145c07d5b
Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10 2025-08-19 22:15:38 -05:00
Rob Winch
68a7f1702f
Merge branch '6.5.x' 2025-08-19 22:15:14 -05:00
Rob Winch
3e8b606aac
Merge branch '6.4.x' into 6.5.x 2025-08-19 22:14:37 -05:00
Tran Ngoc Nhan
ef5c703010 Remove unused import
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2025-08-19 22:05:25 -05:00
Andrey Litvitski
47be93e694 Annotate AuthenticationTrustResolver methods with @Nullable
Since AuthenticationTrustResolver can handle null arguments (this is
also stated in the implementation of this interface), we should mark
these arguments as `@Nullable`.

Closes: gh-17764

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2025-08-19 22:02:59 -05:00
dependabot[bot]
9310153d16 Bump io.spring.nullability:io.spring.nullability.gradle.plugin
Bumps [io.spring.nullability:io.spring.nullability.gradle.plugin](https://github.com/spring-gradle-plugins/nullability-plugin) from 0.0.2 to 0.0.3.
- [Release notes](https://github.com/spring-gradle-plugins/nullability-plugin/releases)
- [Commits](https://github.com/spring-gradle-plugins/nullability-plugin/compare/v0.0.2...v0.0.3)

---
updated-dependencies:
- dependency-name: io.spring.nullability:io.spring.nullability.gradle.plugin
  dependency-version: 0.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 21:41:13 -05:00
dependabot[bot]
a933089ec2
Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.14.9 to 1.14.10.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.9...v1.14.10)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-version: 1.14.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 14:41:53 +00:00
dependabot[bot]
a8da9ec7f7
Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.23.Final to 6.6.26.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.26/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.23...6.6.26)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 6.6.26.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-19 14:40:42 +00:00
Josh Cummings
3396890d8b
Propagate AccessDeniedException Only to ExceptionTranslationFilter
Closes gh-17761
2025-08-18 17:04:19 -06:00
Josh Cummings
c45bc384da
Interpret Expression Templates by Default
Closes gh-17763
2025-08-18 15:45:57 -06:00
Josh Cummings
4da98dde2b
Update What's New
Issue gh-17707
2025-08-18 15:31:03 -06:00
Rob Winch
7575e4ef1c
Next development version 2025-08-18 15:17:59 -05:00
github-actions[bot]
acaed1ad96 Next development version 2025-08-18 18:15:02 +00:00
github-actions[bot]
0549bf566b Next development version 2025-08-18 18:06:07 +00:00
github-actions[bot]
44037c0ea4 Release 6.5.3 2025-08-18 17:37:38 +00:00
github-actions[bot]
01c8cea00f Release 6.4.9 2025-08-18 17:37:33 +00:00
433 changed files with 7604 additions and 1281 deletions

View File

@ -17,8 +17,6 @@ package io.spring.gradle;
import org.apache.commons.io.FileUtils;
import org.gradle.testkit.runner.GradleRunner;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.File;
import java.io.IOException;

View File

@ -23,8 +23,6 @@ import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.io.File;
import static org.assertj.core.api.Assertions.assertThat;
/**

View File

@ -5,7 +5,6 @@ import org.apache.commons.io.FileUtils;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

View File

@ -25,7 +25,6 @@ dependencies {
optional project(':spring-security-ldap')
optional project(':spring-security-messaging')
optional project(path: ':spring-security-saml2-service-provider')
opensaml5 project(path: ':spring-security-saml2-service-provider', configuration: 'opensamlFiveMain')
optional project(':spring-security-oauth2-client')
optional project(':spring-security-oauth2-jose')
optional project(':spring-security-oauth2-resource-server')
@ -170,15 +169,3 @@ test {
}
}
}
tasks.register("opensaml5Test", Test) {
filter {
includeTestsMatching "org.springframework.security.config.annotation.web.configurers.saml2.*"
}
useJUnitPlatform()
classpath = sourceSets.main.output + sourceSets.test.output + configurations.opensaml5
}
tasks.named("check") {
dependsOn opensaml5Test
}

View File

@ -0,0 +1,147 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.rsocket;
import java.util.ArrayList;
import java.util.List;
import io.rsocket.core.RSocketServer;
import io.rsocket.exceptions.RejectedSetupException;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.transport.netty.server.CloseableChannel;
import io.rsocket.transport.netty.server.TcpServerTransport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
import org.springframework.security.rsocket.util.matcher.PayloadExchangeAuthorizationContext;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Andrey Litvitski
*/
@ContextConfiguration
@ExtendWith(SpringExtension.class)
public class AnonymousAuthenticationITests {
@Autowired
RSocketMessageHandler handler;
@Autowired
SecuritySocketAcceptorInterceptor interceptor;
@Autowired
ServerController controller;
private CloseableChannel server;
private RSocketRequester requester;
@BeforeEach
public void setup() {
// @formatter:off
this.server = RSocketServer.create()
.payloadDecoder(PayloadDecoder.ZERO_COPY)
.interceptors((registry) -> registry.forSocketAcceptor(this.interceptor)
)
.acceptor(this.handler.responder())
.bind(TcpServerTransport.create("localhost", 0))
.block();
// @formatter:on
}
@AfterEach
public void dispose() {
this.requester.rsocket().dispose();
this.server.dispose();
this.controller.payloads.clear();
}
@Test
public void requestWhenAnonymousDisabledThenRespondsWithForbidden() {
this.requester = RSocketRequester.builder()
.rsocketStrategies(this.handler.getRSocketStrategies())
.connectTcp("localhost", this.server.address().getPort())
.block();
String data = "andrew";
assertThatExceptionOfType(RejectedSetupException.class).isThrownBy(
() -> this.requester.route("secure.retrieve-mono").data(data).retrieveMono(String.class).block());
assertThat(this.controller.payloads).isEmpty();
}
@Configuration
@EnableRSocketSecurity
static class Config {
@Bean
ServerController controller() {
return new ServerController();
}
@Bean
RSocketMessageHandler messageHandler() {
return new RSocketMessageHandler();
}
@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
ReactiveAuthorizationManager<PayloadExchangeAuthorizationContext> anonymous = (authentication,
exchange) -> authentication.map(trustResolver::isAnonymous).map(AuthorizationDecision::new);
rsocket.authorizePayload((authorize) -> authorize.anyExchange().access(anonymous));
rsocket.anonymous((anonymousAuthentication) -> anonymousAuthentication.disable());
return rsocket.build();
}
}
@Controller
static class ServerController {
private List<String> payloads = new ArrayList<>();
@MessageMapping("**")
String retrieveMono(String payload) {
add(payload);
return "Hi " + payload;
}
private void add(String p) {
this.payloads.add(p);
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config;
/**
* A {@link Customizer} that allows invocation of code that throws a checked exception.
*
* @param <T> The type of input.
*/
@FunctionalInterface
public interface ThrowingCustomizer<T> extends Customizer<T> {
/**
* Default {@link Customizer#customize(Object)} that wraps any thrown checked
* exceptions (by default in a {@link RuntimeException}).
* @param t the object to customize
*/
default void customize(T t) {
try {
customizeWithException(t);
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Performs the customization on the given object, possibly throwing a checked
* exception.
* @param t the object to customize
* @throws Exception on error
*/
void customizeWithException(T t) throws Exception;
}

View File

@ -102,9 +102,7 @@ class AuthorizationProxyWebConfiguration implements WebMvcConfigurer {
Throwable accessDeniedException = this.throwableAnalyzer
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
if (accessDeniedException != null) {
return new ModelAndView((model, req, res) -> {
throw ex;
});
throw (AccessDeniedException) accessDeniedException;
}
return null;
}

View File

@ -109,6 +109,7 @@ import org.springframework.security.rsocket.util.matcher.RoutePayloadExchangeMat
* @author Manuel Tejeda
* @author Ebert Toribio
* @author Ngoc Nhan
* @author Andrey Litvitski
* @since 5.2
*/
public class RSocketSecurity {
@ -119,6 +120,8 @@ public class RSocketSecurity {
private SimpleAuthenticationSpec simpleAuthSpec;
private AnonymousAuthenticationSpec anonymousAuthSpec = new AnonymousAuthenticationSpec(this);
private JwtSpec jwtSpec;
private AuthorizePayloadsSpec authorizePayload;
@ -164,6 +167,20 @@ public class RSocketSecurity {
return this;
}
/**
* Adds anonymous authentication
* @param anonymous a customizer
* @return this instance
* @since 7.0
*/
public RSocketSecurity anonymous(Customizer<AnonymousAuthenticationSpec> anonymous) {
if (this.anonymousAuthSpec == null) {
this.anonymousAuthSpec = new AnonymousAuthenticationSpec(this);
}
anonymous.customize(this.anonymousAuthSpec);
return this;
}
/**
* Adds authentication with BasicAuthenticationPayloadExchangeConverter.
* @param basic
@ -214,7 +231,9 @@ public class RSocketSecurity {
if (this.jwtSpec != null) {
result.addAll(this.jwtSpec.build());
}
result.add(anonymous());
if (this.anonymousAuthSpec != null) {
result.add(this.anonymousAuthSpec.build());
}
if (this.authorizePayload != null) {
result.add(this.authorizePayload.build());
}
@ -222,12 +241,6 @@ public class RSocketSecurity {
return result;
}
private AnonymousPayloadInterceptor anonymous() {
AnonymousPayloadInterceptor result = new AnonymousPayloadInterceptor("anonymousUser");
result.setOrder(PayloadInterceptorOrder.ANONYMOUS.getOrder());
return result;
}
private <T> T getBean(Class<T> beanClass) {
if (this.context == null) {
return null;
@ -283,6 +296,26 @@ public class RSocketSecurity {
}
public final class AnonymousAuthenticationSpec {
private RSocketSecurity parent;
private AnonymousAuthenticationSpec(RSocketSecurity parent) {
this.parent = parent;
}
protected AnonymousPayloadInterceptor build() {
AnonymousPayloadInterceptor result = new AnonymousPayloadInterceptor("anonymousUser");
result.setOrder(PayloadInterceptorOrder.ANONYMOUS.getOrder());
return result;
}
public void disable() {
this.parent.anonymousAuthSpec = null;
}
}
public final class BasicAuthenticationSpec {
private ReactiveAuthenticationManager authenticationManager;

View File

@ -16,19 +16,24 @@
package org.springframework.security.config.annotation.web.configuration;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.ObjectProvider;
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.context.annotation.Scope;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@ -46,6 +51,7 @@ import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.ThrowingSupplier;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
@ -131,6 +137,8 @@ class HttpSecurityConfiguration {
// @formatter:on
applyCorsIfAvailable(http);
applyDefaultConfigurers(http);
applyHttpSecurityCustomizers(this.context, http);
applyTopLevelCustomizers(this.context, http);
return http;
}
@ -160,6 +168,73 @@ class HttpSecurityConfiguration {
}
}
/**
* Applies all {@code Customizer<HttpSecurity>} Bean instances to the
* {@link HttpSecurity} instance.
* @param applicationContext the {@link ApplicationContext} to lookup Bean instances
* @param http the {@link HttpSecurity} to apply the Beans to.
*/
private void applyHttpSecurityCustomizers(ApplicationContext applicationContext, HttpSecurity http) {
ResolvableType httpSecurityCustomizerType = ResolvableType.forClassWithGenerics(Customizer.class,
HttpSecurity.class);
ObjectProvider<Customizer<HttpSecurity>> customizerProvider = this.context
.getBeanProvider(httpSecurityCustomizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
customizer.customize(http)
);
// @formatter:on
}
/**
* Applies all {@link Customizer} Beans to {@link HttpSecurity}. For each public,
* non-static method in HttpSecurity that accepts a Customizer
* <ul>
* <li>Use the {@link MethodParameter} (this preserves generics) to resolve all Beans
* for that type</li>
* <li>For each {@link Customizer} Bean invoke the {@link java.lang.reflect.Method}
* with the {@link Customizer} Bean as the argument</li>
* </ul>
* @param context the {@link ApplicationContext}
* @param http the {@link HttpSecurity}
* @throws Exception
*/
private void applyTopLevelCustomizers(ApplicationContext context, HttpSecurity http) {
ReflectionUtils.MethodFilter isCustomizerMethod = (method) -> {
if (Modifier.isStatic(method.getModifiers())) {
return false;
}
if (!Modifier.isPublic(method.getModifiers())) {
return false;
}
if (!method.canAccess(http)) {
return false;
}
if (method.getParameterCount() != 1) {
return false;
}
if (method.getParameterTypes()[0] == Customizer.class) {
return true;
}
return false;
};
ReflectionUtils.MethodCallback invokeWithEachCustomizerBean = (customizerMethod) -> {
MethodParameter customizerParameter = new MethodParameter(customizerMethod, 0);
ResolvableType customizerType = ResolvableType.forMethodParameter(customizerParameter);
ObjectProvider<?> customizerProvider = context.getBeanProvider(customizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
ReflectionUtils.invokeMethod(customizerMethod, http, customizer)
);
// @formatter:on
};
ReflectionUtils.doWithMethods(HttpSecurity.class, invokeWithEachCustomizerBean, isCustomizerMethod);
}
private Map<Class<?>, Object> createSharedObjects() {
Map<Class<?>, Object> sharedObjects = new HashMap<>();
sharedObjects.put(ApplicationContext.class, this.context);

View File

@ -33,7 +33,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@ -79,6 +78,7 @@ import org.springframework.web.filter.ServletRequestPathFilter;
*
* @author Rob Winch
* @author Keesun Baik
* @author Yanming Zhou
* @since 3.2
* @see EnableWebSecurity
* @see WebSecurity
@ -190,7 +190,7 @@ public class WebSecurityConfiguration implements ImportAware {
}
@Bean
public static BeanFactoryPostProcessor conversionServicePostProcessor() {
public static RsaKeyConversionServicePostProcessor conversionServicePostProcessor() {
return new RsaKeyConversionServicePostProcessor();
}

View File

@ -16,6 +16,7 @@
package org.springframework.security.config.annotation.web.reactive;
import java.lang.reflect.Modifier;
import java.util.Map;
import org.springframework.beans.BeansException;
@ -28,10 +29,13 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
@ -40,6 +44,7 @@ import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.web.reactive.result.method.annotation.CurrentSecurityContextArgumentResolver;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
@ -154,6 +159,83 @@ class ServerHttpSecurityConfiguration {
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
ServerHttpSecurity httpSecurity(ApplicationContext context) {
ServerHttpSecurity http = httpSecurity();
applyServerHttpSecurityCustomizers(context, http);
applyTopLevelBeanCustomizers(context, http);
return http;
}
/**
* Applies all {@code Custmizer<ServerHttpSecurity>} Beans to
* {@link ServerHttpSecurity}.
* @param context the {@link ApplicationContext}
* @param http the {@link ServerHttpSecurity}
* @throws Exception
*/
private void applyServerHttpSecurityCustomizers(ApplicationContext context, ServerHttpSecurity http) {
ResolvableType httpSecurityCustomizerType = ResolvableType.forClassWithGenerics(Customizer.class,
ServerHttpSecurity.class);
ObjectProvider<Customizer<ServerHttpSecurity>> customizerProvider = context
.getBeanProvider(httpSecurityCustomizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
customizer.customize(http)
);
// @formatter:on
}
/**
* Applies all {@link Customizer} Beans to top level {@link ServerHttpSecurity}
* method.
*
* For each public, non-static method in ServerHttpSecurity that accepts a Customizer
* <ul>
* <li>Use the {@link MethodParameter} (this preserves generics) to resolve all Beans
* for that type</li>
* <li>For each {@link Customizer} Bean invoke the {@link java.lang.reflect.Method}
* with the {@link Customizer} Bean as the argument</li>
* </ul>
* @param context the {@link ApplicationContext}
* @param http the {@link ServerHttpSecurity}
* @throws Exception
*/
private void applyTopLevelBeanCustomizers(ApplicationContext context, ServerHttpSecurity http) {
ReflectionUtils.MethodFilter isCustomizerMethod = (method) -> {
if (Modifier.isStatic(method.getModifiers())) {
return false;
}
if (!Modifier.isPublic(method.getModifiers())) {
return false;
}
if (!method.canAccess(http)) {
return false;
}
if (method.getParameterCount() != 1) {
return false;
}
if (method.getParameterTypes()[0] == Customizer.class) {
return true;
}
return false;
};
ReflectionUtils.MethodCallback invokeWithEachCustomizerBean = (customizerMethod) -> {
MethodParameter customizerParameter = new MethodParameter(customizerMethod, 0);
ResolvableType customizerType = ResolvableType.forMethodParameter(customizerParameter);
ObjectProvider<?> customizerProvider = context.getBeanProvider(customizerType);
// @formatter:off
customizerProvider.orderedStream().forEach((customizer) ->
ReflectionUtils.invokeMethod(customizerMethod, http, customizer)
);
// @formatter:on
};
ReflectionUtils.doWithMethods(ServerHttpSecurity.class, invokeWithEachCustomizerBean, isCustomizerMethod);
}
ServerHttpSecurity httpSecurity() {
ContextAwareServerHttpSecurity http = new ContextAwareServerHttpSecurity();
// @formatter:off

View File

@ -21,7 +21,6 @@ import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -44,12 +43,13 @@ import static org.springframework.security.config.Customizer.withDefaults;
/**
* @author Rob Winch
* @author Yanming Zhou
* @since 5.0
*/
@Configuration(proxyBeanMethods = false)
class WebFluxSecurityConfiguration {
public static final int WEB_FILTER_CHAIN_FILTER_ORDER = 0 - 100;
public static final int WEB_FILTER_CHAIN_FILTER_ORDER = -100;
private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.";
@ -100,7 +100,7 @@ class WebFluxSecurityConfiguration {
}
@Bean
static BeanFactoryPostProcessor conversionServicePostProcessor() {
static RsaKeyConversionServicePostProcessor conversionServicePostProcessor() {
return new RsaKeyConversionServicePostProcessor();
}

View File

@ -20,6 +20,7 @@ import java.util.Map;
import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AopUtils;
@ -37,7 +38,8 @@ class PointcutDelegatingAuthorizationManager implements AuthorizationManager<Met
}
@Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation object) {
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
MethodInvocation object) {
for (Map.Entry<Pointcut, AuthorizationManager<MethodInvocation>> entry : this.managers.entrySet()) {
Class<?> targetClass = (object.getThis() != null) ? AopUtils.getTargetClass(object.getThis()) : null;
if (entry.getKey().getClassFilter().matches(targetClass)

View File

@ -1316,6 +1316,10 @@ public class ServerHttpSecurity {
return this.context.getBeanNamesForType(beanClass);
}
ApplicationContext getApplicationContext() {
return this.context;
}
protected void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}

View File

@ -25,6 +25,7 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.w3c.dom.Element;
import org.springframework.beans.BeansException;
@ -458,7 +459,7 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
}
@Override
public AuthorizationResult authorize(Supplier<Authentication> authentication,
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
MessageAuthorizationContext<?> object) {
EvaluationContext context = this.expressionHandler.createEvaluationContext(authentication, object);
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, context);

View File

@ -29,7 +29,6 @@ import org.springframework.security.config.annotation.web.configurers.AuthorizeH
import org.springframework.security.config.core.GrantedAuthorityDefaults
import org.springframework.security.core.Authentication
import org.springframework.security.web.access.IpAddressAuthorizationManager
import org.springframework.security.web.access.intercept.AuthorizationFilter
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher
import org.springframework.security.web.util.matcher.AnyRequestMatcher
@ -235,13 +234,13 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
* Specify that URLs are allowed by anyone.
*/
val permitAll: AuthorizationManager<RequestAuthorizationContext> =
AuthorizationManager { _: Supplier<Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(true) }
AuthorizationManager { _: Supplier<out Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(true) }
/**
* Specify that URLs are not allowed by anyone.
*/
val denyAll: AuthorizationManager<RequestAuthorizationContext> =
AuthorizationManager { _: Supplier<Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(false) }
AuthorizationManager { _: Supplier<out Authentication>, _: RequestAuthorizationContext -> AuthorizationDecision(false) }
/**
* Specify that URLs are allowed by any authenticated user.

View File

@ -18,14 +18,22 @@ package org.springframework.security.config.annotation.web
import jakarta.servlet.Filter
import jakarta.servlet.http.HttpServletRequest
import org.springframework.beans.factory.ObjectProvider
import org.springframework.context.ApplicationContext
import org.springframework.core.MethodParameter
import org.springframework.core.ResolvableType
import org.springframework.core.annotation.AnnotationUtils
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
import org.springframework.security.web.DefaultSecurityFilterChain
import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.util.ReflectionUtils
import java.lang.reflect.Method
import java.lang.reflect.Modifier
/**
* Configures [HttpSecurity] using a [HttpSecurity Kotlin DSL][HttpSecurityDsl].
@ -77,6 +85,117 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
var authenticationManager: AuthenticationManager? = null
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
init {
applyFunction1HttpSecurityDslBeans(this.context, this)
applyTopLevelFunction1SecurityDslBeans(this.context, this)
}
/**
* Applies all `Function1<HttpSecurity,Unit>` Beans which
* allows exposing the DSL as Beans to be applied.
*
* ```
* @Bean
* fun httpSecurityDslBean(): HttpSecurityDsl.() -> Unit {
* return {
* headers {
* contentSecurityPolicy {
* policyDirectives = "object-src 'none'"
* }
* }
* redirectToHttps { }
* }
* }
* ```
*/
private fun applyFunction1HttpSecurityDslBeans(context: ApplicationContext, http: HttpSecurityDsl) : Unit {
val httpSecurityDslFnType = ResolvableType.forClassWithGenerics(Function1::class.java,
HttpSecurityDsl::class.java, Unit::class.java)
val httpSecurityDslFnProvider = context
.getBeanProvider<Function1<HttpSecurityDsl,Unit>>(httpSecurityDslFnType)
// @formatter:off
httpSecurityDslFnProvider.orderedStream().forEach { fn -> fn.invoke(http) }
// @formatter:on
}
/**
* Applies all `Function1<T,Unit>` Beans such that `T` is a top level
* DSL on `HttpSecurityDsl`. This allows exposing the top level
* DSLs as Beans to be applied.
*
*
* ```
* @Bean
* fun httpSecurityCustomizer(): ThrowingCustomizer<HttpSecurity> {
* return ThrowingCustomizer { http -> http
* .headers { headers -> headers
* .contentSecurityPolicy { csp -> csp
* .policyDirectives("object-src 'none'")
* }
* }
* .redirectToHttps(Customizer.withDefaults())
* }
* }
* ```
*
* @param context the [ApplicationContext]
* @param http the [HttpSecurity]
* @throws Exception
*/
private fun applyTopLevelFunction1SecurityDslBeans(context: ApplicationContext, http: HttpSecurityDsl) {
val isCustomizerMethod = ReflectionUtils.MethodFilter { method: Method ->
if (Modifier.isStatic(method.modifiers)) {
return@MethodFilter false
}
if (!Modifier.isPublic(method.modifiers)) {
return@MethodFilter false
}
if (!method.canAccess(http)) {
return@MethodFilter false
}
if (method.parameterCount != 1) {
return@MethodFilter false
}
return@MethodFilter extractDslType(method) != null
}
val invokeWithEachDslBean = ReflectionUtils.MethodCallback { dslMethod: Method ->
val dslFunctionType = firstMethodResolvableType(dslMethod)!!
val dslFunctionProvider: ObjectProvider<*> = context.getBeanProvider<Any>(dslFunctionType)
// @formatter:off
dslFunctionProvider.orderedStream().forEach {customizer: Any -> ReflectionUtils.invokeMethod(dslMethod, http, customizer)}
}
ReflectionUtils.doWithMethods(HttpSecurityDsl::class.java, invokeWithEachDslBean, isCustomizerMethod)
}
/**
* From a `Method` with the first argument `Function<T,Unit>` return `T` or `null`
* if the first argument is not a `Function`.
* @return From a `Method` with the first argument `Function<T,Unit>` return `T`.
*/
private fun extractDslType(method: Method): ResolvableType? {
val functionType = firstMethodResolvableType(method)
if (!Function::class.java.isAssignableFrom(functionType.toClass())) {
return null
}
val functionInputType = functionType.getGeneric(0)
val securityMarker = AnnotationUtils.findAnnotation(functionInputType.toClass(), SecurityMarker::class.java)
val isSecurityDsl = securityMarker != null
if (!isSecurityDsl) {
return null
}
return functionInputType
}
private fun firstMethodResolvableType(method: Method): ResolvableType {
val parameter = MethodParameter(
method, 0
)
return ResolvableType.forMethodParameter(parameter)
}
/**
* Applies a [SecurityConfigurerAdapter] to this [HttpSecurity]
*

View File

@ -16,13 +16,23 @@
package org.springframework.security.config.web.server
import org.springframework.beans.factory.ObjectProvider
import org.springframework.context.ApplicationContext
import org.springframework.core.MethodParameter
import org.springframework.core.ResolvableType
import org.springframework.core.annotation.AnnotationUtils
import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.context.ServerSecurityContextRepository
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
import org.springframework.util.ReflectionUtils
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import java.lang.reflect.Method
import java.lang.reflect.Modifier
/**
* Configures [ServerHttpSecurity] using a [ServerHttpSecurity Kotlin DSL][ServerHttpSecurityDsl].
@ -68,6 +78,115 @@ class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val in
var authenticationManager: ReactiveAuthenticationManager? = null
var securityContextRepository: ServerSecurityContextRepository? = null
init {
applyFunction1HttpSecurityDslBeans(this.http.applicationContext, this)
applyTopLevelFunction1SecurityDslBeans(this.http.applicationContext, this)
}
/**
* Applies all `Function1<ServerHttpSecurityDsl,Unit>` Beans which
* allows exposing the DSL as Beans to be applied.
*
* ```
* @Bean
* @Order(Ordered.LOWEST_PRECEDENCE)
* fun userAuthorization(): ServerHttpSecurityDsl.() -> Unit {
* // @formatter:off
* return {
* authorizeExchange {
* authorize("/user/profile", hasRole("USER"))
* }
* }
* // @formatter:on
* }
* ```
*/
private fun applyFunction1HttpSecurityDslBeans(context: ApplicationContext, http: ServerHttpSecurityDsl) : Unit {
val httpSecurityDslFnType = ResolvableType.forClassWithGenerics(Function1::class.java,
ServerHttpSecurityDsl::class.java, Unit::class.java)
val httpSecurityDslFnProvider = context
.getBeanProvider<Function1<ServerHttpSecurityDsl,Unit>>(httpSecurityDslFnType)
// @formatter:off
httpSecurityDslFnProvider.orderedStream().forEach { fn -> fn.invoke(http) }
// @formatter:on
}
/**
* Applies all `Function1<T,Unit>` Beans such that `T` is a top level
* DSL on `ServerHttpSecurityDsl`. This allows exposing the top level
* DSLs as Beans to be applied.
*
* ```
* @Bean
* fun headersSecurity(): Customizer<ServerHttpSecurity.HeaderSpec> {
* // @formatter:off
* return Customizer { headers -> headers
* .contentSecurityPolicy { csp -> csp
* .policyDirectives("object-src 'none'")
* }
* }
* // @formatter:on
* }
* ```
*
* @param context the [ApplicationContext]
* @param http the [HttpSecurity]
* @throws Exception
*/
private fun applyTopLevelFunction1SecurityDslBeans(context: ApplicationContext, http: ServerHttpSecurityDsl) {
val isCustomizerMethod = ReflectionUtils.MethodFilter { method: Method ->
if (Modifier.isStatic(method.modifiers)) {
return@MethodFilter false
}
if (!Modifier.isPublic(method.modifiers)) {
return@MethodFilter false
}
if (!method.canAccess(http)) {
return@MethodFilter false
}
if (method.parameterCount != 1) {
return@MethodFilter false
}
return@MethodFilter extractDslType(method) != null
}
val invokeWithEachDslBean = ReflectionUtils.MethodCallback { dslMethod: Method ->
val dslFunctionType = firstMethodResolvableType(dslMethod)!!
val dslFunctionProvider: ObjectProvider<*> = context.getBeanProvider<Any>(dslFunctionType)
// @formatter:off
dslFunctionProvider.orderedStream().forEach {customizer: Any -> ReflectionUtils.invokeMethod(dslMethod, http, customizer)}
}
ReflectionUtils.doWithMethods(ServerHttpSecurityDsl::class.java, invokeWithEachDslBean, isCustomizerMethod)
}
/**
* From a `Method` with the first argument `Function<T,Unit>` return `T` or `null`
* if the first argument is not a `Function`.
* @return From a `Method` with the first argument `Function<T,Unit>` return `T`.
*/
private fun extractDslType(method: Method): ResolvableType? {
val functionType = firstMethodResolvableType(method)
if (!Function::class.java.isAssignableFrom(functionType.toClass())) {
return null
}
val functionInputType = functionType.getGeneric(0)
val securityMarker = AnnotationUtils.findAnnotation(functionInputType.toClass(), ServerSecurityMarker::class.java)
val isSecurityDsl = securityMarker != null
if (!isSecurityDsl) {
return null
}
return functionInputType
}
private fun firstMethodResolvableType(method: Method): ResolvableType {
val parameter = MethodParameter(
method, 0
)
return ResolvableType.forMethodParameter(parameter)
}
/**
* Allows configuring the [ServerHttpSecurity] to only be invoked when matching the
* provided [ServerWebExchangeMatcher].

View File

@ -78,6 +78,7 @@ import org.springframework.security.authentication.jaas.event.JaasAuthentication
import org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent;
import org.springframework.security.authentication.ott.DefaultOneTimeToken;
import org.springframework.security.authentication.ott.InvalidOneTimeTokenException;
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication;
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken;
import org.springframework.security.authentication.password.CompromisedPasswordException;
import org.springframework.security.authorization.AuthorityAuthorizationDecision;
@ -400,6 +401,8 @@ final class SerializationSamples {
});
generatorByClassName.put(OneTimeTokenAuthenticationToken.class,
(r) -> applyDetails(new OneTimeTokenAuthenticationToken("username", "token")));
generatorByClassName.put(OneTimeTokenAuthentication.class,
(r) -> applyDetails(new OneTimeTokenAuthentication("username", authentication.getAuthorities())));
generatorByClassName.put(AccessDeniedException.class,
(r) -> new AccessDeniedException("access denied", new RuntimeException()));
generatorByClassName.put(AuthorizationServiceException.class,

View File

@ -33,6 +33,7 @@ import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.ObservationTextPublisher;
import jakarta.annotation.security.DenyAll;
import jakarta.servlet.RequestDispatcher;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@ -138,6 +139,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@ -149,6 +151,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
@ -1279,6 +1282,19 @@ public class PrePostMethodSecurityConfigurationTests {
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
}
// gh-17761
@Test
void getWhenPostAuthorizeAuthenticationNameNotMatchThenNoExceptionExposedInRequest() throws Exception {
this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/authorized-person")
.param("name", "john")
.with(user("rob"));
// @formatter:on
this.mvc.perform(requestWithUser)
.andExpect(request().attribute(RequestDispatcher.ERROR_EXCEPTION, nullValue()));
}
@Test
void getWhenPostAuthorizeWithinServiceAuthenticationNameMatchesThenRespondsWithOk() throws Exception {
this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class, BasicService.class).autowire();

View File

@ -25,6 +25,7 @@ import javax.security.auth.login.LoginContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -310,7 +311,7 @@ public class NamespaceHttpTests {
}
@Override
public AuthorizationResult authorize(Supplier<Authentication> authentication,
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
RequestAuthorizationContext object) {
HttpServletRequest request = object.getRequest();
FilterInvocation invocation = new FilterInvocation(request.getContextPath(), request.getServletPath(),

View File

@ -28,14 +28,20 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.access.AccessDeniedException;
@ -54,6 +60,7 @@ import org.springframework.security.config.annotation.SecurityContextChangedList
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
@ -82,12 +89,15 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.withSettings;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
@ -425,6 +435,77 @@ public class HttpSecurityConfigurationTests {
.andExpectAll(status().isFound(), redirectedUrl("/"), authenticated());
}
@Test
void authorizeHttpRequestsCustomizerBean() throws Exception {
this.spring.register(AuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
.getContext()
.getBean("authorizeRequests", Customizer.class);
verify(authorizeRequests).customize(any());
}
@Test
void multiAuthorizeHttpRequestsCustomizerBean() throws Exception {
this.spring.register(MultiAuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests0 = this.spring
.getContext()
.getBean("authorizeRequests0", Customizer.class);
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
.getContext()
.getBean("authorizeRequests", Customizer.class);
InOrder inOrder = Mockito.inOrder(authorizeRequests0, authorizeRequests);
ArgumentCaptor<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> arg0 = ArgumentCaptor
.forClass(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry.class);
ArgumentCaptor<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> arg1 = ArgumentCaptor
.forClass(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry.class);
inOrder.verify(authorizeRequests0).customize(arg0.capture());
inOrder.verify(authorizeRequests).customize(arg1.capture());
}
@Test
void disableAuthorizeHttpRequestsCustomizerBean() throws Exception {
this.spring.register(AuthorizeRequestsBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests = this.spring
.getContext()
.getBean("authorizeRequests", Customizer.class);
verify(authorizeRequests).customize(any());
}
@Test
void httpSecurityCustomizerBean() throws Exception {
this.spring.register(HttpSecurityCustomizerBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<HttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
ArgumentCaptor<HttpSecurity> arg0 = ArgumentCaptor.forClass(HttpSecurity.class);
verify(httpSecurityCustomizer).customize(arg0.capture());
}
@Test
void multiHttpSecurityCustomizerBean() throws Exception {
this.spring.register(MultiHttpSecurityCustomizerBeanConfiguration.class, UserDetailsConfig.class).autowire();
Customizer<HttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
Customizer<HttpSecurity> httpSecurityCustomizer0 = this.spring.getContext()
.getBean("httpSecurityCustomizer0", Customizer.class);
InOrder inOrder = Mockito.inOrder(httpSecurityCustomizer0, httpSecurityCustomizer);
ArgumentCaptor<HttpSecurity> arg0 = ArgumentCaptor.forClass(HttpSecurity.class);
ArgumentCaptor<HttpSecurity> arg1 = ArgumentCaptor.forClass(HttpSecurity.class);
inOrder.verify(httpSecurityCustomizer0).customize(arg0.capture());
inOrder.verify(httpSecurityCustomizer).customize(arg1.capture());
}
@RestController
static class NameController {
@ -785,6 +866,134 @@ public class HttpSecurityConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableWebMvc
static class AuthorizeRequestsBeanConfiguration {
@Bean
SecurityFilterChain noAuthorizeSecurity(HttpSecurity http) throws Exception {
http.httpBasic(withDefaults());
return http.build();
}
@Bean
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests()
throws Exception {
Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authz = mock(
Customizer.class, withSettings().name("authz"));
// prevent validation errors of no authorization rules being defined
willAnswer(((invocation) -> {
AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry requests = invocation
.getArgument(0);
requests.anyRequest().authenticated();
return null;
})).given(authz).customize(any());
return authz;
}
@RestController
static class PublicController {
@GetMapping("/public")
String permitAll() {
return "public";
}
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableWebMvc
static class DisableAuthorizeRequestsBeanConfiguration {
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
http.httpBasic(withDefaults());
// @formatter:off
http.authorizeHttpRequests((requests) -> requests
.anyRequest().permitAll()
);
// @formatter:on
return http.build();
}
@Bean
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests()
throws Exception {
// @formatter:off
return (requests) -> requests
.anyRequest().denyAll();
// @formatter:on
}
@RestController
static class PublicController {
@GetMapping("/public")
String permitAll() {
return "public";
}
}
}
@Configuration(proxyBeanMethods = false)
@Import(AuthorizeRequestsBeanConfiguration.class)
static class MultiAuthorizeRequestsBeanConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeRequests0()
throws Exception {
return mock(Customizer.class, withSettings().name("authz0"));
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableWebMvc
static class HttpSecurityCustomizerBeanConfiguration {
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
http.httpBasic(withDefaults());
return http.build();
}
@Bean
static Customizer<HttpSecurity> httpSecurityCustomizer() {
return mock(Customizer.class, withSettings().name("httpSecurityCustomizer"));
}
@RestController
static class PublicController {
@GetMapping("/public")
String permitAll() {
return "public";
}
}
}
@Configuration(proxyBeanMethods = false)
@Import(HttpSecurityCustomizerBeanConfiguration.class)
static class MultiHttpSecurityCustomizerBeanConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
static Customizer<HttpSecurity> httpSecurityCustomizer0() throws Exception {
return mock(Customizer.class, withSettings().name("httpSecurityCustomizer0"));
}
}
private static class TestCompromisedPasswordChecker implements CompromisedPasswordChecker {
@Override

View File

@ -24,6 +24,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@ -34,6 +35,7 @@ import org.springframework.security.core.userdetails.AuthenticationUserDetailsSe
import org.springframework.security.core.userdetails.User;
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
import org.springframework.test.web.servlet.MockMvc;
@ -64,18 +66,16 @@ public class JeeConfigurerTests {
@Test
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eePreAuthenticatedProcessingFilter() {
ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
this.spring.register(ObjectPostProcessorConfig.class).autowire();
verify(ObjectPostProcessorConfig.objectPostProcessor)
.postProcess(any(J2eePreAuthenticatedProcessingFilter.class));
ObjectPostProcessor<Object> objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class);
verify(objectPostProcessor).postProcess(any(J2eePreAuthenticatedProcessingFilter.class));
}
@Test
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eeBasedPreAuthenticatedWebAuthenticationDetailsSource() {
ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
this.spring.register(ObjectPostProcessorConfig.class).autowire();
verify(ObjectPostProcessorConfig.objectPostProcessor)
.postProcess(any(J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.class));
ObjectPostProcessor<Object> objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class);
verify(objectPostProcessor).postProcess(any(J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.class));
}
@Test
@ -135,12 +135,14 @@ public class JeeConfigurerTests {
public void requestWhenCustomAuthenticatedUserDetailsServiceInLambdaThenCustomAuthenticatedUserDetailsServiceUsed()
throws Exception {
this.spring.register(JeeCustomAuthenticatedUserDetailsServiceConfig.class).autowire();
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userDetailsService = this.spring
.getContext()
.getBean(AuthenticationUserDetailsService.class);
Principal user = mock(Principal.class);
User userDetails = new User("user", "N/A", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_USER"));
given(user.getName()).willReturn("user");
given(JeeCustomAuthenticatedUserDetailsServiceConfig.authenticationUserDetailsService.loadUserDetails(any()))
.willReturn(userDetails);
given(userDetailsService.loadUserDetails(any())).willReturn(userDetails);
// @formatter:off
MockHttpServletRequestBuilder authRequest = get("/")
.principal(user)
@ -157,7 +159,7 @@ public class JeeConfigurerTests {
@EnableWebSecurity
static class ObjectPostProcessorConfig {
static ObjectPostProcessor<Object> objectPostProcessor;
ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@ -169,8 +171,9 @@ public class JeeConfigurerTests {
}
@Bean
static ObjectPostProcessor<Object> objectPostProcessor() {
return objectPostProcessor;
@Primary
ObjectPostProcessor<Object> objectPostProcessor() {
return this.objectPostProcessor;
}
}
@ -245,7 +248,7 @@ public class JeeConfigurerTests {
@EnableWebSecurity
public static class JeeCustomAuthenticatedUserDetailsServiceConfig {
static AuthenticationUserDetailsService authenticationUserDetailsService = mock(
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService = mock(
AuthenticationUserDetailsService.class);
@Bean
@ -256,12 +259,17 @@ public class JeeConfigurerTests {
.anyRequest().hasRole("USER")
)
.jee((jee) -> jee
.authenticatedUserDetailsService(authenticationUserDetailsService)
.authenticatedUserDetailsService(this.authenticationUserDetailsService)
);
return http.build();
// @formatter:on
}
@Bean
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService() {
return this.authenticationUserDetailsService;
}
}
}

View File

@ -29,12 +29,17 @@ import io.micrometer.observation.ObservationRegistry;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
import reactor.core.publisher.Mono;
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.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.password.CompromisedPasswordDecision;
@ -46,6 +51,7 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@ -267,6 +273,47 @@ public class ServerHttpSecurityConfigurationTests {
assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after");
}
@Test
void authorizeExchangeCustomizerBean() {
this.spring.register(AuthorizeExchangeCustomizerBeanConfig.class).autowire();
Customizer<AuthorizeExchangeSpec> authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class);
ArgumentCaptor<AuthorizeExchangeSpec> arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class);
verify(authzCustomizer).customize(arg0.capture());
}
@Test
void multiAuthorizeExchangeCustomizerBean() {
this.spring.register(MultiAuthorizeExchangeCustomizerBeanConfig.class).autowire();
Customizer<AuthorizeExchangeSpec> authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class);
ArgumentCaptor<AuthorizeExchangeSpec> arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class);
verify(authzCustomizer).customize(arg0.capture());
}
@Test
void serverHttpSecurityCustomizerBean() {
this.spring.register(ServerHttpSecurityCustomizerConfig.class).autowire();
Customizer<ServerHttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
ArgumentCaptor<ServerHttpSecurity> arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class);
verify(httpSecurityCustomizer).customize(arg0.capture());
}
@Test
void multiServerHttpSecurityCustomizerBean() {
this.spring.register(MultiServerHttpSecurityCustomizerConfig.class).autowire();
Customizer<ServerHttpSecurity> httpSecurityCustomizer = this.spring.getContext()
.getBean("httpSecurityCustomizer", Customizer.class);
Customizer<ServerHttpSecurity> httpSecurityCustomizer0 = this.spring.getContext()
.getBean("httpSecurityCustomizer0", Customizer.class);
InOrder inOrder = Mockito.inOrder(httpSecurityCustomizer0, httpSecurityCustomizer);
ArgumentCaptor<ServerHttpSecurity> arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class);
inOrder.verify(httpSecurityCustomizer0).customize(arg0.capture());
inOrder.verify(httpSecurityCustomizer).customize(arg0.capture());
}
@Configuration
static class SubclassConfig extends ServerHttpSecurityConfiguration {
@ -474,4 +521,64 @@ public class ServerHttpSecurityConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@EnableWebFluxSecurity
@Import(UserDetailsConfig.class)
static class AuthorizeExchangeCustomizerBeanConfig {
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http.build();
}
@Bean
static Customizer<AuthorizeExchangeSpec> authz() {
return mock(Customizer.class);
}
}
@Configuration(proxyBeanMethods = false)
@Import(AuthorizeExchangeCustomizerBeanConfig.class)
static class MultiAuthorizeExchangeCustomizerBeanConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
Customizer<AuthorizeExchangeSpec> authz0() {
return mock(Customizer.class);
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebFlux
@EnableWebFluxSecurity
@Import(UserDetailsConfig.class)
static class ServerHttpSecurityCustomizerConfig {
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http.build();
}
@Bean
static Customizer<ServerHttpSecurity> httpSecurityCustomizer() {
return mock(Customizer.class);
}
}
@Configuration(proxyBeanMethods = false)
@Import(ServerHttpSecurityCustomizerConfig.class)
static class MultiServerHttpSecurityCustomizerConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
static Customizer<ServerHttpSecurity> httpSecurityCustomizer0() {
return mock(Customizer.class);
}
}
}

View File

@ -464,7 +464,9 @@ public class MethodSecurityBeanDefinitionParserTests {
static class MyAuthorizationManager implements AuthorizationManager<MethodInvocation> {
@Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation object) {
public AuthorizationResult authorize(
Supplier<? extends @org.jspecify.annotations.Nullable Authentication> authentication,
MethodInvocation object) {
return new AuthorizationDecision("bob".equals(authentication.get().getName()));
}

View File

@ -30,8 +30,11 @@ import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostP
import org.springframework.mock.web.MockServletConfig;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.util.InMemoryXmlWebApplicationContext;
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.servlet.MockServletContext;
import org.springframework.test.context.web.GenericXmlWebContextLoader;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
@ -42,6 +45,7 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.server.WebFilter;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
@ -156,6 +160,18 @@ public class SpringTestContext implements Closeable {
// @formatter:on
this.context.getBeanFactory().registerResolvableDependency(MockMvc.class, mockMvc);
}
String webFluxSecurityBean = "org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.WebFilterChainFilter";
if (this.context.containsBean(webFluxSecurityBean)) {
WebFilter springSecurityFilter = this.context.getBean(webFluxSecurityBean, WebFilter.class);
// @formatter:off
WebTestClient webTest = WebTestClient
.bindToController(new WebTestClientBuilder.Http200RestController())
.webFilter(springSecurityFilter)
.apply(SecurityMockServerConfigurers.springSecurity())
.build();
// @formatter:on
this.context.getBeanFactory().registerResolvableDependency(WebTestClient.class, webTest);
}
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(this.context.getBeanFactory());
bpp.processInjection(this.test);

View File

@ -26,6 +26,7 @@ import java.util.Map;
import java.util.function.Supplier;
import org.assertj.core.api.ThrowableAssert;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -735,7 +736,7 @@ public class WebSocketMessageBrokerConfigTests {
}
@Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication,
public EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
Message<Object> message) {
return new StandardEvaluationContext(new MessageSecurityExpressionRoot(authentication, message) {
public boolean denyNile() {

View File

@ -193,7 +193,7 @@ class AuthorizeHttpRequestsDslTests {
open class MvcMatcherPathVariablesConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
val access = AuthorizationManager { _: Supplier<Authentication>, context: RequestAuthorizationContext ->
val access = AuthorizationManager { _: Supplier<out Authentication>, context: RequestAuthorizationContext ->
AuthorizationDecision(context.variables["userName"] == "user")
}
http {

View File

@ -367,7 +367,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filters: List<Filter> = filterChain.getFilters("/")
val filters: List<Filter>? = filterChain.getFilters("/")
assertThat(filters).anyMatch { it is CustomFilter }
}
@ -390,7 +390,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterConfigReified::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filters: List<Filter> = filterChain.getFilters("/")
val filters: List<Filter>? = filterChain.getFilters("/")
assertThat(filters).anyMatch { it is CustomFilter }
}
@ -413,7 +413,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterAfterConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filters: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
val filters: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filters).containsSubsequence(
UsernamePasswordAuthenticationFilter::class.java,
@ -440,7 +440,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterAfterConfigReified::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filterClasses).containsSubsequence(
UsernamePasswordAuthenticationFilter::class.java,
@ -467,7 +467,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterBeforeConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filters: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
val filters: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filters).containsSubsequence(
CustomFilter::class.java,
@ -494,7 +494,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomFilterBeforeConfigReified::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filterClasses).containsSubsequence(
CustomFilter::class.java,
@ -523,7 +523,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomSecurityConfigurerConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filterClasses).contains(
CustomFilter::class.java
@ -535,7 +535,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomSecurityConfigurerConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filterClasses).contains(
CustomFilter::class.java
@ -588,7 +588,7 @@ class HttpSecurityDslTests {
this.spring.register(CustomDslUsingWithConfig::class.java).autowire()
val filterChain = spring.context.getBean(FilterChainProxy::class.java)
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/").map { it.javaClass }
val filterClasses: List<Class<out Filter>> = filterChain.getFilters("/")!!.map { it.javaClass }
assertThat(filterClasses).contains(
UsernamePasswordAuthenticationFilter::class.java
@ -623,5 +623,38 @@ class HttpSecurityDslTests {
}
@Test
fun `HTTP security when Dsl Bean`() {
this.spring.register(DslBeanConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header {
string("Content-Security-Policy", "object-src 'none'")
}
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
open class DslBeanConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
httpBasic { }
}
return http.build()
}
@Bean
open fun headersDsl(): HeadersDsl.() -> Unit {
return {
contentSecurityPolicy {
policyDirectives = "object-src 'none'"
}
}
}
}
}

View File

@ -350,8 +350,8 @@ class LogoutDslTests {
class NoopLogoutHandler: LogoutHandler {
override fun logout(
request: HttpServletRequest?,
response: HttpServletResponse?,
request: HttpServletRequest,
response: HttpServletResponse,
authentication: Authentication?
) { }

View File

@ -22,7 +22,6 @@ import io.mockk.mockk
import io.mockk.verify
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
@ -44,7 +43,6 @@ import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequ
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
import org.springframework.security.web.authentication.ott.DefaultGenerateOneTimeTokenRequestResolver
import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
@ -53,7 +51,6 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import java.time.Duration
import java.time.Instant
import java.time.ZoneOffset
/**
* Tests for [OneTimeTokenLoginDsl]
@ -267,7 +264,7 @@ class OneTimeTokenLoginDslTests {
)
}
constructor(redirectUrl: String?) {
constructor(redirectUrl: String) {
this.delegate =
RedirectOneTimeTokenGenerationSuccessHandler(
redirectUrl

View File

@ -132,8 +132,8 @@ class RequiresChannelDslTests {
companion object {
val CHANNEL_PROCESSOR: ChannelProcessor = object : ChannelProcessor {
override fun decide(invocation: FilterInvocation?, config: MutableCollection<ConfigAttribute>?) {}
override fun supports(attribute: ConfigAttribute?): Boolean = true
override fun decide(invocation: FilterInvocation, config: MutableCollection<ConfigAttribute>) {}
override fun supports(attribute: ConfigAttribute): Boolean = true
}
}

View File

@ -93,7 +93,7 @@ class SecurityContextDslTests {
testContext.autowire()
val filterChainProxy = testContext.context.getBean(FilterChainProxy::class.java)
// @formatter:off
val filterTypes = filterChainProxy.getFilters("/").toList()
val filterTypes = filterChainProxy.getFilters("/")!!.toList()
assertThat(filterTypes)
.anyMatch { it is SecurityContextHolderFilter }

View File

@ -270,8 +270,8 @@ class ServerHttpBasicDslTests {
open class MockServerAuthenticationFailureHandler: ServerAuthenticationFailureHandler {
override fun onAuthenticationFailure(
webFilterExchange: WebFilterExchange?,
exception: AuthenticationException?
webFilterExchange: WebFilterExchange,
exception: AuthenticationException
): Mono<Void> {
return Mono.empty()
}

View File

@ -175,8 +175,8 @@ class ServerOAuth2ResourceServerDslTests {
open class MockServerAuthenticationFailureHandler: ServerAuthenticationFailureHandler {
override fun onAuthenticationFailure(
webFilterExchange: WebFilterExchange?,
exception: AuthenticationException?
webFilterExchange: WebFilterExchange,
exception: AuthenticationException
): Mono<Void> {
return Mono.empty()
}

View File

@ -280,11 +280,11 @@ class ServerOneTimeTokenLoginDslTests {
this.delegate = ServerRedirectOneTimeTokenGenerationSuccessHandler("/login/ott")
}
constructor(redirectUrl: String?) {
constructor(redirectUrl: String) {
this.delegate = ServerRedirectOneTimeTokenGenerationSuccessHandler(redirectUrl)
}
override fun handle(exchange: ServerWebExchange?, oneTimeToken: OneTimeToken?): Mono<Void> {
override fun handle(exchange: ServerWebExchange, oneTimeToken: OneTimeToken): Mono<Void> {
lastToken = oneTimeToken
return delegate!!.handle(exchange, oneTimeToken)
}

View File

@ -18,6 +18,8 @@ package org.springframework.security.access;
import java.io.Serializable;
import org.jspecify.annotations.NullUnmarked;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
@ -45,6 +47,7 @@ import org.springframework.security.core.annotation.SecurityAnnotationScanner;
* {@link AuthorizationManager}.
*/
@Deprecated
@NullUnmarked
public interface ConfigAttribute extends Serializable {
/**

View File

@ -70,7 +70,7 @@ public abstract class AbstractSecurityExpressionHandler<T>
* suitable root object.
*/
@Override
public final EvaluationContext createEvaluationContext(Authentication authentication, T invocation) {
public final EvaluationContext createEvaluationContext(@Nullable Authentication authentication, T invocation) {
SecurityExpressionOperations root = createSecurityExpressionRoot(authentication, invocation);
StandardEvaluationContext ctx = createEvaluationContextInternal(authentication, invocation);
if (this.beanResolver != null) {
@ -91,7 +91,8 @@ public abstract class AbstractSecurityExpressionHandler<T>
* @return A {@code StandardEvaluationContext} or potentially a custom subclass if
* overridden.
*/
protected StandardEvaluationContext createEvaluationContextInternal(Authentication authentication, T invocation) {
protected StandardEvaluationContext createEvaluationContextInternal(@Nullable Authentication authentication,
T invocation) {
return new StandardEvaluationContext();
}
@ -102,8 +103,8 @@ public abstract class AbstractSecurityExpressionHandler<T>
* @param invocation the invocation (filter, method, channel)
* @return the object
*/
protected abstract SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
T invocation);
protected abstract SecurityExpressionOperations createSecurityExpressionRoot(
@Nullable Authentication authentication, T invocation);
protected @Nullable RoleHierarchy getRoleHierarchy() {
return this.roleHierarchy;

View File

@ -18,6 +18,8 @@ package org.springframework.security.access.expression;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
@ -42,7 +44,7 @@ public interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
* Provides an evaluation context in which to evaluate security expressions for the
* invocation type.
*/
EvaluationContext createEvaluationContext(Authentication authentication, T invocation);
EvaluationContext createEvaluationContext(@Nullable Authentication authentication, T invocation);
/**
* Provides an evaluation context in which to evaluate security expressions for the
@ -55,7 +57,8 @@ public interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
* @return the {@link EvaluationContext} to use
* @since 5.8
*/
default EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, T invocation) {
default EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
T invocation) {
return createEvaluationContext(authentication.get(), invocation);
}

View File

@ -78,7 +78,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
* Creates a new instance
* @param authentication the {@link Authentication} to use. Cannot be null.
*/
public SecurityExpressionRoot(Authentication authentication) {
public SecurityExpressionRoot(@Nullable Authentication authentication) {
this(() -> authentication);
}
@ -89,7 +89,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
* Cannot be null.
* @since 5.8
*/
public SecurityExpressionRoot(Supplier<Authentication> authentication) {
public SecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication) {
this.authentication = SingletonSupplier.of(() -> {
Authentication value = authentication.get();
Assert.notNull(value, "Authentication object cannot be null");
@ -177,7 +177,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat
this.trustResolver = trustResolver;
}
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
public void setRoleHierarchy(@Nullable RoleHierarchy roleHierarchy) {
this.roleHierarchy = roleHierarchy;
}

View File

@ -79,12 +79,15 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
* implementation.
*/
@Override
public StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) {
public StandardEvaluationContext createEvaluationContextInternal(@Nullable Authentication auth,
MethodInvocation mi) {
return new MethodSecurityEvaluationContext(auth, mi, getParameterNameDiscoverer());
}
@Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
@SuppressWarnings("NullAway") // FIXME: Dataflow analysis limitation
public EvaluationContext createEvaluationContext(Supplier<? extends @Nullable Authentication> authentication,
MethodInvocation mi) {
MethodSecurityExpressionOperations root = createSecurityExpressionRoot(authentication, mi);
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(root, mi,
getParameterNameDiscoverer());
@ -96,13 +99,13 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr
* Creates the root object for expression evaluation.
*/
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication,
MethodInvocation invocation) {
return createSecurityExpressionRoot(() -> authentication, invocation);
}
private MethodSecurityExpressionOperations createSecurityExpressionRoot(Supplier<Authentication> authentication,
MethodInvocation invocation) {
private MethodSecurityExpressionOperations createSecurityExpressionRoot(
Supplier<? extends @Nullable Authentication> authentication, MethodInvocation invocation) {
MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());

View File

@ -38,11 +38,11 @@ class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements Met
private @Nullable Object target;
MethodSecurityExpressionRoot(Authentication a) {
MethodSecurityExpressionRoot(@Nullable Authentication a) {
super(a);
}
MethodSecurityExpressionRoot(Supplier<Authentication> authentication) {
MethodSecurityExpressionRoot(Supplier<? extends @Nullable Authentication> authentication) {
super(authentication);
}

View File

@ -18,6 +18,8 @@ package org.springframework.security.access.vote;
import java.util.Collection;
import org.jspecify.annotations.NullUnmarked;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
@ -53,6 +55,7 @@ import org.springframework.security.core.GrantedAuthority;
* instead
*/
@Deprecated
@NullUnmarked
public class RoleVoter implements AccessDecisionVoter<Object> {
private String rolePrefix = "ROLE_";

View File

@ -16,6 +16,9 @@
package org.springframework.security.authentication;
import org.jspecify.annotations.Nullable;
import org.springframework.lang.Contract;
import org.springframework.security.core.Authentication;
/**
@ -37,7 +40,7 @@ public interface AuthenticationTrustResolver {
* @return <code>true</code> the passed authentication token represented an anonymous
* principal, <code>false</code> otherwise
*/
boolean isAnonymous(Authentication authentication);
boolean isAnonymous(@Nullable Authentication authentication);
/**
* Indicates whether the passed <code>Authentication</code> token represents user that
@ -51,7 +54,7 @@ public interface AuthenticationTrustResolver {
* @return <code>true</code> the passed authentication token represented a principal
* authenticated using a remember-me token, <code>false</code> otherwise
*/
boolean isRememberMe(Authentication authentication);
boolean isRememberMe(@Nullable Authentication authentication);
/**
* Indicates whether the passed <code>Authentication</code> token represents a fully
@ -66,7 +69,7 @@ public interface AuthenticationTrustResolver {
* {@link #isRememberMe(Authentication)}, <code>false</code> otherwise
* @since 6.1
*/
default boolean isFullyAuthenticated(Authentication authentication) {
default boolean isFullyAuthenticated(@Nullable Authentication authentication) {
return isAuthenticated(authentication) && !isRememberMe(authentication);
}
@ -78,7 +81,8 @@ public interface AuthenticationTrustResolver {
* {@link Authentication#isAuthenticated()} is true.
* @since 6.1.7
*/
default boolean isAuthenticated(Authentication authentication) {
@Contract("null -> false")
default boolean isAuthenticated(@Nullable Authentication authentication) {
return authentication != null && authentication.isAuthenticated() && !isAnonymous(authentication);
}

View File

@ -16,6 +16,8 @@
package org.springframework.security.authentication;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
/**
@ -44,7 +46,7 @@ public class AuthenticationTrustResolverImpl implements AuthenticationTrustResol
}
@Override
public boolean isAnonymous(Authentication authentication) {
public boolean isAnonymous(@Nullable Authentication authentication) {
if ((this.anonymousClass == null) || (authentication == null)) {
return false;
}
@ -52,7 +54,7 @@ public class AuthenticationTrustResolverImpl implements AuthenticationTrustResol
}
@Override
public boolean isRememberMe(Authentication authentication) {
public boolean isRememberMe(@Nullable Authentication authentication) {
if ((this.rememberMeClass == null) || (authentication == null)) {
return false;
}

View File

@ -39,7 +39,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
private static final long serialVersionUID = 620L;
private final Object principal;
private final @Nullable Object principal;
private @Nullable Object credentials;
@ -49,7 +49,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
* will return <code>false</code>.
*
*/
public UsernamePasswordAuthenticationToken(Object principal, @Nullable Object credentials) {
public UsernamePasswordAuthenticationToken(@Nullable Object principal, @Nullable Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
@ -82,7 +82,8 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
*
* @since 5.7
*/
public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, @Nullable Object credentials) {
public static UsernamePasswordAuthenticationToken unauthenticated(@Nullable Object principal,
@Nullable Object credentials) {
return new UsernamePasswordAuthenticationToken(principal, credentials);
}
@ -106,7 +107,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
}
@Override
public Object getPrincipal() {
public @Nullable Object getPrincipal() {
return this.principal;
}

View File

@ -178,8 +178,10 @@ public abstract class AbstractJaasAuthenticationProvider implements Authenticati
// applied.
authorities = getAuthorities(principals);
// Convert the authorities set back to an array and apply it to the token.
JaasAuthenticationToken result = new JaasAuthenticationToken(request.getPrincipal(),
request.getCredentials(), new ArrayList<>(authorities), loginContext);
Object principal = request.getPrincipal();
Assert.notNull(principal, "The principal cannot be null");
JaasAuthenticationToken result = new JaasAuthenticationToken(principal, request.getCredentials(),
new ArrayList<>(authorities), loginContext);
// Publish the success event
publishSuccessEvent(result);
// we're done, return the token.

View File

@ -0,0 +1,56 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authentication.ott;
import java.io.Serial;
import java.util.Collection;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
/**
* The result of a successful one-time-token authentication
*
* @author Josh Cummings
* @since 7.0
*/
public class OneTimeTokenAuthentication extends AbstractAuthenticationToken {
@Serial
private static final long serialVersionUID = 1195893764725073959L;
private final Object principal;
public OneTimeTokenAuthentication(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
setAuthenticated(true);
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public @Nullable Object getCredentials() {
return null;
}
}

View File

@ -56,8 +56,7 @@ public final class OneTimeTokenAuthenticationProvider implements AuthenticationP
}
try {
UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user,
user.getAuthorities());
OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, user.getAuthorities());
authenticated.setDetails(otpAuthenticationToken.getDetails());
return authenticated;
}

View File

@ -40,6 +40,10 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
private @Nullable String tokenValue;
/**
* @deprecated Please use constructor that takes a {@link String} instead
*/
@Deprecated(forRemoval = true, since = "7.0")
public OneTimeTokenAuthenticationToken(@Nullable Object principal, String tokenValue) {
super(Collections.emptyList());
this.tokenValue = tokenValue;
@ -50,6 +54,10 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
this(null, tokenValue);
}
/**
* @deprecated Please use {@link OneTimeTokenAuthentication} instead
*/
@Deprecated(forRemoval = true, since = "7.0")
public OneTimeTokenAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
@ -60,9 +68,11 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
* Creates an unauthenticated token
* @param tokenValue the one-time token value
* @return an unauthenticated {@link OneTimeTokenAuthenticationToken}
* @deprecated Please use constructor that takes a {@link String} instead
*/
public static OneTimeTokenAuthenticationToken unauthenticated(String tokenValue) {
return new OneTimeTokenAuthenticationToken(null, tokenValue);
@Deprecated(forRemoval = true, since = "7.0")
public static OneTimeTokenAuthenticationToken unauthenticated(@Nullable String tokenValue) {
return new OneTimeTokenAuthenticationToken(null, (tokenValue != null) ? tokenValue : "");
}
/**
@ -70,7 +80,9 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
* @param principal the principal
* @param tokenValue the one-time token value
* @return an unauthenticated {@link OneTimeTokenAuthenticationToken}
* @deprecated Please use constructor that takes a {@link String} instead
*/
@Deprecated(forRemoval = true, since = "7.0")
public static OneTimeTokenAuthenticationToken unauthenticated(Object principal, String tokenValue) {
return new OneTimeTokenAuthenticationToken(principal, tokenValue);
}
@ -80,7 +92,9 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken
* @param principal the principal
* @param authorities the principal authorities
* @return an authenticated {@link OneTimeTokenAuthenticationToken}
* @deprecated Please use {@link OneTimeTokenAuthentication} instead
*/
@Deprecated(forRemoval = true, since = "7.0")
public static OneTimeTokenAuthenticationToken authenticated(Object principal,
Collection<? extends GrantedAuthority> authorities) {
return new OneTimeTokenAuthenticationToken(principal, authorities);

View File

@ -22,6 +22,7 @@ import reactor.core.publisher.Mono;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ott.InvalidOneTimeTokenException;
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication;
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
@ -59,10 +60,9 @@ public final class OneTimeTokenReactiveAuthenticationManager implements Reactive
.map(onSuccess(otpAuthenticationToken));
}
private Function<UserDetails, OneTimeTokenAuthenticationToken> onSuccess(OneTimeTokenAuthenticationToken token) {
private Function<UserDetails, OneTimeTokenAuthentication> onSuccess(OneTimeTokenAuthenticationToken token) {
return (user) -> {
OneTimeTokenAuthenticationToken authenticated = OneTimeTokenAuthenticationToken.authenticated(user,
user.getAuthorities());
OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, user.getAuthorities());
authenticated.setDetails(token.getDetails());
return authenticated;
};

View File

@ -18,6 +18,8 @@ package org.springframework.security.authorization;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
@ -111,7 +113,7 @@ public final class AuthenticatedAuthorizationManager<T> implements Authorization
* @return an {@link AuthorizationDecision}
*/
@Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, T object) {
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, T object) {
boolean granted = this.authorizationStrategy.isGranted(authentication.get());
return new AuthorizationDecision(granted);
}

View File

@ -19,6 +19,8 @@ package org.springframework.security.authorization;
import java.util.Collection;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.core.Authentication;
@ -55,7 +57,8 @@ public final class AuthoritiesAuthorizationManager implements AuthorizationManag
* @return an {@link AuthorityAuthorizationDecision}
*/
@Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, Collection<String> authorities) {
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
Collection<String> authorities) {
boolean granted = isGranted(authentication.get(), authorities);
return new AuthorityAuthorizationDecision(granted, AuthorityUtils.createAuthorityList(authorities));
}

View File

@ -19,6 +19,8 @@ package org.springframework.security.authorization;
import java.util.Set;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.core.Authentication;
@ -137,7 +139,7 @@ public final class AuthorityAuthorizationManager<T> implements AuthorizationMana
* {@inheritDoc}
*/
@Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, T object) {
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, T object) {
return this.delegate.authorize(authentication, this.authorities);
}

View File

@ -18,6 +18,8 @@ package org.springframework.security.authorization;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
import org.springframework.security.core.Authentication;
@ -46,6 +48,7 @@ public interface AuthorizationEventPublisher {
* @param <T> the secured object's type
* @since 6.4
*/
<T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object, AuthorizationResult result);
<T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
@Nullable AuthorizationResult result);
}

View File

@ -39,7 +39,7 @@ public interface AuthorizationManager<T extends @Nullable Object> {
* @param object the {@link T} object to check
* @throws AccessDeniedException if access is not granted
*/
default void verify(Supplier<Authentication> authentication, T object) {
default void verify(Supplier<? extends @Nullable Authentication> authentication, T object) {
AuthorizationResult result = authorize(authentication, object);
if (result != null && !result.isGranted()) {
throw new AuthorizationDeniedException("Access Denied", result);
@ -54,6 +54,6 @@ public interface AuthorizationManager<T extends @Nullable Object> {
* @return an {@link AuthorizationResult}
* @since 6.4
*/
@Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, T object);
@Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, T object);
}

View File

@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
/**
@ -58,7 +60,8 @@ public final class AuthorizationManagers {
@SafeVarargs
public static <T> AuthorizationManager<T> anyOf(AuthorizationDecision allAbstainDefaultDecision,
AuthorizationManager<T>... managers) {
return (AuthorizationManagerCheckAdapter<T>) (authentication, object) -> {
return (AuthorizationManagerCheckAdapter<T>) (Supplier<? extends @Nullable Authentication> authentication,
T object) -> {
List<AuthorizationResult> results = new ArrayList<>();
for (AuthorizationManager<T> manager : managers) {
AuthorizationResult result = manager.authorize(authentication, object);
@ -104,7 +107,8 @@ public final class AuthorizationManagers {
@SafeVarargs
public static <T> AuthorizationManager<T> allOf(AuthorizationDecision allAbstainDefaultDecision,
AuthorizationManager<T>... managers) {
return (AuthorizationManagerCheckAdapter<T>) (authentication, object) -> {
return (AuthorizationManagerCheckAdapter<T>) (Supplier<? extends @Nullable Authentication> authentication,
T object) -> {
List<AuthorizationResult> results = new ArrayList<>();
for (AuthorizationManager<T> manager : managers) {
AuthorizationResult result = manager.authorize(authentication, object);
@ -133,7 +137,7 @@ public final class AuthorizationManagers {
* @since 6.3
*/
public static <T> AuthorizationManager<T> not(AuthorizationManager<T> manager) {
return (authentication, object) -> {
return (Supplier<? extends @Nullable Authentication> authentication, T object) -> {
AuthorizationResult result = manager.authorize(authentication, object);
if (result == null) {
return null;
@ -182,7 +186,7 @@ public final class AuthorizationManagers {
private interface AuthorizationManagerCheckAdapter<T> extends AuthorizationManager<T> {
@Override
AuthorizationResult authorize(Supplier<Authentication> authentication, T object);
AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, T object);
}

View File

@ -63,9 +63,10 @@ public final class ObservationAuthorizationManager<T>
}
@Override
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, T object) {
public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
T object) {
AuthorizationObservationContext<T> context = new AuthorizationObservationContext<>(object);
Supplier<Authentication> wrapped = () -> {
Supplier<@Nullable Authentication> wrapped = () -> {
context.setAuthentication(authentication.get());
return context.getAuthentication();
};

View File

@ -18,6 +18,8 @@ package org.springframework.security.authorization;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@ -44,7 +46,7 @@ public final class SingleResultAuthorizationManager<C> implements AuthorizationM
}
@Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, C object) {
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, C object) {
if (!(this.result instanceof AuthorizationDecision)) {
throw new IllegalArgumentException("result should be AuthorizationDecision");
}

View File

@ -19,6 +19,8 @@ package org.springframework.security.authorization;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
@ -57,7 +59,7 @@ public final class SpringAuthorizationEventPublisher implements AuthorizationEve
*/
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationResult result) {
@Nullable AuthorizationResult result) {
if (result == null) {
return;
}

View File

@ -83,7 +83,7 @@ public final class Jsr250AuthorizationManager implements AuthorizationManager<Me
* {@inheritDoc}
*/
@Override
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication,
public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
MethodInvocation methodInvocation) {
AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(methodInvocation);
return delegate.authorize(authentication, methodInvocation);
@ -104,8 +104,9 @@ public final class Jsr250AuthorizationManager implements AuthorizationManager<Me
return SingleResultAuthorizationManager.permitAll();
}
if (annotation instanceof RolesAllowed rolesAllowed) {
return (a, o) -> Jsr250AuthorizationManager.this.authoritiesAuthorizationManager.authorize(a,
getAllowedRolesWithPrefix(rolesAllowed));
return (Supplier<? extends @Nullable Authentication> a,
MethodInvocation o) -> Jsr250AuthorizationManager.this.authoritiesAuthorizationManager
.authorize(a, getAllowedRolesWithPrefix(rolesAllowed));
}
return NULL_MANAGER;
}

View File

@ -19,6 +19,7 @@ package org.springframework.security.authorization.method;
import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.jspecify.annotations.Nullable;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
@ -73,7 +74,9 @@ public final class MethodExpressionAuthorizationManager implements Authorization
* expression
*/
@Override
public AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation context) {
@SuppressWarnings("NullAway") // FIXME: Dataflow analysis limitation
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
MethodInvocation context) {
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, context);
boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
return new ExpressionAuthorizationDecision(granted, this.expression);

View File

@ -18,6 +18,8 @@ package org.springframework.security.authorization.method;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
@ -32,7 +34,7 @@ final class NoOpAuthorizationEventPublisher implements AuthorizationEventPublish
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationResult result) {
@Nullable AuthorizationResult result) {
}

View File

@ -86,7 +86,8 @@ public final class PostAuthorizeAuthorizationManager
* {@link PostAuthorize} annotation is not present
*/
@Override
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocationResult mi) {
public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
MethodInvocationResult mi) {
ExpressionAttribute attribute = this.registry.getAttribute(mi.getMethodInvocation());
if (attribute == null) {
return null;

View File

@ -78,7 +78,8 @@ public final class PreAuthorizeAuthorizationManager
* {@link PreAuthorize} annotation is not present
*/
@Override
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation mi) {
public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
MethodInvocation mi) {
ExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == null) {
return null;

View File

@ -68,7 +68,8 @@ public final class SecuredAuthorizationManager implements AuthorizationManager<M
}
@Override
public @Nullable AuthorizationResult authorize(Supplier<Authentication> authentication, MethodInvocation mi) {
public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
MethodInvocation mi) {
Set<String> authorities = getAuthorities(mi);
return authorities.isEmpty() ? null
: this.authoritiesAuthorizationManager.authorize(authentication, authorities);

View File

@ -76,7 +76,7 @@ public abstract class AuthenticationException extends RuntimeException {
* authentication attempt
* @since 6.5
*/
public void setAuthenticationRequest(Authentication authenticationRequest) {
public void setAuthenticationRequest(@Nullable Authentication authenticationRequest) {
Assert.notNull(authenticationRequest, "authenticationRequest cannot be null");
this.authenticationRequest = authenticationRequest;
}

View File

@ -31,8 +31,6 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public final class SecurityAnnotationScanners {
private static final Map<Class<? extends Annotation>, SecurityAnnotationScanner<? extends Annotation>> uniqueScanners = new ConcurrentHashMap<>();
private static final Map<Class<? extends Annotation>, SecurityAnnotationScanner<? extends Annotation>> uniqueTemplateScanners = new ConcurrentHashMap<>();
private static final Map<List<Class<? extends Annotation>>, SecurityAnnotationScanner<? extends Annotation>> uniqueTypesScanners = new ConcurrentHashMap<>();
@ -48,8 +46,7 @@ public final class SecurityAnnotationScanners {
* @return the default {@link SecurityAnnotationScanner}
*/
public static <A extends Annotation> SecurityAnnotationScanner<A> requireUnique(Class<A> type) {
return (SecurityAnnotationScanner<A>) uniqueScanners.computeIfAbsent(type,
(t) -> new UniqueSecurityAnnotationScanner<>(type));
return requireUnique(type, new AnnotationTemplateExpressionDefaults());
}
/**
@ -68,9 +65,6 @@ public final class SecurityAnnotationScanners {
*/
public static <A extends Annotation> SecurityAnnotationScanner<A> requireUnique(Class<A> type,
AnnotationTemplateExpressionDefaults templateDefaults) {
if (templateDefaults == null) {
return requireUnique(type);
}
return (SecurityAnnotationScanner<A>) uniqueTemplateScanners.computeIfAbsent(type,
(t) -> new ExpressionTemplateSecurityAnnotationScanner<>(t, templateDefaults));
}

View File

@ -26,6 +26,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@ -69,8 +70,7 @@ public class OneTimeTokenAuthenticationProviderTests {
.willReturn(new User(USERNAME, PASSWORD, List.of()));
OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN);
OneTimeTokenAuthenticationToken authentication = (OneTimeTokenAuthenticationToken) this.provider
.authenticate(token);
Authentication authentication = this.provider.authenticate(token);
User user = (User) authentication.getPrincipal();
assertThat(authentication.isAuthenticated()).isTrue();

View File

@ -88,13 +88,12 @@ public class OneTimeTokenReactiveAuthenticationManagerTests {
this.authenticationManager = new OneTimeTokenReactiveAuthenticationManager(oneTimeTokenService,
userDetailsService);
Authentication auth = this.authenticationManager
Authentication token = this.authenticationManager
.authenticate(OneTimeTokenAuthenticationToken.unauthenticated(TOKEN))
.block();
OneTimeTokenAuthenticationToken token = (OneTimeTokenAuthenticationToken) auth;
UserDetails user = (UserDetails) token.getPrincipal();
Collection<GrantedAuthority> authorities = token.getAuthorities();
Collection<? extends GrantedAuthority> authorities = token.getAuthorities();
assertThat(user).isNotNull();
assertThat(user.getUsername()).isEqualTo(USERNAME);

View File

@ -1,3 +1,7 @@
plugins {
id 'security-nullability'
}
apply plugin: 'io.spring.convention.spring-module'
dependencies {

View File

@ -0,0 +1,23 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* AOT integration for Spring Security's Data integration.
*/
@NullMarked
package org.springframework.security.data.aot.hint;
import org.jspecify.annotations.NullMarked;

View File

@ -16,6 +16,8 @@
package org.springframework.security.data.repository.query;
import org.jspecify.annotations.Nullable;
import org.springframework.data.spel.spi.EvaluationContextExtension;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
@ -93,7 +95,7 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private Authentication authentication;
private @Nullable Authentication authentication;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
@ -114,7 +116,7 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
* Creates a new instance that always uses the same {@link Authentication} object.
* @param authentication the {@link Authentication} to use
*/
public SecurityEvaluationContextExtension(Authentication authentication) {
public SecurityEvaluationContextExtension(@Nullable Authentication authentication) {
this.authentication = authentication;
}
@ -146,7 +148,7 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
private Authentication getAuthentication() {
private @Nullable Authentication getAuthentication() {
if (this.authentication != null) {
return this.authentication;
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Spring Security extensions for Spring Data queries.
*/
@NullMarked
package org.springframework.security.data.repository.query;
import org.jspecify.annotations.NullMarked;

View File

@ -242,3 +242,72 @@ It matches the requests in order by the `securityMatcher` definition.
In this case, that means that, if the URL path starts with `/api`, Spring Security uses `apiHttpSecurity`.
If the URL does not start with `/api`, Spring Security defaults to `webHttpSecurity`, which has an implied `securityMatcher` that matches any request.
[[modular-serverhttpsecurity-configuration]]
== Modular ServerHttpSecurity Configuration
Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it within the `SecurityWebFilterChain` Bean declaration.
However, there are times that users may want to modularize the configuration.
This can be done using:
* xref:#serverhttpsecurity-customizer-bean[Customizer<ServerHttpSecurity> Beans]
* xref:#top-level-customizer-bean[Top Level ServerHttpSecurity Customizer Beans]
// FIXME: this needs to link to appropriate spot
// NOTE: If you are using Spring Security's xref:servlet/configuration/kotlin.adoc[], then you can also expose `*Dsl -> Unit` Beans as outlined in xref:./kotlin.adoc#modular-httpsecuritydsl-configuration[Modular HttpSecurityDsl Configuration].
[[serverhttpsecurity-customizer-bean]]
=== Customizer<ServerHttpSecurity> Beans
If you would like to modularize your security configuration you can place logic in a `Customizer<ServerHttpSecurity>` Bean.
For example, the following configuration will ensure all `ServerHttpSecurity` instances are configured to:
include-code::./ServerHttpSecurityCustomizerBeanConfiguration[tag=httpSecurityCustomizer,indent=0]
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
<2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
[[top-level-customizer-bean]]
=== Top Level ServerHttpSecurity Customizer Beans
If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level `HttpSecurity` `Customizer` Beans.
A top level `HttpSecurity` `Customizer` type can be summarized as any `Customizer<T>` that matches `public HttpSecurity.*(Customizer<T>)`.
This translates to any `Customizer<T>` that is a single argument to a public method on javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity[].
A few examples can help to clarify.
If `Customizer<ContentTypeOptionsConfig>` is published as a Bean, it will not be be automatically applied because it is an argument to javadoc:org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#contentTypeOptions(org.springframework.security.config.Customizer)[] which is not a method defined on `HttpSecurity`.
However, if `Customizer<HeadersConfigurer<HttpSecurity>>` is published as a Bean, it will be automatically applied because it is an argument to javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity#headers(org.springframework.security.config.Customizer)[].
For example, the following configuration will ensure that the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] is set to `object-src 'none'`:
include-code::./TopLevelCustomizerBeanConfiguration[tag=headersCustomizer,indent=0]
[[customizer-bean-ordering]]
=== Customizer Bean Ordering
First each xref:#httpsecurity-customizer-bean[Customizer<HttpSecurity> Bean] is applied using https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html#orderedStream()[ObjectProvider#orderedStream()].
This means that if there are multiple `Customizer<HttpSecurity>` Beans, the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[@Order] annotation can be added to the Bean definitions to control the ordering.
Next every xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans] type is looked up and each is is applied using `ObjectProvider#orderedStream()`.
If there is are two `Customizer<HeadersConfigurer<HttpSecurity>>` beans and two `Customizer<HttpsRedirectConfigurer<HttpSecurity>>` instances, the order that each `Customizer` type is invoked is undefined.
However, the order that each instance of `Customizer<HttpsRedirectConfigurer<HttpSecurity>>` is defined by `ObjectProvider#orderedStream()` and can be controlled using `@Order` on the Bean the definitions.
Finally, the `HttpSecurity` Bean is injected as a Bean.
All `Customizer` instances are applied before the `HttpSecurity` Bean is created.
This allows overriding the customizations provided by the `Customizer` Beans.
You can find an example below that illustrates the ordering:
include-code::./CustomizerBeanOrderingConfiguration[tag=sample,indent=0]
<1> First all `Customizer<HttpSecurity>` instances are applied.
The `adminAuthorization` Bean has the highest `@Order` so it is applied first.
If there are no `@Order` annotations on the `Customizer<HttpSecurity>` Beans or the `@Order` annotations had the same value, then the order that the `Customizer<HttpSecurity>` instances are applied is undefined.
<2> The `userAuthorization` is applied next due to being an instance of `Customizer<HttpSecurity>`
<3> The order that the `Customizer` types are undefined.
In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` are undefined.
If `@Order(Ordered.HIGHEST_PRECEDENCE)` was added to `contentTypeOptions`, then we would know that `contentTypeOptions` is before `contentSecurityPolicy` (they are the same type), but we do not know if `httpsRedirect` is before or after the `Customizer<HeadersConfigurer<HttpSecurity>>` Beans.
<4> After all of the `Customizer` Beans are applied, the `HttpSecurity` is passed in as a Bean.

View File

@ -546,6 +546,14 @@ open class BankService {
The result is that the above method will only return the `Account` if its `owner` attribute matches the logged-in user's `name`.
If not, Spring Security will throw an `AccessDeniedException` and return a 403 status code.
[NOTE]
=====
Note that `@PostAuthorize` is not recommended for classes that perform database writes since that typically means that a database change was made before the security invariants were checked.
A common example of doing this is if you have `@Transactional` and `@PostAuthorize` on the same method.
Instead, read the value first, using `@PostAuthorize` on the read, and then perform the database write, should that read is authorized.
If you must do something like this, you can <<changing-the-order, ensure that `@EnableTransactionManagement` comes before `@EnableMethodSecurity`>>.
=====
[[use-prefilter]]
=== Filtering Method Parameters with `@PreFilter`
@ -1797,39 +1805,7 @@ As already noted, there is a Spring AOP method interceptor for each annotation,
Namely, the `@PreFilter` method interceptor's order is 100, ``@PreAuthorize``'s is 200, and so on.
The reason this is important to note is that there are other AOP-based annotations like `@EnableTransactionManagement` that have an order of `Integer.MAX_VALUE`.
In other words, they are located at the end of the advisor chain by default.
At times, it can be valuable to have other advice execute before Spring Security.
For example, if you have a method annotated with `@Transactional` and `@PostAuthorize`, you might want the transaction to still be open when `@PostAuthorize` runs so that an `AccessDeniedException` will cause a rollback.
To get `@EnableTransactionManagement` to open a transaction before method authorization advice runs, you can set ``@EnableTransactionManagement``'s order like so:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@EnableTransactionManagement(order = 0)
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@EnableTransactionManagement(order = 0)
----
Xml::
+
[source,xml,role="secondary"]
----
<tx:annotation-driven ref="txManager" order="0"/>
----
======
Since the earliest method interceptor (`@PreFilter`) is set to an order of 100, a setting of zero means that the transaction advice will run before all Spring Security advice.
You can use the `offset` parameter on `@EnableMethodSecurity` to move all interceptors en masse to provide their advice earlier or later in a method invocation.
[[authorization-expressions]]
== Expressing Authorization with SpEL

View File

@ -664,6 +664,75 @@ class Config {
----
======
[[modular-httpsecurity-configuration]]
== Modular HttpSecurity Configuration
Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it in a single `SecurityFilterChain` instance.
However, there are times that users may want to modularize the configuration.
This can be done using:
* xref:#httpsecurity-customizer-bean[Customizer<HttpSecurity> Beans]
* xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans]
NOTE: If you are using Spring Security's xref:servlet/configuration/kotlin.adoc[], then you can also expose `*Dsl -> Unit` Beans as outlined in xref:./kotlin.adoc#modular-httpsecuritydsl-configuration[Modular HttpSecurityDsl Configuration].
[[httpsecurity-customizer-bean]]
=== Customizer<HttpSecurity> Beans
If you would like to modularize your security configuration you can place logic in a `Customizer<HttpSecurity>` Bean.
For example, the following configuration will ensure all `HttpSecurity` instances are configured to:
include-code::./HttpSecurityCustomizerBeanConfiguration[tag=httpSecurityCustomizer,indent=0]
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
<2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
[[top-level-customizer-bean]]
=== Top Level HttpSecurity Customizer Beans
If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level `HttpSecurity` `Customizer` Beans.
A top level `HttpSecurity` `Customizer` type can be summarized as any `Customizer<T>` that matches `public HttpSecurity.*(Customizer<T>)`.
This translates to any `Customizer<T>` that is a single argument to a public method on javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity[].
A few examples can help to clarify.
If `Customizer<ContentTypeOptionsConfig>` is published as a Bean, it will not be be automatically applied because it is an argument to javadoc:org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#contentTypeOptions(org.springframework.security.config.Customizer)[] which is not a method defined on `HttpSecurity`.
However, if `Customizer<HeadersConfigurer<HttpSecurity>>` is published as a Bean, it will be automatically applied because it is an argument to javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity#headers(org.springframework.security.config.Customizer)[].
For example, the following configuration will ensure that the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] is set to `object-src 'none'`:
include-code::./TopLevelCustomizerBeanConfiguration[tag=headersCustomizer,indent=0]
[[customizer-bean-ordering]]
=== Customizer Bean Ordering
First each xref:#httpsecurity-customizer-bean[Customizer<HttpSecurity> Bean] is applied using https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html#orderedStream()[ObjectProvider#orderedStream()].
This means that if there are multiple `Customizer<HttpSecurity>` Beans, the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[@Order] annotation can be added to the Bean definitions to control the ordering.
Next every xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans] type is looked up and each is is applied using `ObjectProvider#orderedStream()`.
If there is are two `Customizer<HeadersConfigurer<HttpSecurity>>` beans and two `Customizer<HttpsRedirectConfigurer<HttpSecurity>>` instances, the order that each `Customizer` type is invoked is undefined.
However, the order that each instance of `Customizer<HttpsRedirectConfigurer<HttpSecurity>>` is defined by `ObjectProvider#orderedStream()` and can be controlled using `@Order` on the Bean the definitions.
Finally, the `HttpSecurity` Bean is injected as a Bean.
All `Customizer` instances are applied before the `HttpSecurity` Bean is created.
This allows overriding the customizations provided by the `Customizer` Beans.
You can find an example below that illustrates the ordering:
include-code::./CustomizerBeanOrderingConfiguration[tag=sample,indent=0]
<1> First all `Customizer<HttpSecurity>` instances are applied.
The `adminAuthorization` Bean has the highest `@Order` so it is applied first.
If there are no `@Order` annotations on the `Customizer<HttpSecurity>` Beans or the `@Order` annotations had the same value, then the order that the `Customizer<HttpSecurity>` instances are applied is undefined.
<2> The `userAuthorization` is applied next due to being an instance of `Customizer<HttpSecurity>`
<3> The order that the `Customizer` types are undefined.
In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` are undefined.
If `@Order(Ordered.HIGHEST_PRECEDENCE)` was added to `contentTypeOptions`, then we would know that `contentTypeOptions` is before `contentSecurityPolicy` (they are the same type), but we do not know if `httpsRedirect` is before or after the `Customizer<HeadersConfigurer<HttpSecurity>>` Beans.
<4> After all of the `Customizer` Beans are applied, the `HttpSecurity` is passed in as a Bean.
[[post-processing-configured-objects]]
== Post Processing Configured Objects

View File

@ -346,3 +346,76 @@ class BankingSecurityConfig {
This configuration will handle requests not covered by the other filter chains and will be processed last (no `@Order` defaults to last).
Requests that match `/`, `/user-login`, `/user-logout`, `/notices`, `/contact` and `/register` allow access without authentication.
Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains.
[[modular-httpsecuritydsl-configuration]]
== Modular HttpSecurityDsl Configuration
Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it in a single `SecurityFilterChain` instance.
However, there are times that users may want to modularize the configuration.
This can be done using:
* xref:#httpsecuritydsl-bean[HttpSecurityDsl.() -> Unit Beans]
* xref:#top-level-dsl-bean[Top Level Security Dsl Beans]
NOTE: Since the Spring Security Kotlin Dsl (`HttpSecurityDsl`) uses `HttpSecurity`, all of the Java xref:./kotlin.adoc#modular-bean-configuration[Modular Bean Customization] is applied before xref:#modular-httpsecuritydsl-configuration[Modular HttpSecurity Configuration].
[[httpsecuritydsl-bean]]
=== HttpSecurityDsl.() -> Unit Beans
If you would like to modularize your security configuration you can place logic in a `HttpSecurityDsl.() -> Unit` Bean.
For example, the following configuration will ensure all `HttpSecurityDsl` instances are configured to:
include-code::./HttpSecurityDslBeanConfiguration[tag=httpSecurityDslBean,indent=0]
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
<2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
[[top-level-dsl-bean]]
=== Top Level Security Dsl Beans
If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level Security Dsl Beans.
A top level Security Dsl can be summarized as any class Dsl class that matches `public HttpSecurityDsl.*(<Dsl>)`.
This translates to any Security Dsl that is a single argument to a public method on `HttpSecurityDsl`.
A few examples can help to clarify.
If `ContentTypeOptionsDsl.() -> Unit` is published as a Bean, it will not be be automatically applied because it is an argument to `HeadersDsl#contentTypeOptions(ContentTypeOptionsDsl.() -> Unit)` and is not an argument to a method defined on `HttpSecurityDsl`.
However, if `HeadersDsl.() -> Unit` is published as a Bean, it will be automatically applied because it is an argument to `HttpSecurityDsl.headers(HeadersDsl.() -> Unit)`.
For example, the following configuration ensure all `HttpSecurityDsl` instances are configured to:
include-code::./TopLevelDslBeanConfiguration[tag=headersSecurity,indent=0]
<1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
[[dsl-bean-ordering]]
=== Dsl Bean Ordering
First, all xref:servlet/configuration/java.adoc#modular-httpsecurity-configuration[Modular HttpSecurity Configuration] is applied since the Kotlin Dsl uses an `HttpSecurity` Bean.
Second, each xref:#httpsecuritydsl-bean[HttpSecurityDsl.() -> Unit Beans] is applied using https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html#orderedStream()[ObjectProvider#orderedStream()].
This means that if there are multiple `HttpSecurity.() -> Unit` Beans, the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[@Order] annotation can be added to the Bean definitions to control the ordering.
Next, every xref:#top-level-dsl-bean[Top Level Security Dsl Beans] type is looked up and each is is applied using `ObjectProvider#orderedStream()`.
If there is are differt types of top level security Beans (.e.g. `HeadersDsl.() -> Unit` and `HttpsRedirectDsl.() -> Unit`), then the order that each Dsl type is invoked is undefined.
However, the order that each instance of of the same top level security Bean type is defined by `ObjectProvider#orderedStream()` and can be controlled using `@Order` on the Bean the definitions.
Finally, the `HttpSecurityDsl` Bean is injected as a Bean.
All `*Dsl.() -> Unit` Beans are applied before the `HttpSecurityDsl` Bean is created.
This allows overriding the customizations provided by the `*Dsl.() -> Unit` Beans.
You can find an example below that illustrates the ordering:
include-code::./DslBeanOrderingConfiguration[tag=sample,indent=0]
<1> All xref:servlet/configuration/java.adoc#modular-httpsecurity-configuration[Modular HttpSecurity Configuration] is applied since the Kotlin Dsl uses an `HttpSecurity` Bean.
<2> All `HttpSecurity.() -> Unit` instances are applied.
The `adminAuthorization` Bean has the highest `@Order` so it is applied first.
If there are no `@Order` annotations on the `HttpSecurity.() -> Unit` Beans or the `@Order` annotations had the same value, then the order that the `HttpSecurity.() -> Unit` instances are applied is undefined.
<3> The `userAuthorization` is applied next due to being an instance of `HttpSecurity.() -> Unit`
<4> The order that the `*Dsl.() -> Unit` types are undefined.
In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` are undefined.
If `@Order(Ordered.HIGHEST_PRECEDENCE)` was added to `contentTypeOptions`, then we would know that `contentTypeOptions` is before `contentSecurityPolicy` (they are the same type), but we do not know if `httpsRedirect` is before or after the `HeadersDsl.() -> Unit` Beans.
<5> After all of the `*Dsl.() -> Unit` Beans are applied, the `HttpSecurityDsl` is passed in as a Bean.

View File

@ -4,36 +4,7 @@
This section demonstrates how to use Spring Security's Test support to test method-based security.
We first introduce a `MessageService` that requires the user to be authenticated to be able to access it:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
public class HelloMessageService implements MessageService {
@PreAuthorize("authenticated")
public String getMessage() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
return "Hello " + authentication;
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
class HelloMessageService : MessageService {
@PreAuthorize("authenticated")
fun getMessage(): String {
val authentication: Authentication = SecurityContextHolder.getContext().authentication
return "Hello $authentication"
}
}
----
======
include-code::./HelloMessageService[tag=authenticated,indent=0]
The result of `getMessage` is a `String` that says "`Hello`" to the current Spring Security `Authentication`.
The following listing shows example output:
@ -48,30 +19,8 @@ Hello org.springframework.security.authentication.UsernamePasswordAuthentication
Before we can use the Spring Security test support, we must perform some setup:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@ExtendWith(SpringExtension.class) // <1>
@ContextConfiguration // <2>
public class WithMockUserTests {
// ...
}
----
include-code::./WithMockUserTests[tag=setup,indent=0]
Kotlin::
+
[source,kotlin,role="secondary"]
----
@ExtendWith(SpringExtension.class)
@ContextConfiguration
class WithMockUserTests {
// ...
}
----
======
<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].
@ -87,28 +36,7 @@ If you need only Spring Security related support, you can replace `@ContextConfi
Remember, we added the `@PreAuthorize` annotation to our `HelloMessageService`, so it requires an authenticated user to invoke it.
If we run the tests, we expect the following test will pass:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void getMessageUnauthenticated() {
messageService.getMessage();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Test(expected = AuthenticationCredentialsNotFoundException::class)
fun getMessageUnauthenticated() {
messageService.getMessage()
}
----
======
include-code::./WithMockUserSampleTests[tag=snippet,indent=0]
[[test-method-withmockuser]]
== @WithMockUser
@ -117,32 +45,7 @@ The question is "How could we most easily run the test as a specific user?"
The answer is to use `@WithMockUser`.
The following test will be run as a user with the username "user", the password "password", and the roles "ROLE_USER".
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Test
@WithMockUser
public void getMessageWithMockUser() {
String message = messageService.getMessage();
...
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Test
@WithMockUser
fun getMessageWithMockUser() {
val message: String = messageService.getMessage()
// ...
}
----
======
include-code::./WithMockUserTests[tag=mock-user,indent=0]
Specifically the following is true:
@ -157,168 +60,28 @@ The preceding example is handy, because it lets us use a lot of defaults.
What if we wanted to run the test with a different username?
The following test would run with a username of `customUser` (again, the user does not need to actually exist):
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Test
@WithMockUser("customUsername")
public void getMessageWithMockUserCustomUsername() {
String message = messageService.getMessage();
...
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Test
@WithMockUser("customUsername")
fun getMessageWithMockUserCustomUsername() {
val message: String = messageService.getMessage()
// ...
}
----
======
include-code::./WithMockUserTests[tag=custom-user,indent=0]
We can also easily customize the roles.
For example, the following test is invoked with a username of `admin` and roles of `ROLE_USER` and `ROLE_ADMIN`.
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public void getMessageWithMockUserCustomUser() {
String message = messageService.getMessage();
...
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Test
@WithMockUser(username="admin",roles=["USER","ADMIN"])
fun getMessageWithMockUserCustomUser() {
val message: String = messageService.getMessage()
// ...
}
----
======
include-code::./WithMockUserTests[tag=custom-roles,indent=0]
If we do not want the value to automatically be prefixed with `ROLE_` we can use the `authorities` attribute.
For example, the following test is invoked with a username of `admin` and the `USER` and `ADMIN` authorities.
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Test
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
public void getMessageWithMockUserCustomAuthorities() {
String message = messageService.getMessage();
...
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Test
@WithMockUser(username = "admin", authorities = ["ADMIN", "USER"])
fun getMessageWithMockUserCustomUsername() {
val message: String = messageService.getMessage()
// ...
}
----
======
include-code::./WithMockUserTests[tag=custom-authorities,indent=0]
It can be a bit tedious to place the annotation on every test method.
Instead, we can place the annotation at the class level. Then every test uses the specified user.
The following example runs every test with a user whose username is `admin`, whose password is `password`, and who has the `ROLE_USER` and `ROLE_ADMIN` roles:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public class WithMockUserTests {
// ...
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WithMockUser(username="admin",roles=["USER","ADMIN"])
class WithMockUserTests {
// ...
}
----
======
include-code::./WithMockUserClassTests[tag=snippet,indent=0]
If you use JUnit 5's `@Nested` test support, you can also place the annotation on the enclosing class to apply to all nested classes.
The following example runs every test with a user whose username is `admin`, whose password is `password`, and who has the `ROLE_USER` and `ROLE_ADMIN` roles for both test methods.
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public class WithMockUserTests {
@Nested
public class TestSuite1 {
// ... all test methods use admin user
}
@Nested
public class TestSuite2 {
// ... all test methods use admin user
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@ExtendWith(SpringExtension::class)
@ContextConfiguration
@WithMockUser(username = "admin", roles = ["USER", "ADMIN"])
class WithMockUserTests {
@Nested
inner class TestSuite1 { // ... all test methods use admin user
}
@Nested
inner class TestSuite2 { // ... all test methods use admin user
}
}
----
======
include-code::./WithMockUserNestedTests[tag=snippet,indent=0]
By default, the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
This is the equivalent of happening before JUnit's `@Before`.
@ -337,55 +100,7 @@ Using `@WithAnonymousUser` allows running as an anonymous user.
This is especially convenient when you wish to run most of your tests with a specific user but want to run a few tests as an anonymous user.
The following example runs `withMockUser1` and `withMockUser2` by using <<test-method-withmockuser,@WithMockUser>> and `anonymous` as an anonymous user:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@ExtendWith(SpringExtension.class)
@WithMockUser
public class WithUserClassLevelAuthenticationTests {
@Test
public void withMockUser1() {
}
@Test
public void withMockUser2() {
}
@Test
@WithAnonymousUser
public void anonymous() throws Exception {
// override default to run as anonymous user
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@ExtendWith(SpringExtension.class)
@WithMockUser
class WithUserClassLevelAuthenticationTests {
@Test
fun withMockUser1() {
}
@Test
fun withMockUser2() {
}
@Test
@WithAnonymousUser
fun anonymous() {
// override default to run as anonymous user
}
}
----
======
include-code::./WithUserClassLevelAuthenticationTests[tag=snippet,indent=0]
By default, the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
This is the equivalent of happening before JUnit's `@Before`.
@ -410,92 +125,17 @@ That is exactly what `@WithUserDetails` does.
Assuming we have a `UserDetailsService` exposed as a bean, the following test is invoked with an `Authentication` of type `UsernamePasswordAuthenticationToken` and a principal that is returned from the `UserDetailsService` with the username of `user`:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Test
@WithUserDetails
public void getMessageWithUserDetails() {
String message = messageService.getMessage();
...
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Test
@WithUserDetails
fun getMessageWithUserDetails() {
val message: String = messageService.getMessage()
// ...
}
----
======
include-code::./WithUserDetailsTests[tag=user-details,indent=0]
We can also customize the username used to lookup the user from our `UserDetailsService`.
For example, this test can be run with a principal that is returned from the `UserDetailsService` with the username of `customUsername`:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Test
@WithUserDetails("customUsername")
public void getMessageWithUserDetailsCustomUsername() {
String message = messageService.getMessage();
...
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Test
@WithUserDetails("customUsername")
fun getMessageWithUserDetailsCustomUsername() {
val message: String = messageService.getMessage()
// ...
}
----
======
include-code::./WithUserDetailsTests[tag=user-details-custom-username,indent=0]
We can also provide an explicit bean name to look up the `UserDetailsService`.
The following test looks up the username of `customUsername` by using the `UserDetailsService` with a bean name of `myUserDetailsService`:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Test
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
public void getMessageWithUserDetailsServiceBeanName() {
String message = messageService.getMessage();
...
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Test
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
fun getMessageWithUserDetailsServiceBeanName() {
val message: String = messageService.getMessage()
// ...
}
----
======
include-code::./WithCustomUserDetailsTests[tag=custom-user-details-service,indent=0]
As we did with `@WithMockUser`, we can also place our annotation at the class level so that every test uses the same user.
However, unlike `@WithMockUser`, `@WithUserDetails` requires the user to exist.
@ -519,128 +159,21 @@ We now see an option that allows the most flexibility.
We can create our own annotation that uses the `@WithSecurityContext` to create any `SecurityContext` we want.
For example, we might create an annotation named `@WithMockCustomUser`:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {
String username() default "rob";
String name() default "Rob Winch";
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Retention(AnnotationRetention.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class)
annotation class WithMockCustomUser(val username: String = "rob", val name: String = "Rob Winch")
----
======
include-code::./WithMockCustomUser[tag=snippet,indent=0]
You can see that `@WithMockCustomUser` is annotated with the `@WithSecurityContext` annotation.
This is what signals to Spring Security test support that we intend to create a `SecurityContext` for the test.
The `@WithSecurityContext` annotation requires that we specify a `SecurityContextFactory` to create a new `SecurityContext`, given our `@WithMockCustomUser` annotation.
The following listing shows our `WithMockCustomUserSecurityContextFactory` implementation:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
public class WithMockCustomUserSecurityContextFactory
implements WithSecurityContextFactory<WithMockCustomUser> {
@Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
CustomUserDetails principal =
new CustomUserDetails(customUser.name(), customUser.username());
Authentication auth =
UsernamePasswordAuthenticationToken.authenticated(principal, "password", principal.getAuthorities());
context.setAuthentication(auth);
return context;
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory<WithMockCustomUser> {
override fun createSecurityContext(customUser: WithMockCustomUser): SecurityContext {
val context = SecurityContextHolder.createEmptyContext()
val principal = CustomUserDetails(customUser.name, customUser.username)
val auth: Authentication =
UsernamePasswordAuthenticationToken(principal, "password", principal.authorities)
context.authentication = auth
return context
}
}
----
======
include-code::./WithMockCustomUserSecurityContextFactory[tag=snippet,indent=0]
We can now annotate a test class or a test method with our new annotation and Spring Security's `WithSecurityContextTestExecutionListener` to ensure that our `SecurityContext` is populated appropriately.
When creating your own `WithSecurityContextFactory` implementations, it is nice to know that they can be annotated with standard Spring annotations.
For example, the `WithUserDetailsSecurityContextFactory` uses the `@Autowired` annotation to acquire the `UserDetailsService`:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
final class WithUserDetailsSecurityContextFactory
implements WithSecurityContextFactory<WithUserDetails> {
private UserDetailsService userDetailsService;
@Autowired
public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public SecurityContext createSecurityContext(WithUserDetails withUser) {
String username = withUser.value();
Assert.hasLength(username, "value() must be non-empty String");
UserDetails principal = userDetailsService.loadUserByUsername(username);
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(principal, principal.getPassword(), principal.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
class WithUserDetailsSecurityContextFactory @Autowired constructor(private val userDetailsService: UserDetailsService) :
WithSecurityContextFactory<WithUserDetails> {
override fun createSecurityContext(withUser: WithUserDetails): SecurityContext {
val username: String = withUser.value
Assert.hasLength(username, "value() must be non-empty String")
val principal = userDetailsService.loadUserByUsername(username)
val authentication: Authentication =
UsernamePasswordAuthenticationToken(principal, principal.password, principal.authorities)
val context = SecurityContextHolder.createEmptyContext()
context.authentication = authentication
return context
}
}
----
======
include-code::./WithUserDetailsSecurityContextFactory[tag=snippet,indent=0]
By default, the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
This is the equivalent of happening before JUnit's `@Before`.
@ -658,46 +191,12 @@ You can change this to happen during the `TestExecutionListener.beforeTestExecut
If you reuse the same user within your tests often, it is not ideal to have to repeatedly specify the attributes.
For example, if you have many tests related to an administrative user with a username of `admin` and roles of `ROLE_USER` and `ROLE_ADMIN`, you have to write:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@WithMockUser(username="admin",roles={"USER","ADMIN"})
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@WithMockUser(username="admin",roles=["USER","ADMIN"])
----
======
include-code::./WithMockUserTests[tag=snippet,indent=0]
Rather than repeating this everywhere, we can use a meta annotation.
For example, we could create a meta annotation named `WithMockAdmin`:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(value="rob",roles="ADMIN")
public @interface WithMockAdmin { }
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Retention(AnnotationRetention.RUNTIME)
@WithMockUser(value = "rob", roles = ["ADMIN"])
annotation class WithMockAdmin
----
======
include-code::./WithMockAdmin[tag=snippet,indent=0]
Now we can use `@WithMockAdmin` in the same way as the more verbose `@WithMockUser`.

View File

@ -15,6 +15,7 @@ Each section that follows will indicate the more notable removals as well as the
== Config
* Support modular configuration in xref::servlet/configuration/java.adoc#modular-httpsecurity-configuration[Servlets] and xref::reactive/configuration/webflux.adoc#modular-serverhttpsecurity-configuration[WebFlux]
* Removed `and()` from the `HttpSecurity` DSL in favor of using the lambda methods
* Removed `authorizeRequests` in favor of `authorizeHttpRequests`
* Simplified expression migration for `authorizeRequests`
@ -48,6 +49,7 @@ http.csrf((csrf) -> csrf.spa());
* Removed GET request support from `Saml2AuthenticationTokenConverter`
* Added JDBC-based `AssertingPartyMetadataRepository`
* Made so that SLO still returns `<saml2:LogoutResponse>` even when validation fails
* Removed Open SAML 4 support; applications should migrate to Open SAML 5
== Web

View File

@ -31,7 +31,6 @@ import org.springframework.http.MediaType;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.docs.features.integrations.rest.clientregistrationid.UserService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;

View File

@ -0,0 +1,99 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.customizerbeanordering;
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.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.reactive.config.EnableWebFlux;
/**
*
*/
@EnableWebFlux
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
class CustomizerBeanOrderingConfiguration {
// tag::sample[]
@Bean // <4>
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchange) -> exchange
.anyExchange().authenticated()
);
return http.build();
// @formatter:on
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) // <2>
Customizer<ServerHttpSecurity> userAuthorization() {
// @formatter:off
return (http) -> http
.authorizeExchange((exchange) -> exchange
.pathMatchers("/users/**").hasRole("USER")
);
// @formatter:on
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) // <1>
Customizer<ServerHttpSecurity> adminAuthorization() {
// @formatter:off
return (http) -> http
.authorizeExchange((exchange) -> exchange
.pathMatchers("/admins/**").hasRole("ADMIN")
);
// @formatter:on
}
// <3>
@Bean
Customizer<ServerHttpSecurity.HeaderSpec> contentSecurityPolicy() {
// @formatter:off
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
.policyDirectives("object-src 'none'")
);
// @formatter:on
}
@Bean
Customizer<ServerHttpSecurity.HeaderSpec> contentTypeOptions() {
// @formatter:off
return (headers) -> headers
.contentTypeOptions(Customizer.withDefaults());
// @formatter:on
}
@Bean
Customizer<ServerHttpSecurity.HttpsRedirectSpec> httpsRedirect() {
// @formatter:off
return Customizer.withDefaults();
// @formatter:on
}
// end::sample[]
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.customizerbeanordering;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class CustomizerBeanOrderingTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private WebTestClient webTest;
@Test
void authorizationOrdered() throws Exception {
this.spring.register(
CustomizerBeanOrderingConfiguration.class).autowire();
// @formatter:off
this.webTest.mutateWith(mockUser("admin").roles("ADMIN"))
.get()
.uri("https://localhost/admins/1")
.exchange()
.expectStatus().isOk();
this.webTest.mutateWith(mockUser("user").roles("USER"))
.get()
.uri("https://localhost/admins/1")
.exchange()
.expectStatus().isForbidden();
this.webTest.mutateWith(mockUser("user").roles("USER"))
.get()
.uri("https://localhost/users/1")
.exchange()
.expectStatus().isOk();
this.webTest.mutateWith(mockUser("user").roles("OTHER"))
.get()
.uri("https://localhost/users/1")
.exchange()
.expectStatus().isForbidden();
this.webTest.mutateWith(mockUser("authenticated").roles("OTHER"))
.get()
.uri("https://localhost/other")
.exchange()
.expectStatus().isOk();
// @formatter:on
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.serverhttpsecuritycustomizerbean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
*
*/
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
class ServerHttpSecurityCustomizerBeanConfiguration {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchange) -> exchange
.anyExchange().authenticated()
);
return http.build();
// @formatter:on
}
// tag::httpSecurityCustomizer[]
@Bean
Customizer<ServerHttpSecurity> httpSecurityCustomizer() {
// @formatter:off
return (http) -> http
.headers((headers) -> headers
.contentSecurityPolicy((csp) -> csp
// <1>
.policyDirectives("object-src 'none'")
)
)
// <2>
.redirectToHttps(Customizer.withDefaults());
// @formatter:on
}
// end::httpSecurityCustomizer[]
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.serverhttpsecuritycustomizerbean;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class ServerHttpSecurityCustomizerBeanTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private WebTestClient webTest;
@Test
void httpSecurityCustomizer() throws Exception {
this.spring.register(
ServerHttpSecurityCustomizerBeanConfiguration.class).autowire();
// @formatter:off
this.webTest
.get()
.uri("http://localhost/")
.exchange()
.expectHeader().location("https://localhost/")
.expectHeader()
.value("Content-Security-Policy", csp ->
assertThat(csp).isEqualTo("object-src 'none'")
);
// @formatter:on
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.toplevelcustomizerbean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.web.reactive.config.EnableWebFlux;
/**
*
*/
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
public class TopLevelCustomizerBeanConfiguration {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchange) -> exchange
.anyExchange().authenticated()
);
return http.build();
// @formatter:on
}
// tag::headersCustomizer[]
@Bean
Customizer<ServerHttpSecurity.HeaderSpec> headersSecurity() {
// @formatter:off
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
// <1>
.policyDirectives("object-src 'none'")
);
// @formatter:on
}
// end::headersCustomizer[]
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.reactive.configuration.toplevelcustomizerbean;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class TopLevelCustomizerBeanTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private WebTestClient webTest;
@Test
void headersCustomizer() throws Exception {
this.spring.register(TopLevelCustomizerBeanConfiguration.class).autowire();
// @formatter:off
this.webTest
.get()
.uri("http://localhost/")
.exchange()
.expectHeader()
.value("Content-Security-Policy", csp ->
assertThat(csp).isEqualTo("object-src 'none'")
);
// @formatter:on
}
private static @NotNull ResultMatcher cspIsObjectSrcNone() {
return header().string("Content-Security-Policy", "object-src 'none'");
}
}

View File

@ -20,8 +20,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

View File

@ -16,33 +16,18 @@
package org.springframework.security.docs.servlet.authentication.servletx509config;
import java.io.InputStream;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;

View File

@ -0,0 +1,102 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.customizerbeanordering;
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.security.config.Customizer;
import org.springframework.security.config.ThrowingCustomizer;
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.configurers.HeadersConfigurer;
import org.springframework.security.config.annotation.web.configurers.HttpsRedirectConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
*
*/
@EnableWebMvc
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class CustomizerBeanOrderingConfiguration {
// tag::sample[]
@Bean // <4>
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
);
return http.build();
// @formatter:on
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) // <2>
ThrowingCustomizer<HttpSecurity> userAuthorization() {
// @formatter:off
return (http) -> http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/users/**").hasRole("USER")
);
// @formatter:on
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) // <1>
ThrowingCustomizer<HttpSecurity> adminAuthorization() {
// @formatter:off
return (http) -> http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/admins/**").hasRole("ADMIN")
);
// @formatter:on
}
// <3>
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> contentSecurityPolicy() {
// @formatter:off
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
.policyDirectives("object-src 'none'")
);
// @formatter:on
}
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> contentTypeOptions() {
// @formatter:off
return (headers) -> headers
.contentTypeOptions(Customizer.withDefaults());
// @formatter:on
}
@Bean
Customizer<HttpsRedirectConfigurer<HttpSecurity>> httpsRedirect() {
// @formatter:off
return Customizer.withDefaults();
// @formatter:on
}
// end::sample[]
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.customizerbeanordering;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class CustomizerBeanOrderingTests {
public final SpringTestContext spring = new SpringTestContext(this).mockMvcAfterSpringSecurityOk();
@Autowired
private MockMvc mockMvc;
@Test
void authorizationOrdered() throws Exception {
this.spring.register(
CustomizerBeanOrderingConfiguration.class).autowire();
// @formatter:off
this.mockMvc
.perform(get("https://localhost/admins/1").with(user("admin").roles("ADMIN")))
.andExpect(status().isOk());
this.mockMvc
.perform(get("https://localhost/admins/1").with(user("user").roles("USER")))
.andExpect(status().isForbidden());
this.mockMvc
.perform(get("https://localhost/users/1").with(user("user").roles("USER")))
.andExpect(status().isOk());
this.mockMvc
.perform(get("https://localhost/users/1").with(user("user").roles("OTHER")))
.andExpect(status().isForbidden());
this.mockMvc
.perform(get("https://localhost/other").with(user("authenticated").roles("OTHER")))
.andExpect(status().isOk());
// @formatter:on
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.httpsecuritycustomizerbean;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ThrowingCustomizer;
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.configurers.HeadersConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
*
*/
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class HttpSecurityCustomizerBeanConfiguration {
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
);
return http.build();
// @formatter:on
}
// tag::httpSecurityCustomizer[]
@Bean
ThrowingCustomizer<HttpSecurity> httpSecurityCustomizer() {
// @formatter:off
return (http) -> http
.headers((headers) -> headers
.contentSecurityPolicy((csp) -> csp
// <1>
.policyDirectives("object-src 'none'")
)
)
// <2>
.redirectToHttps(Customizer.withDefaults());
// @formatter:on
}
// end::httpSecurityCustomizer[]
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.httpsecuritycustomizerbean;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ThrowingCustomizer;
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.configurers.HeadersConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class HttpSecurityCustomizerBeanTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private MockMvc mockMvc;
@Test
void httpSecurityCustomizer() throws Exception {
this.spring.register(HttpSecurityCustomizerBeanConfiguration.class).autowire();
// @formatter:off
this.mockMvc
.perform(get("/"))
.andExpect(redirectsToHttps());
// headers are not sent back as a part of the redirect to https, so a separate request is necessary
this.mockMvc.perform(get("https://localhost/"))
.andExpect(cspIsObjectSrcNone());
// @formatter:on
}
private static @NotNull ResultMatcher redirectsToHttps() {
return mvcResult -> assertThat(
mvcResult.getResponse().getRedirectedUrl()).startsWith("https://");
}
private static @NotNull ResultMatcher cspIsObjectSrcNone() {
return header().string("Content-Security-Policy", "object-src 'none'");
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.toplevelcustomizerbean;
import org.jetbrains.annotations.NotNull;
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.context.annotation.Import;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ThrowingCustomizer;
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.configurers.HeadersConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
*
*/
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class TopLevelCustomizerBeanConfiguration {
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
);
return http.build();
// @formatter:on
}
// tag::headersCustomizer[]
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> headersSecurity() {
// @formatter:off
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
// <1>
.policyDirectives("object-src 'none'")
);
// @formatter:on
}
// end::headersCustomizer[]
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.configuration.toplevelcustomizerbean;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ThrowingCustomizer;
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.configurers.HeadersConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class TopLevelCustomizerBeanTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private MockMvc mockMvc;
@Test
void headersCustomizer() throws Exception {
this.spring.register(TopLevelCustomizerBeanConfiguration.class).autowire();
// @formatter:off
this.mockMvc
.perform(get("/"))
.andExpect(cspIsObjectSrcNone());
// @formatter:on
}
private static @NotNull ResultMatcher cspIsObjectSrcNone() {
return header().string("Content-Security-Policy", "object-src 'none'");
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.test.testmethod;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.core.MessageService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* A message service for demonstrating test support for method-based security.
*/
// tag::authenticated[]
public class HelloMessageService implements MessageService {
@Override
@PreAuthorize("isAuthenticated()")
public String getMessage() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
return "Hello " + authentication;
}
@Override
@PreAuthorize("isAuthenticated()")
public String getJsrMessage() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
return "Hello JSR " + authentication;
}
}
// end::authenticated[]

View File

@ -0,0 +1,65 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.test.testmethod;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.core.MessageService;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration
class HelloServiceTests {
@Autowired
MessageService messageService;
@BeforeEach
void setup() {
UsernamePasswordAuthenticationToken user = UsernamePasswordAuthenticationToken.authenticated("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER"));
SecurityContextHolder.getContext().setAuthentication(user);
}
@Test
void helloServiceTest() {
assertThat(messageService.getMessage())
.contains("user")
.contains("ROLE_USER");
}
@EnableMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
@Configuration
static class Config {
@Bean
MessageService messageService() {
return new HelloMessageService();
}
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.test.testmethodmetaannotations;
import org.springframework.security.test.context.support.WithMockUser;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
// tag::snippet[]
@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(value="rob",roles={"USER","ADMIN"})
public @interface WithMockAdmin { }
// end::snippet[]

View File

@ -0,0 +1,58 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.test.testmethodmetaannotations;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.core.MessageService;
import org.springframework.security.docs.servlet.test.testmethod.HelloMessageService;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration
public class WithMockAdminTests {
@Autowired
MessageService messageService;
@Test
@WithMockAdmin
void getMessageWithMockUserAdminRoles() {
String message = messageService.getMessage();
assertThat(message)
.contains("rob")
.contains("ROLE_ADMIN")
.contains("ROLE_USER");
}
@EnableMethodSecurity
@Configuration
static class Config {
@Bean
MessageService messageService() {
return new HelloMessageService();
}
}
}

Some files were not shown because too many files have changed in this diff Show More