Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fe6d9259f | |||
| 57a06ead66 | |||
| e4dde52fa4 | |||
| d9e9e3cdeb | |||
| 5ee1586a5a | |||
| 018e1ae1a4 | |||
| b9911fd522 | |||
| e3cd4339b2 | |||
| fb954063bf | |||
| 2a76a09552 | |||
| 809d8a84a3 | |||
| 7e83fd54aa | |||
| 7592483654 | |||
| f7b9b7228f | |||
| 82cc3ad5ec | |||
| 47b5ab400a | |||
| 103a98ff9e | |||
| d290c5c205 | |||
| 3e43eda42b | |||
| 511bcc25f2 | |||
| 3008ccc98e | |||
| e7d6dc22b2 | |||
| 180ac5497e | |||
| cb08e37d31 | |||
| 49e7a3e65f | |||
| ee1ede8b2d | |||
| 682c40894e | |||
| da06f6a9e6 | |||
| d7d5253607 | |||
| 99cc65d74c | |||
| cb4c7e5886 | |||
| 6a0b683e60 | |||
| 99015300f2 | |||
| 2b6d586987 | |||
| 74e1777349 | |||
| 593211d2a9 | |||
| 55ea78d441 | |||
| a731af103e | |||
| d1a4fac520 | |||
| 4cbaabb239 | |||
| f565b23b51 | |||
| 40f8ac642a | |||
| b168ec5f0d | |||
| 8d1b0d9764 | |||
| 2fe7faea87 | |||
| eb313ec901 | |||
| a18475c6cc | |||
| 7873ab8601 | |||
| 348f064df1 | |||
| 8b9fe13c88 | |||
| aa85ebc65f | |||
| f613ff2285 | |||
| 892bcbd958 | |||
| 1f7dcc0fa6 | |||
| 7faf7ef9ec | |||
| 908b9b5a85 | |||
| 807c3dd3ab | |||
| cac03995a3 | |||
| 6ea7da5178 | |||
| cb1c23f4c5 | |||
| 49f1b3554c | |||
| b95e63ecc6 | |||
| 519df3f7d5 | |||
| 16272f634c | |||
| 0b9887505e | |||
| bbc1fa0d16 | |||
| 7ad3cfc9c4 | |||
| 4dd00fe146 | |||
| dd8ee38194 | |||
| 87de6cea1b | |||
| 3d1e4b5f18 | |||
| a4469686c7 | |||
| 05ffdcafff | |||
| 3e20f7b5b5 | |||
| 32e1d4c990 | |||
| 39cd8d9faf | |||
| d39e329234 | |||
| 96b3c77ce0 | |||
| ef7b11ac01 | |||
| d3a95c5c1e | |||
| 0f85da77be | |||
| 96a9cf0d2d | |||
| 5966baf9bf | |||
| 2ed1cafed8 | |||
| 8c0ea3e630 | |||
| 9c3b11914d | |||
| cc2506b0c1 | |||
| dc82a6e97e | |||
| 5329030d27 | |||
| c97edf2bd0 | |||
| e947678424 | |||
| 24a42a9b02 | |||
| 27e2640250 | |||
| ebb75f5e90 | |||
| ea53a4999b | |||
| a6c3d123ed | |||
| 4fadcad26a | |||
| 324de7af93 | |||
| fd5c5a8105 | |||
| e79ceaeb75 | |||
| ff7dbb4585 | |||
| e4155ba246 | |||
| 77529996a8 | |||
| f361ee72e8 | |||
| f5eccf7cd3 | |||
| 14be74051e | |||
| 21ac1022ef | |||
| c16b2c262d | |||
| 2b5a2eef82 | |||
| a55021539a | |||
| 68d91916e2 | |||
| 46fe0124ba | |||
| 409d552ae0 | |||
| 2de19cd9fb | |||
| 753f8aecc9 | |||
| 748a521dd5 | |||
| 84e4c9bc42 | |||
| 1dbaa08cd4 | |||
| 1d916c35d1 | |||
| 315aafd464 | |||
| e8ba039a61 | |||
| aa635b02a8 | |||
| d1e6806464 | |||
| 026b500cc3 | |||
| 24ccac3555 | |||
| 77233daae7 | |||
| 4b41f8cb5b | |||
| 0eb6acde96 | |||
| 73f3f75712 | |||
| 93ce7e97be |
@@ -36,6 +36,5 @@ updates:
|
||||
schedule:
|
||||
interval: weekly
|
||||
ignore:
|
||||
- dependency-name: "sjohnr/*"
|
||||
- dependency-name: "spring-io/*"
|
||||
- dependency-name: "spring-security-release-tools/*"
|
||||
|
||||
@@ -4,56 +4,6 @@ registries:
|
||||
type: maven-repository
|
||||
url: https://repo.spring.io/milestone
|
||||
updates:
|
||||
- package-ecosystem: gradle
|
||||
target-branch: 5.8.x
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
time: '03:00'
|
||||
timezone: Etc/UTC
|
||||
labels:
|
||||
- 'type: dependency-upgrade'
|
||||
registries:
|
||||
- spring-milestones
|
||||
ignore:
|
||||
- dependency-name: com.nimbusds:nimbus-jose-jwt
|
||||
- dependency-name: org.python:jython
|
||||
- dependency-name: org.apache.directory.server:*
|
||||
- dependency-name: org.junit:junit-bom
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- dependency-name: org.mockito:mockito-bom
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- dependency-name: '*'
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- version-update:semver-minor
|
||||
- package-ecosystem: gradle
|
||||
target-branch: 6.2.x
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
time: '03:00'
|
||||
timezone: Etc/UTC
|
||||
labels:
|
||||
- 'type: dependency-upgrade'
|
||||
registries:
|
||||
- spring-milestones
|
||||
ignore:
|
||||
- dependency-name: com.nimbusds:nimbus-jose-jwt
|
||||
- dependency-name: org.python:jython
|
||||
- dependency-name: org.apache.directory.server:*
|
||||
- dependency-name: org.junit:junit-bom
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- dependency-name: org.mockito:mockito-bom
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- dependency-name: '*'
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
- version-update:semver-minor
|
||||
- package-ecosystem: gradle
|
||||
target-branch: 6.3.x
|
||||
directory: /
|
||||
@@ -108,26 +58,6 @@ updates:
|
||||
update-types:
|
||||
- version-update:semver-major
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
target-branch: 5.8.x
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
ignore:
|
||||
- dependency-name: sjohnr/*
|
||||
- package-ecosystem: github-actions
|
||||
target-branch: 6.2.x
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
ignore:
|
||||
- dependency-name: sjohnr/*
|
||||
- package-ecosystem: github-actions
|
||||
target-branch: 6.3.x
|
||||
directory: /
|
||||
@@ -136,8 +66,6 @@ updates:
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
ignore:
|
||||
- dependency-name: sjohnr/*
|
||||
- package-ecosystem: github-actions
|
||||
target-branch: main
|
||||
directory: /
|
||||
@@ -146,8 +74,6 @@ updates:
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
ignore:
|
||||
- dependency-name: sjohnr/*
|
||||
- package-ecosystem: github-actions
|
||||
target-branch: docs-build
|
||||
directory: /
|
||||
@@ -156,8 +82,6 @@ updates:
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
ignore:
|
||||
- dependency-name: sjohnr/*
|
||||
|
||||
- package-ecosystem: npm
|
||||
target-branch: docs-build
|
||||
@@ -184,19 +108,3 @@ updates:
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
- package-ecosystem: npm
|
||||
target-branch: 6.2.x
|
||||
directory: /docs
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
- package-ecosystem: npm
|
||||
target-branch: 5.8.x
|
||||
directory: /docs
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
workflow_dispatch: # Manual trigger
|
||||
|
||||
env:
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
id: run-auto-merge-forward
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/auto-merge-forward@actions-v1
|
||||
with:
|
||||
branches: 5.8.x,${{ needs.get-supported-branches.outputs.supported_versions }},main
|
||||
branches: ${{ needs.get-supported-branches.outputs.supported_versions }},main
|
||||
from-author: dependabot[bot]
|
||||
notify_result:
|
||||
name: Check for failures
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Set up Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
uses: gradle/gradle-build-action@v3
|
||||
- name: Upgrade Wrappers
|
||||
run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false
|
||||
env:
|
||||
|
||||
@@ -122,6 +122,8 @@ dependencies {
|
||||
exclude group: "org.slf4j", module: "jcl-over-slf4j"
|
||||
}
|
||||
testImplementation libs.org.instancio.instancio.junit
|
||||
testImplementation libs.org.eclipse.jetty.jetty.server
|
||||
testImplementation libs.org.eclipse.jetty.jetty.servlet
|
||||
|
||||
testRuntimeOnly 'org.hsqldb:hsqldb'
|
||||
}
|
||||
@@ -170,6 +172,17 @@ configure(project.tasks.withType(Test)) {
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
onOutput { descriptor, event ->
|
||||
if (!project.hasProperty('serialization')) {
|
||||
return
|
||||
}
|
||||
if (descriptor.name=='listClassesMissingSerialVersion()') {
|
||||
logger.lifecycle(event.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("opensaml5Test", Test) {
|
||||
filter {
|
||||
includeTestsMatching "org.springframework.security.config.annotation.web.configurers.saml2.*"
|
||||
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.configurers;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.chrome.ChromeDriverService;
|
||||
import org.openqa.selenium.chrome.ChromeOptions;
|
||||
import org.openqa.selenium.chromium.HasCdp;
|
||||
import org.openqa.selenium.devtools.HasDevTools;
|
||||
import org.openqa.selenium.remote.Augmenter;
|
||||
import org.openqa.selenium.remote.RemoteWebDriver;
|
||||
import org.openqa.selenium.support.ui.FluentWait;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.mock.env.MockPropertySource;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Webdriver-based tests for the WebAuthnConfigurer. This uses a full browser because
|
||||
* these features require Javascript and browser APIs to be available.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
class WebAuthnWebDriverTests {
|
||||
|
||||
private String baseUrl;
|
||||
|
||||
private static ChromeDriverService driverService;
|
||||
|
||||
private Server server;
|
||||
|
||||
private RemoteWebDriver driver;
|
||||
|
||||
private static final String USERNAME = "user";
|
||||
|
||||
private static final String PASSWORD = "password";
|
||||
|
||||
@BeforeAll
|
||||
static void startChromeDriverService() throws Exception {
|
||||
driverService = new ChromeDriverService.Builder().usingAnyFreePort().build();
|
||||
driverService.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void stopChromeDriverService() {
|
||||
driverService.stop();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void startServer() throws Exception {
|
||||
// Create the server on port 8080
|
||||
this.server = new Server(0);
|
||||
|
||||
// Set up the ServletContextHandler
|
||||
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
contextHandler.setContextPath("/");
|
||||
this.server.setHandler(contextHandler);
|
||||
this.server.start();
|
||||
int serverPort = ((ServerConnector) this.server.getConnectors()[0]).getLocalPort();
|
||||
this.baseUrl = "http://localhost:" + serverPort;
|
||||
|
||||
// Set up Spring application context
|
||||
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
|
||||
applicationContext.register(WebAuthnConfiguration.class);
|
||||
applicationContext.setServletContext(contextHandler.getServletContext());
|
||||
|
||||
// Add the server port
|
||||
MockPropertySource propertySource = new MockPropertySource().withProperty("server.port", serverPort);
|
||||
applicationContext.getEnvironment().getPropertySources().addFirst(propertySource);
|
||||
|
||||
// Register the filter chain
|
||||
DelegatingFilterProxy filterProxy = new DelegatingFilterProxy("securityFilterChain", applicationContext);
|
||||
FilterHolder filterHolder = new FilterHolder(filterProxy);
|
||||
contextHandler.addFilter(filterHolder, "/*", null);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void stopServer() throws Exception {
|
||||
this.server.stop();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setupDriver() {
|
||||
ChromeOptions options = new ChromeOptions();
|
||||
options.addArguments("--headless=new");
|
||||
RemoteWebDriver baseDriver = new RemoteWebDriver(driverService.getUrl(), options);
|
||||
// Enable dev tools
|
||||
this.driver = (RemoteWebDriver) new Augmenter().augment(baseDriver);
|
||||
this.driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(1));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanupDriver() {
|
||||
this.driver.quit();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginWhenNoValidAuthenticatorCredentialsThenRejects() {
|
||||
createVirtualAuthenticator(true);
|
||||
this.driver.get(this.baseUrl);
|
||||
this.driver.findElement(signinWithPasskeyButton()).click();
|
||||
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWhenNoLabelThenRejects() {
|
||||
login();
|
||||
|
||||
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||
|
||||
this.driver.findElement(registerPasskeyButton()).click();
|
||||
assertHasAlertStartingWith("error", "Error: Passkey Label is required");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWhenAuthenticatorNoUserVerificationThenRejects() {
|
||||
createVirtualAuthenticator(false);
|
||||
login();
|
||||
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
|
||||
this.driver.findElement(registerPasskeyButton()).click();
|
||||
|
||||
await(() -> assertHasAlertStartingWith("error",
|
||||
"Registration failed. Call to navigator.credentials.create failed:"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test in 4 steps to verify the end-to-end flow of registering an authenticator and
|
||||
* using it to register.
|
||||
* <ul>
|
||||
* <li>Step 1: Log in with username / password</li>
|
||||
* <li>Step 2: Register a credential from the virtual authenticator</li>
|
||||
* <li>Step 3: Log out</li>
|
||||
* <li>Step 4: Log in with the authenticator</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Test
|
||||
void loginWhenAuthenticatorRegisteredThenSuccess() {
|
||||
// Setup
|
||||
createVirtualAuthenticator(true);
|
||||
|
||||
// Step 1: log in with username / password
|
||||
login();
|
||||
|
||||
// Step 2: register a credential from the virtual authenticator
|
||||
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
|
||||
this.driver.findElement(registerPasskeyButton()).click();
|
||||
|
||||
// Ensure the page location has changed before performing further assertions.
|
||||
// This is required because the location change is asynchronously performed in
|
||||
// javascript, and performing assertions based on this.driver.findElement(...)
|
||||
// may result in a StaleElementReferenceException.
|
||||
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?success"));
|
||||
await(() -> assertHasAlertStartingWith("success", "Success!"));
|
||||
|
||||
List<WebElement> passkeyRows = this.driver.findElements(passkeyTableRows());
|
||||
assertThat(passkeyRows).hasSize(1)
|
||||
.first()
|
||||
.extracting((row) -> row.findElement(firstCell()))
|
||||
.extracting(WebElement::getText)
|
||||
.isEqualTo("Virtual authenticator");
|
||||
|
||||
// Step 3: log out
|
||||
logout();
|
||||
|
||||
// Step 4: log in with the virtual authenticator
|
||||
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||
this.driver.findElement(signinWithPasskeyButton()).click();
|
||||
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a virtual authenticator.
|
||||
* <p>
|
||||
* Note that Selenium docs for {@link HasCdp} strongly encourage to use
|
||||
* {@link HasDevTools} instead. However, devtools require more dependencies and
|
||||
* boilerplate, notably to sync the Devtools-CDP version with the current browser
|
||||
* version, whereas CDP runs out of the box.
|
||||
* <p>
|
||||
* @param userIsVerified whether the authenticator simulates user verification.
|
||||
* Setting it to false will make the ceremonies fail.
|
||||
* @see <a href=
|
||||
* "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a>
|
||||
*/
|
||||
private void createVirtualAuthenticator(boolean userIsVerified) {
|
||||
HasCdp cdpDriver = (HasCdp) this.driver;
|
||||
cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false));
|
||||
// this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions());
|
||||
//@formatter:off
|
||||
cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
|
||||
Map.of(
|
||||
"options",
|
||||
Map.of(
|
||||
"protocol", "ctap2",
|
||||
"transport", "usb",
|
||||
"hasUserVerification", true,
|
||||
"hasResidentKey", true,
|
||||
"isUserVerified", userIsVerified,
|
||||
"automaticPresenceSimulation", true
|
||||
)
|
||||
));
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
private void login() {
|
||||
this.driver.get(this.baseUrl);
|
||||
this.driver.findElement(usernameField()).sendKeys(USERNAME);
|
||||
this.driver.findElement(passwordField()).sendKeys(PASSWORD);
|
||||
this.driver.findElement(signinWithUsernamePasswordButton()).click();
|
||||
}
|
||||
|
||||
private void logout() {
|
||||
this.driver.get(this.baseUrl + "/logout");
|
||||
this.driver.findElement(logoutButton()).click();
|
||||
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout"));
|
||||
}
|
||||
|
||||
private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) {
|
||||
WebElement alert = this.driver.findElement(new By.ById(alertType));
|
||||
assertThat(alert.isDisplayed())
|
||||
.withFailMessage(
|
||||
() -> alertType + " alert was not displayed. Full page source:\n\n" + this.driver.getPageSource())
|
||||
.isTrue();
|
||||
|
||||
return assertThat(alert.getText()).startsWith(alertMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Await until the assertion passes. If the assertion fails, it will display the
|
||||
* assertion error in stdout.
|
||||
*/
|
||||
private void await(Supplier<AbstractAssert<?, ?>> assertion) {
|
||||
new FluentWait<>(this.driver).withTimeout(Duration.ofSeconds(2))
|
||||
.pollingEvery(Duration.ofMillis(100))
|
||||
.ignoring(AssertionError.class)
|
||||
.until((d) -> {
|
||||
assertion.get();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private static By.ById passkeyLabel() {
|
||||
return new By.ById("label");
|
||||
}
|
||||
|
||||
private static By.ById registerPasskeyButton() {
|
||||
return new By.ById("register");
|
||||
}
|
||||
|
||||
private static By.ByCssSelector passkeyTableRows() {
|
||||
return new By.ByCssSelector("table > tbody > tr");
|
||||
}
|
||||
|
||||
private static By.ByCssSelector firstCell() {
|
||||
return new By.ByCssSelector("td:first-child");
|
||||
}
|
||||
|
||||
private static By.ById passwordField() {
|
||||
return new By.ById(PASSWORD);
|
||||
}
|
||||
|
||||
private static By.ById usernameField() {
|
||||
return new By.ById("username");
|
||||
}
|
||||
|
||||
private static By.ByCssSelector signinWithUsernamePasswordButton() {
|
||||
return new By.ByCssSelector("form > button[type=\"submit\"]");
|
||||
}
|
||||
|
||||
private static By.ById signinWithPasskeyButton() {
|
||||
return new By.ById("passkey-signin");
|
||||
}
|
||||
|
||||
private static By.ByCssSelector logoutButton() {
|
||||
return new By.ByCssSelector("button");
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration for WebAuthN tests. It accesses the Server's current port, so we
|
||||
* can configurer WebAuthnConfigurer#allowedOrigin
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
static class WebAuthnConfiguration {
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
return new InMemoryUserDetailsManager(
|
||||
User.withDefaultPasswordEncoder().username(USERNAME).password(PASSWORD).build());
|
||||
}
|
||||
|
||||
@Bean
|
||||
FilterChainProxy securityFilterChain(HttpSecurity http, Environment environment) throws Exception {
|
||||
SecurityFilterChain securityFilterChain = http
|
||||
.authorizeHttpRequests((auth) -> auth.anyRequest().authenticated())
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.webAuthn((passkeys) -> passkeys.rpId("localhost")
|
||||
.rpName("Spring Security WebAuthN tests")
|
||||
.allowedOrigins("http://localhost:" + environment.getProperty("server.port")))
|
||||
.build();
|
||||
return new FilterChainProxy(securityFilterChain);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2019 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.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationHandler;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.rsocket.core.RSocketServer;
|
||||
import io.rsocket.frame.decoder.PayloadDecoder;
|
||||
import io.rsocket.metadata.WellKnownMimeType;
|
||||
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.mockito.ArgumentCaptor;
|
||||
|
||||
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.RSocketStrategies;
|
||||
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
|
||||
import org.springframework.security.rsocket.metadata.SimpleAuthenticationEncoder;
|
||||
import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@ExtendWith(SpringExtension.class)
|
||||
public class HelloRSocketObservationITests {
|
||||
|
||||
@Autowired
|
||||
RSocketMessageHandler handler;
|
||||
|
||||
@Autowired
|
||||
SecuritySocketAcceptorInterceptor interceptor;
|
||||
|
||||
@Autowired
|
||||
ServerController controller;
|
||||
|
||||
@Autowired
|
||||
ObservationHandler<Observation.Context> observationHandler;
|
||||
|
||||
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 getWhenUsingObservationRegistryThenObservesRequest() {
|
||||
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("rob", "password");
|
||||
// @formatter:off
|
||||
this.requester = RSocketRequester.builder()
|
||||
.setupMetadata(credentials, MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()))
|
||||
.rsocketStrategies(this.handler.getRSocketStrategies())
|
||||
.connectTcp("localhost", this.server.address().getPort())
|
||||
.block();
|
||||
// @formatter:on
|
||||
String data = "rob";
|
||||
// @formatter:off
|
||||
this.requester.route("secure.retrieve-mono")
|
||||
.metadata(credentials, MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()))
|
||||
.data(data)
|
||||
.retrieveMono(String.class)
|
||||
.block();
|
||||
// @formatter:on
|
||||
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
|
||||
verify(this.observationHandler, times(2)).onStart(captor.capture());
|
||||
Iterator<Observation.Context> contexts = captor.getAllValues().iterator();
|
||||
// once for setup
|
||||
assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications");
|
||||
// once for request
|
||||
assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRSocketSecurity
|
||||
static class Config {
|
||||
|
||||
private ObservationHandler<Observation.Context> handler = mock(ObservationHandler.class);
|
||||
|
||||
@Bean
|
||||
ServerController controller() {
|
||||
return new ServerController();
|
||||
}
|
||||
|
||||
@Bean
|
||||
RSocketMessageHandler messageHandler() {
|
||||
RSocketMessageHandler handler = new RSocketMessageHandler();
|
||||
handler.setRSocketStrategies(rsocketStrategies());
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder().encoder(new SimpleAuthenticationEncoder()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MapReactiveUserDetailsService uds() {
|
||||
// @formatter:off
|
||||
UserDetails rob = User.withDefaultPasswordEncoder()
|
||||
.username("rob")
|
||||
.password("password")
|
||||
.roles("USER", "ADMIN")
|
||||
.build();
|
||||
// @formatter:on
|
||||
return new MapReactiveUserDetailsService(rob);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ObservationHandler<Observation.Context> observationHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ObservationRegistry observationRegistry() {
|
||||
given(this.handler.supportsContext(any())).willReturn(true);
|
||||
ObservationRegistry registry = ObservationRegistry.create();
|
||||
registry.observationConfig().observationHandler(this.handler);
|
||||
return registry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2019-2024 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.RSocketStrategies;
|
||||
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
|
||||
import org.springframework.security.rsocket.metadata.BasicAuthenticationEncoder;
|
||||
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 Rob Winch
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@ExtendWith(SpringExtension.class)
|
||||
public class HelloRSocketWithWebFluxITests {
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
// gh-16161
|
||||
@Test
|
||||
public void retrieveMonoWhenSecureThenDenied() {
|
||||
// @formatter:off
|
||||
this.requester = RSocketRequester.builder()
|
||||
.rsocketStrategies(this.handler.getRSocketStrategies())
|
||||
.connectTcp("localhost", this.server.address().getPort())
|
||||
.block();
|
||||
// @formatter:on
|
||||
String data = "rob";
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(Exception.class).isThrownBy(
|
||||
() -> this.requester.route("secure.retrieve-mono")
|
||||
.data(data)
|
||||
.retrieveMono(String.class)
|
||||
.block()
|
||||
)
|
||||
.matches((ex) -> ex instanceof RejectedSetupException
|
||||
|| ex.getClass().toString().contains("ReactiveException"));
|
||||
// @formatter:on
|
||||
assertThat(this.controller.payloads).isEmpty();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRSocketSecurity
|
||||
@EnableWebFluxSecurity
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
ServerController controller() {
|
||||
return new ServerController();
|
||||
}
|
||||
|
||||
@Bean
|
||||
RSocketMessageHandler messageHandler() {
|
||||
RSocketMessageHandler handler = new RSocketMessageHandler();
|
||||
handler.setRSocketStrategies(rsocketStrategies());
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder().encoder(new BasicAuthenticationEncoder()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MapReactiveUserDetailsService uds() {
|
||||
// @formatter:off
|
||||
UserDetails rob = User.withDefaultPasswordEncoder()
|
||||
.username("rob")
|
||||
.password("password")
|
||||
.roles("USER", "ADMIN")
|
||||
.build();
|
||||
// @formatter:on
|
||||
return new MapReactiveUserDetailsService(rob);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+22
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -79,6 +79,15 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
|
||||
this(objectPostProcessor, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
protected AbstractConfiguredSecurityBuilder(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
this(objectPostProcessor, false);
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates a new instance with the provided {@link ObjectPostProcessor}. This post
|
||||
* processor must support Object since there are many types of objects that may be
|
||||
@@ -94,6 +103,18 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
|
||||
this.allowConfigurersOfSameType = allowConfigurersOfSameType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
protected AbstractConfiguredSecurityBuilder(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor,
|
||||
boolean allowConfigurersOfSameType) {
|
||||
Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");
|
||||
this.objectPostProcessor = objectPostProcessor;
|
||||
this.allowConfigurersOfSameType = allowConfigurersOfSameType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #build()} and {@link #getObject()} but checks the state to
|
||||
* determine if {@link #build()} needs to be called first.
|
||||
|
||||
+10
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -92,6 +92,15 @@ public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>>
|
||||
this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public void addObjectPostProcessor(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
|
||||
this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link SecurityBuilder} to be used. This is automatically set when using
|
||||
* {@link AbstractConfiguredSecurityBuilder#apply(SecurityConfigurerAdapter)}
|
||||
|
||||
+13
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -67,12 +67,23 @@ public class AuthenticationManagerBuilder
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param objectPostProcessor the {@link ObjectPostProcessor} instance to use.
|
||||
* @param objectPostProcessor the
|
||||
* {@link org.springframework.security.config.annotation.ObjectPostProcessor} instance
|
||||
* to use.
|
||||
*/
|
||||
public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
super(objectPostProcessor, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public AuthenticationManagerBuilder(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
super(objectPostProcessor, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows providing a parent {@link AuthenticationManager} that will be tried if this
|
||||
* {@link AuthenticationManager} was unable to attempt to authenticate the provided
|
||||
|
||||
+9
-48
@@ -16,8 +16,7 @@
|
||||
|
||||
package org.springframework.security.config.annotation.authentication.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -63,62 +62,24 @@ class InitializeAuthenticationProviderBeanManagerConfigurer extends GlobalAuthen
|
||||
if (auth.isConfigured()) {
|
||||
return;
|
||||
}
|
||||
List<BeanWithName<AuthenticationProvider>> authenticationProviders = getBeansWithName(
|
||||
AuthenticationProvider.class);
|
||||
if (authenticationProviders.isEmpty()) {
|
||||
String[] beanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
|
||||
.getBeanNamesForType(AuthenticationProvider.class);
|
||||
if (beanNames.length == 0) {
|
||||
return;
|
||||
}
|
||||
else if (authenticationProviders.size() > 1) {
|
||||
List<String> beanNames = authenticationProviders.stream().map(BeanWithName::getName).toList();
|
||||
else if (beanNames.length > 1) {
|
||||
this.logger.info(LogMessage.format("Found %s AuthenticationProvider beans, with names %s. "
|
||||
+ "Global Authentication Manager will not be configured with AuthenticationProviders. "
|
||||
+ "Consider publishing a single AuthenticationProvider bean, or wiring your Providers directly "
|
||||
+ "using the DSL.", authenticationProviders.size(), beanNames));
|
||||
+ "using the DSL.", beanNames.length, Arrays.toString(beanNames)));
|
||||
return;
|
||||
}
|
||||
AuthenticationProvider authenticationProvider = authenticationProviders.get(0).getBean();
|
||||
String authenticationProviderBeanName = authenticationProviders.get(0).getName();
|
||||
|
||||
AuthenticationProvider authenticationProvider = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
|
||||
.getBean(beanNames[0], AuthenticationProvider.class);
|
||||
auth.authenticationProvider(authenticationProvider);
|
||||
this.logger.info(LogMessage.format(
|
||||
"Global AuthenticationManager configured with AuthenticationProvider bean with name %s",
|
||||
authenticationProviderBeanName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a list of beans of the requested class, along with their names. If
|
||||
* there are no registered beans of that type, the list is empty.
|
||||
*/
|
||||
private <T> List<BeanWithName<T>> getBeansWithName(Class<T> type) {
|
||||
List<BeanWithName<T>> beanWithNames = new ArrayList<>();
|
||||
String[] beanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
|
||||
.getBeanNamesForType(type);
|
||||
for (String beanName : beanNames) {
|
||||
T bean = InitializeAuthenticationProviderBeanManagerConfigurer.this.context.getBean(beanName, type);
|
||||
beanWithNames.add(new BeanWithName<>(bean, beanName));
|
||||
}
|
||||
return beanWithNames;
|
||||
}
|
||||
|
||||
static class BeanWithName<T> {
|
||||
|
||||
private final T bean;
|
||||
|
||||
private final String name;
|
||||
|
||||
BeanWithName(T bean, String name) {
|
||||
this.bean = bean;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
T getBean() {
|
||||
return this.bean;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
beanNames[0]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-48
@@ -16,8 +16,7 @@
|
||||
|
||||
package org.springframework.security.config.annotation.authentication.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -67,9 +66,10 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
|
||||
|
||||
@Override
|
||||
public void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
List<BeanWithName<UserDetailsService>> userDetailsServices = getBeansWithName(UserDetailsService.class);
|
||||
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context
|
||||
.getBeanNamesForType(UserDetailsService.class);
|
||||
if (auth.isConfigured()) {
|
||||
if (!userDetailsServices.isEmpty()) {
|
||||
if (beanNames.length > 0) {
|
||||
this.logger.warn("Global AuthenticationManager configured with an AuthenticationProvider bean. "
|
||||
+ "UserDetailsService beans will not be used by Spring Security for automatically configuring username/password login. "
|
||||
+ "Consider removing the AuthenticationProvider bean. "
|
||||
@@ -80,19 +80,18 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
|
||||
return;
|
||||
}
|
||||
|
||||
if (userDetailsServices.isEmpty()) {
|
||||
if (beanNames.length == 0) {
|
||||
return;
|
||||
}
|
||||
else if (userDetailsServices.size() > 1) {
|
||||
List<String> beanNames = userDetailsServices.stream().map(BeanWithName::getName).toList();
|
||||
else if (beanNames.length > 1) {
|
||||
this.logger.warn(LogMessage.format("Found %s UserDetailsService beans, with names %s. "
|
||||
+ "Global Authentication Manager will not use a UserDetailsService for username/password login. "
|
||||
+ "Consider publishing a single UserDetailsService bean.", userDetailsServices.size(),
|
||||
beanNames));
|
||||
+ "Consider publishing a single UserDetailsService bean.", beanNames.length,
|
||||
Arrays.toString(beanNames)));
|
||||
return;
|
||||
}
|
||||
UserDetailsService userDetailsService = userDetailsServices.get(0).getBean();
|
||||
String userDetailsServiceBeanName = userDetailsServices.get(0).getName();
|
||||
UserDetailsService userDetailsService = InitializeUserDetailsBeanManagerConfigurer.this.context
|
||||
.getBean(beanNames[0], UserDetailsService.class);
|
||||
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
|
||||
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
|
||||
CompromisedPasswordChecker passwordChecker = getBeanOrNull(CompromisedPasswordChecker.class);
|
||||
@@ -113,8 +112,7 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
|
||||
provider.afterPropertiesSet();
|
||||
auth.authenticationProvider(provider);
|
||||
this.logger.info(LogMessage.format(
|
||||
"Global AuthenticationManager configured with UserDetailsService bean with name %s",
|
||||
userDetailsServiceBeanName));
|
||||
"Global AuthenticationManager configured with UserDetailsService bean with name %s", beanNames[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,41 +123,6 @@ class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationCon
|
||||
return InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanProvider(type).getIfUnique();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a list of beans of the requested class, along with their names. If
|
||||
* there are no registered beans of that type, the list is empty.
|
||||
*/
|
||||
private <T> List<BeanWithName<T>> getBeansWithName(Class<T> type) {
|
||||
List<BeanWithName<T>> beanWithNames = new ArrayList<>();
|
||||
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
|
||||
for (String beanName : beanNames) {
|
||||
T bean = InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanName, type);
|
||||
beanWithNames.add(new BeanWithName<>(bean, beanName));
|
||||
}
|
||||
return beanWithNames;
|
||||
}
|
||||
|
||||
static class BeanWithName<T> {
|
||||
|
||||
private final T bean;
|
||||
|
||||
private final String name;
|
||||
|
||||
BeanWithName(T bean, String name) {
|
||||
this.bean = bean;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
T getBean() {
|
||||
return this.bean;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -140,6 +140,16 @@ public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuild
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public LdapAuthenticationProviderConfigurer<B> withObjectPostProcessor(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
|
||||
addObjectPostProcessor(objectPostProcessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link LdapAuthoritiesPopulator} and defaults to
|
||||
* {@link DefaultLdapAuthoritiesPopulator}
|
||||
|
||||
+12
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -63,6 +63,17 @@ public abstract class AbstractDaoAuthenticationConfigurer<B extends ProviderMana
|
||||
return (C) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
@SuppressWarnings("unchecked")
|
||||
public C withObjectPostProcessor(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
|
||||
addObjectPostProcessor(objectPostProcessor);
|
||||
return (C) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows specifying the {@link PasswordEncoder} to use with the
|
||||
* {@link DaoAuthenticationProvider}. The default is to use plain text.
|
||||
|
||||
+10
@@ -407,6 +407,16 @@ public class GlobalMethodSecurityConfiguration implements ImportAware, SmartInit
|
||||
this.objectPostProcessor = objectPostProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
@Autowired(required = false)
|
||||
public void setObjectPostProcessor(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
this.objectPostProcessor = objectPostProcessor;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setMethodSecurityExpressionHandler(List<MethodSecurityExpressionHandler> handlers) {
|
||||
if (handlers.size() != 1) {
|
||||
|
||||
+9
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
* Copyright 2019-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.annotation.rsocket;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -62,8 +64,12 @@ class RSocketSecurityConfiguration {
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAuthenticationManagerPostProcessor(ObjectPostProcessor<ReactiveAuthenticationManager> postProcessor) {
|
||||
this.postProcessor = postProcessor;
|
||||
void setAuthenticationManagerPostProcessor(
|
||||
Map<String, ObjectPostProcessor<ReactiveAuthenticationManager>> postProcessors) {
|
||||
if (postProcessors.size() == 1) {
|
||||
this.postProcessor = postProcessors.values().iterator().next();
|
||||
}
|
||||
this.postProcessor = postProcessors.get("rSocketAuthenticationManagerPostProcessor");
|
||||
}
|
||||
|
||||
@Bean(name = RSOCKET_SECURITY_BEAN_NAME)
|
||||
|
||||
+2
-18
@@ -29,9 +29,7 @@ import org.springframework.security.authorization.ObservationReactiveAuthorizati
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager;
|
||||
import org.springframework.security.config.ObjectPostProcessor;
|
||||
import org.springframework.security.config.observation.SecurityObservationSettings;
|
||||
import org.springframework.security.web.server.ObservationWebFilterChainDecorator;
|
||||
import org.springframework.security.web.server.WebFilterChainProxy.WebFilterChainDecorator;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.security.rsocket.api.PayloadExchange;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
@@ -45,7 +43,7 @@ class ReactiveObservationConfiguration {
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
static ObjectPostProcessor<ReactiveAuthorizationManager<ServerWebExchange>> rSocketAuthorizationManagerPostProcessor(
|
||||
static ObjectPostProcessor<ReactiveAuthorizationManager<PayloadExchange>> rSocketAuthorizationManagerPostProcessor(
|
||||
ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
|
||||
return new ObjectPostProcessor<>() {
|
||||
@Override
|
||||
@@ -71,18 +69,4 @@ class ReactiveObservationConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
static ObjectPostProcessor<WebFilterChainDecorator> rSocketFilterChainDecoratorPostProcessor(
|
||||
ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
|
||||
return new ObjectPostProcessor<>() {
|
||||
@Override
|
||||
public WebFilterChainDecorator postProcess(WebFilterChainDecorator object) {
|
||||
ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
|
||||
boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveRequests();
|
||||
return active ? new ObservationWebFilterChainDecorator(r) : object;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
@@ -188,6 +188,23 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
||||
this.requestMatcherConfigurer = new RequestMatcherConfigurer(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
@SuppressWarnings("unchecked")
|
||||
public HttpSecurity(org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor,
|
||||
AuthenticationManagerBuilder authenticationBuilder, Map<Class<?>, Object> sharedObjects) {
|
||||
super(objectPostProcessor);
|
||||
Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
|
||||
setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
|
||||
for (Map.Entry<Class<?>, Object> entry : sharedObjects.entrySet()) {
|
||||
setSharedObject((Class<Object>) entry.getKey(), entry.getValue());
|
||||
}
|
||||
ApplicationContext context = (ApplicationContext) sharedObjects.get(ApplicationContext.class);
|
||||
this.requestMatcherConfigurer = new RequestMatcherConfigurer(context);
|
||||
}
|
||||
|
||||
private ApplicationContext getContext() {
|
||||
return getSharedObject(ApplicationContext.class);
|
||||
}
|
||||
|
||||
+8
@@ -135,6 +135,14 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
super(objectPostProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public WebSecurity(org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor) {
|
||||
super(objectPostProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Allows adding {@link RequestMatcher} instances that Spring Security should ignore.
|
||||
|
||||
+11
@@ -178,6 +178,17 @@ class HttpSecurityConfiguration {
|
||||
this.defaultPasswordEncoder = defaultPasswordEncoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
DefaultPasswordEncoderAuthenticationManagerBuilder(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<Object> objectPostProcessor,
|
||||
PasswordEncoder defaultPasswordEncoder) {
|
||||
super(objectPostProcessor);
|
||||
this.defaultPasswordEncoder = defaultPasswordEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
|
||||
throws Exception {
|
||||
|
||||
-3
@@ -79,9 +79,6 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
@Autowired(required = false)
|
||||
private ObjectPostProcessor<Object> objectObjectPostProcessor;
|
||||
|
||||
@Autowired(required = false)
|
||||
private HttpSecurity httpSecurity;
|
||||
|
||||
|
||||
+12
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -54,6 +54,17 @@ public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T,
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withObjectPostProcessor(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
|
||||
addObjectPostProcessor(objectPostProcessor);
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
protected SecurityContextHolderStrategy getSecurityContextHolderStrategy() {
|
||||
if (this.securityContextHolderStrategy != null) {
|
||||
return this.securityContextHolderStrategy;
|
||||
|
||||
+11
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -193,6 +193,16 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public AuthorizationManagerRequestMatcherRegistry withObjectPostProcessor(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
|
||||
addObjectPostProcessor(objectPostProcessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether all dispatcher types should be filtered.
|
||||
* @param shouldFilter should filter all dispatcher types. Default is {@code true}
|
||||
|
||||
+11
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -169,6 +169,16 @@ public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public ChannelRequestMatcherRegistry withObjectPostProcessor(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
|
||||
addObjectPostProcessor(objectPostProcessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ChannelProcessor} instances to use in
|
||||
* {@link ChannelDecisionManagerImpl}
|
||||
|
||||
+10
@@ -238,6 +238,16 @@ public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBu
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public ExpressionInterceptUrlRegistry withObjectPostProcessor(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
|
||||
addObjectPostProcessor(objectPostProcessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public H and() {
|
||||
return ExpressionUrlAuthorizationConfigurer.this.and();
|
||||
}
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -117,6 +117,14 @@ public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
@Override
|
||||
public UrlAuthorizationConfigurer<H> withObjectPostProcessor(
|
||||
org.springframework.security.config.annotation.ObjectPostProcessor<?> objectPostProcessor) {
|
||||
addObjectPostProcessor(objectPostProcessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the default {@link AccessDecisionVoter} instances used if an
|
||||
* {@link AccessDecisionManager} was not specified.
|
||||
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
@@ -36,6 +37,9 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
*/
|
||||
class OidcBackChannelLogoutAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 9095810699956350287L;
|
||||
|
||||
private final OidcLogoutToken logoutToken;
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
+6
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
@@ -29,6 +31,9 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
*/
|
||||
class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -1568528983223505540L;
|
||||
|
||||
private final String logoutToken;
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ class ReactiveObservationConfiguration {
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
static ObjectPostProcessor<ReactiveAuthenticationManager> authenticationManagerPostProcessor(
|
||||
static ObjectPostProcessor<ReactiveAuthenticationManager> reactiveAuthenticationManagerPostProcessor(
|
||||
ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
|
||||
return new ObjectPostProcessor<>() {
|
||||
@Override
|
||||
|
||||
+8
-2
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.reactive;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
@@ -96,8 +98,12 @@ class ServerHttpSecurityConfiguration {
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAuthenticationManagerPostProcessor(ObjectPostProcessor<ReactiveAuthenticationManager> postProcessor) {
|
||||
this.postProcessor = postProcessor;
|
||||
void setAuthenticationManagerPostProcessor(
|
||||
Map<String, ObjectPostProcessor<ReactiveAuthenticationManager>> postProcessors) {
|
||||
if (postProcessors.size() == 1) {
|
||||
this.postProcessor = postProcessors.values().iterator().next();
|
||||
}
|
||||
this.postProcessor = postProcessors.get("reactiveAuthenticationManagerPostProcessor");
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -76,12 +76,13 @@ class AuthorizationFilterParser implements BeanDefinitionParser {
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
if (!isUseExpressions(element)) {
|
||||
parserContext.getReaderContext()
|
||||
.error("AuthorizationManager must be used with `use-expressions=\"true\"", element);
|
||||
.error("AuthorizationManager must be used with `use-expressions=\"true\"; either add `use-authorization-manager=\"false\"` or `use-expressions=`\"false\"` in your `<http>` block",
|
||||
element);
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.hasText(element.getAttribute(ATT_ACCESS_DECISION_MANAGER_REF))) {
|
||||
parserContext.getReaderContext()
|
||||
.error("AuthorizationManager cannot be used in conjunction with `access-decision-manager-ref`",
|
||||
.error("AuthorizationManager cannot be used in conjunction with `access-decision-manager-ref`; either remove the reference to AccessDecisionManager or add `use-authorization-manager=\"false\"` to your `<http>` block",
|
||||
element);
|
||||
return null;
|
||||
}
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.web.server;
|
||||
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Utility methods for {@link HttpMessageConverter}'s.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author luamas
|
||||
* @since 5.1
|
||||
*/
|
||||
final class HttpMessageConverters {
|
||||
|
||||
private static final boolean jackson2Present;
|
||||
|
||||
private static final boolean gsonPresent;
|
||||
|
||||
private static final boolean jsonbPresent;
|
||||
|
||||
static {
|
||||
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
|
||||
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
|
||||
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
|
||||
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
|
||||
jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader);
|
||||
}
|
||||
|
||||
private HttpMessageConverters() {
|
||||
}
|
||||
|
||||
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
|
||||
if (jackson2Present) {
|
||||
return new MappingJackson2HttpMessageConverter();
|
||||
}
|
||||
if (gsonPresent) {
|
||||
return new GsonHttpMessageConverter();
|
||||
}
|
||||
if (jsonbPresent) {
|
||||
return new JsonbHttpMessageConverter();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.web.server;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.HttpMessageEncoder;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
class OAuth2ErrorEncoder implements HttpMessageEncoder<OAuth2Error> {
|
||||
|
||||
private final HttpMessageConverter<Object> messageConverter = HttpMessageConverters.getJsonMessageConverter();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<MediaType> getStreamingMediaTypes() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEncode(ResolvableType elementType, MimeType mimeType) {
|
||||
return getEncodableMimeTypes().contains(mimeType);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Flux<DataBuffer> encode(Publisher<? extends OAuth2Error> error, DataBufferFactory bufferFactory,
|
||||
ResolvableType elementType, MimeType mimeType, Map<String, Object> hints) {
|
||||
return Mono.from(error).flatMap((data) -> {
|
||||
ByteArrayHttpOutputMessage bytes = new ByteArrayHttpOutputMessage();
|
||||
try {
|
||||
this.messageConverter.write(data, MediaType.APPLICATION_JSON, bytes);
|
||||
return Mono.just(bytes.getBody().toByteArray());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
}).map(bufferFactory::wrap).flux();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<MimeType> getEncodableMimeTypes() {
|
||||
return List.of(MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
private static class ByteArrayHttpOutputMessage implements HttpOutputMessage {
|
||||
|
||||
private final ByteArrayOutputStream body = new ByteArrayOutputStream();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ByteArrayOutputStream getBody() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return new HttpHeaders();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
@@ -36,6 +37,9 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
*/
|
||||
class OidcBackChannelLogoutAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 9095810699956350287L;
|
||||
|
||||
private final OidcLogoutToken logoutToken;
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
+15
-18
@@ -16,16 +16,17 @@
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
@@ -61,6 +62,9 @@ class OidcBackChannelLogoutWebFilter implements WebFilter {
|
||||
|
||||
private final ServerLogoutHandler logoutHandler;
|
||||
|
||||
private final HttpMessageWriter<OAuth2Error> errorHttpMessageConverter = new EncoderHttpMessageWriter<>(
|
||||
new OAuth2ErrorEncoder());
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcBackChannelLogoutWebFilter}
|
||||
* @param authenticationConverter the {@link AuthenticationConverter} for deriving
|
||||
@@ -85,7 +89,7 @@ class OidcBackChannelLogoutWebFilter implements WebFilter {
|
||||
if (ex instanceof AuthenticationServiceException) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
return handleAuthenticationFailure(exchange.getResponse(), ex).then(Mono.empty());
|
||||
return handleAuthenticationFailure(exchange, ex).then(Mono.empty());
|
||||
})
|
||||
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
|
||||
.flatMap(this.authenticationManager::authenticate)
|
||||
@@ -94,7 +98,7 @@ class OidcBackChannelLogoutWebFilter implements WebFilter {
|
||||
if (ex instanceof AuthenticationServiceException) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
return handleAuthenticationFailure(exchange.getResponse(), ex).then(Mono.empty());
|
||||
return handleAuthenticationFailure(exchange, ex).then(Mono.empty());
|
||||
})
|
||||
.flatMap((authentication) -> {
|
||||
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
|
||||
@@ -102,19 +106,12 @@ class OidcBackChannelLogoutWebFilter implements WebFilter {
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Void> handleAuthenticationFailure(ServerHttpResponse response, Exception ex) {
|
||||
private Mono<Void> handleAuthenticationFailure(ServerWebExchange exchange, Exception ex) {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
|
||||
response.setRawStatusCode(HttpServletResponse.SC_BAD_REQUEST);
|
||||
OAuth2Error error = oauth2Error(ex);
|
||||
byte[] bytes = String.format("""
|
||||
{
|
||||
"error_code": "%s",
|
||||
"error_description": "%s",
|
||||
"error_uri: "%s"
|
||||
}
|
||||
""", error.getErrorCode(), error.getDescription(), error.getUri()).getBytes(StandardCharsets.UTF_8);
|
||||
DataBuffer buffer = response.bufferFactory().wrap(bytes);
|
||||
return response.writeWith(Flux.just(buffer));
|
||||
exchange.getResponse().setRawStatusCode(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return this.errorHttpMessageConverter.write(Mono.just(oauth2Error(ex)), ResolvableType.forClass(Object.class),
|
||||
ResolvableType.forClass(Object.class), MediaType.APPLICATION_JSON, exchange.getRequest(),
|
||||
exchange.getResponse(), Collections.emptyMap());
|
||||
}
|
||||
|
||||
private OAuth2Error oauth2Error(Exception ex) {
|
||||
|
||||
+15
-16
@@ -16,8 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -25,14 +25,15 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation;
|
||||
@@ -44,6 +45,7 @@ import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@@ -63,6 +65,9 @@ public final class OidcBackChannelServerLogoutHandler implements ServerLogoutHan
|
||||
|
||||
private final ReactiveOidcSessionRegistry sessionRegistry;
|
||||
|
||||
private final HttpMessageWriter<OAuth2Error> errorHttpMessageConverter = new EncoderHttpMessageWriter<>(
|
||||
new OAuth2ErrorEncoder());
|
||||
|
||||
private WebClient web = WebClient.create();
|
||||
|
||||
private String logoutUri = "{baseUrl}/logout/connect/back-channel/{registrationId}";
|
||||
@@ -101,7 +106,7 @@ public final class OidcBackChannelServerLogoutHandler implements ServerLogoutHan
|
||||
totalCount.intValue()));
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
return handleLogoutFailure(exchange.getExchange().getResponse(), oauth2Error(list));
|
||||
return handleLogoutFailure(exchange.getExchange(), oauth2Error(list));
|
||||
}
|
||||
else {
|
||||
return Mono.empty();
|
||||
@@ -164,17 +169,11 @@ public final class OidcBackChannelServerLogoutHandler implements ServerLogoutHan
|
||||
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
|
||||
}
|
||||
|
||||
private Mono<Void> handleLogoutFailure(ServerHttpResponse response, OAuth2Error error) {
|
||||
response.setRawStatusCode(HttpServletResponse.SC_BAD_REQUEST);
|
||||
byte[] bytes = String.format("""
|
||||
{
|
||||
"error_code": "%s",
|
||||
"error_description": "%s",
|
||||
"error_uri: "%s"
|
||||
}
|
||||
""", error.getErrorCode(), error.getDescription(), error.getUri()).getBytes(StandardCharsets.UTF_8);
|
||||
DataBuffer buffer = response.bufferFactory().wrap(bytes);
|
||||
return response.writeWith(Flux.just(buffer));
|
||||
private Mono<Void> handleLogoutFailure(ServerWebExchange exchange, OAuth2Error error) {
|
||||
exchange.getResponse().setRawStatusCode(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return this.errorHttpMessageConverter.write(Mono.just(error), ResolvableType.forClass(Object.class),
|
||||
ResolvableType.forClass(Object.class), MediaType.APPLICATION_JSON, exchange.getRequest(),
|
||||
exchange.getResponse(), Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
@@ -29,6 +31,9 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
*/
|
||||
class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -1568528983223505540L;
|
||||
|
||||
private final String logoutToken;
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
+171
-31
@@ -25,17 +25,20 @@ import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.ObjectStreamClass;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apereo.cas.client.validation.AssertionImpl;
|
||||
@@ -44,6 +47,7 @@ import org.instancio.InstancioApi;
|
||||
import org.instancio.Select;
|
||||
import org.instancio.generator.Generator;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
@@ -51,26 +55,42 @@ import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.filter.AssignableTypeFilter;
|
||||
import org.springframework.security.access.intercept.RunAsUserToken;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.RememberMeAuthenticationToken;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.authentication.jaas.JaasAuthenticationToken;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken;
|
||||
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
|
||||
import org.springframework.security.cas.authentication.CasAuthenticationToken;
|
||||
import org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.session.ReactiveSessionInformation;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.ldap.userdetails.LdapAuthority;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthenticationTokens;
|
||||
import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthorizationCodeAuthenticationTokens;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation;
|
||||
import org.springframework.security.oauth2.client.oidc.session.TestOidcSessionInformations;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
import org.springframework.security.oauth2.core.OAuth2DeviceCode;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2UserCode;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2AuthenticatedPrincipals;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||
@@ -79,14 +99,30 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||
import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
import org.springframework.security.oauth2.core.user.TestOAuth2Users;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.TestJwts;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenError;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
|
||||
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.authentication.TestSaml2Authentications;
|
||||
import org.springframework.security.saml2.provider.service.authentication.TestSaml2PostAuthenticationRequests;
|
||||
import org.springframework.security.saml2.provider.service.authentication.TestSaml2RedirectAuthenticationRequests;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
|
||||
@@ -118,23 +154,14 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
static Path previousVersionFolder = Paths.get("src/test/resources/serialized/" + getPreviousVersion());
|
||||
|
||||
static {
|
||||
ClientRegistration.Builder clientRegistrationBuilder = TestClientRegistrations.clientRegistration();
|
||||
ClientRegistration clientRegistration = clientRegistrationBuilder.build();
|
||||
UserDetails user = TestAuthentication.user();
|
||||
WebAuthenticationDetails details = new WebAuthenticationDetails("remote", "sessionId");
|
||||
|
||||
// oauth2-core
|
||||
generatorByClassName.put(DefaultOAuth2User.class, (r) -> TestOAuth2Users.create());
|
||||
generatorByClassName.put(ClientRegistration.class, (r) -> clientRegistration);
|
||||
generatorByClassName.put(ClientRegistration.ProviderDetails.class,
|
||||
(r) -> clientRegistration.getProviderDetails());
|
||||
generatorByClassName.put(ClientRegistration.ProviderDetails.UserInfoEndpoint.class,
|
||||
(r) -> clientRegistration.getProviderDetails().getUserInfoEndpoint());
|
||||
generatorByClassName.put(ClientRegistration.Builder.class, (r) -> clientRegistrationBuilder);
|
||||
generatorByClassName.put(OAuth2AuthorizationRequest.class,
|
||||
(r) -> TestOAuth2AuthorizationRequests.request().build());
|
||||
generatorByClassName.put(OAuth2AuthorizationResponse.class,
|
||||
(r) -> TestOAuth2AuthorizationResponses.success().build());
|
||||
generatorByClassName.put(OAuth2AuthorizedClient.class,
|
||||
(r) -> new OAuth2AuthorizedClient(clientRegistration, "principal", TestOAuth2AccessTokens.noScopes()));
|
||||
generatorByClassName.put(OAuth2UserAuthority.class, (r) -> new OAuth2UserAuthority(Map.of("username", "user")));
|
||||
generatorByClassName.put(OAuth2AuthorizationExchange.class, (r) -> TestOAuth2AuthorizationExchanges.success());
|
||||
generatorByClassName.put(OidcUserInfo.class, (r) -> OidcUserInfo.builder().email("email@example.com").build());
|
||||
@@ -142,6 +169,30 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
(r) -> new SessionInformation(user, r.alphanumeric(4), new Date(1704378933936L)));
|
||||
generatorByClassName.put(ReactiveSessionInformation.class,
|
||||
(r) -> new ReactiveSessionInformation(user, r.alphanumeric(4), Instant.ofEpochMilli(1704378933936L)));
|
||||
generatorByClassName.put(OAuth2AccessToken.class, (r) -> TestOAuth2AccessTokens.scopes("scope"));
|
||||
generatorByClassName.put(OAuth2DeviceCode.class,
|
||||
(r) -> new OAuth2DeviceCode("token", Instant.now(), Instant.now()));
|
||||
generatorByClassName.put(OAuth2RefreshToken.class,
|
||||
(r) -> new OAuth2RefreshToken("refreshToken", Instant.now(), Instant.now()));
|
||||
generatorByClassName.put(OAuth2UserCode.class,
|
||||
(r) -> new OAuth2UserCode("token", Instant.now(), Instant.now()));
|
||||
generatorByClassName.put(DefaultOidcUser.class, (r) -> TestOidcUsers.create());
|
||||
generatorByClassName.put(OidcUserAuthority.class,
|
||||
(r) -> new OidcUserAuthority(TestOidcIdTokens.idToken().build(),
|
||||
new OidcUserInfo(Map.of("claim", "value")), "claim"));
|
||||
|
||||
// oauth2-client
|
||||
ClientRegistration.Builder clientRegistrationBuilder = TestClientRegistrations.clientRegistration();
|
||||
ClientRegistration clientRegistration = clientRegistrationBuilder.build();
|
||||
WebAuthenticationDetails details = new WebAuthenticationDetails("remote", "sessionId");
|
||||
generatorByClassName.put(ClientRegistration.class, (r) -> clientRegistration);
|
||||
generatorByClassName.put(ClientRegistration.ProviderDetails.class,
|
||||
(r) -> clientRegistration.getProviderDetails());
|
||||
generatorByClassName.put(ClientRegistration.ProviderDetails.UserInfoEndpoint.class,
|
||||
(r) -> clientRegistration.getProviderDetails().getUserInfoEndpoint());
|
||||
generatorByClassName.put(ClientRegistration.Builder.class, (r) -> clientRegistrationBuilder);
|
||||
generatorByClassName.put(OAuth2AuthorizedClient.class,
|
||||
(r) -> new OAuth2AuthorizedClient(clientRegistration, "principal", TestOAuth2AccessTokens.noScopes()));
|
||||
generatorByClassName.put(OAuth2LoginAuthenticationToken.class, (r) -> {
|
||||
var token = new OAuth2LoginAuthenticationToken(clientRegistration,
|
||||
TestOAuth2AuthorizationExchanges.success());
|
||||
@@ -153,6 +204,25 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(OAuth2AuthenticationToken.class, (r) -> {
|
||||
var token = TestOAuth2AuthenticationTokens.authenticated();
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(OidcIdToken.class, (r) -> TestOidcIdTokens.idToken().build());
|
||||
generatorByClassName.put(OidcLogoutToken.class,
|
||||
(r) -> TestOidcLogoutTokens.withSessionId("issuer", "sessionId").issuedAt(Instant.now()).build());
|
||||
generatorByClassName.put(OidcSessionInformation.class, (r) -> TestOidcSessionInformations.create());
|
||||
generatorByClassName.put(DefaultOAuth2AuthenticatedPrincipal.class, (r) -> {
|
||||
OAuth2AuthenticatedPrincipal principal = TestOAuth2AuthenticatedPrincipals.active();
|
||||
return new DefaultOAuth2AuthenticatedPrincipal(principal.getName(), principal.getAttributes(),
|
||||
(Collection<GrantedAuthority>) principal.getAuthorities());
|
||||
});
|
||||
|
||||
// oauth2-jwt
|
||||
generatorByClassName.put(Jwt.class, (r) -> TestJwts.user());
|
||||
|
||||
// oauth2-resource-server
|
||||
generatorByClassName
|
||||
.put(org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken.class, (r) -> {
|
||||
var token = new org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken(
|
||||
@@ -171,22 +241,44 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(OAuth2AuthenticationToken.class, (r) -> {
|
||||
var token = TestOAuth2AuthenticationTokens.authenticated();
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(JwtAuthenticationToken.class, (r) -> {
|
||||
var token = new JwtAuthenticationToken(TestJwts.user());
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(BearerTokenError.class, (r) -> BearerTokenErrors.invalidToken("invalid token"));
|
||||
generatorByClassName.put(OAuth2IntrospectionAuthenticatedPrincipal.class,
|
||||
(r) -> TestOAuth2AuthenticatedPrincipals.active());
|
||||
|
||||
// core
|
||||
generatorByClassName.put(RunAsUserToken.class, (r) -> {
|
||||
RunAsUserToken token = new RunAsUserToken("key", user, "creds", user.getAuthorities(),
|
||||
AnonymousAuthenticationToken.class);
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(RememberMeAuthenticationToken.class, (r) -> {
|
||||
RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("key", user, user.getAuthorities());
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(UsernamePasswordAuthenticationToken.class, (r) -> {
|
||||
var token = UsernamePasswordAuthenticationToken.unauthenticated(user, "creds");
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(JaasAuthenticationToken.class, (r) -> {
|
||||
var token = new JaasAuthenticationToken(user, "creds", null);
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(OneTimeTokenAuthenticationToken.class,
|
||||
(r) -> applyDetails(new OneTimeTokenAuthenticationToken("username", "token")));
|
||||
|
||||
generatorByClassName.put(TestingAuthenticationToken.class,
|
||||
(r) -> applyDetails(new TestingAuthenticationToken("username", "password")));
|
||||
|
||||
// cas
|
||||
generatorByClassName.put(CasServiceTicketAuthenticationToken.class, (r) -> {
|
||||
CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateless("creds");
|
||||
token.setDetails(details);
|
||||
@@ -203,10 +295,25 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(RememberMeAuthenticationToken.class, (r) -> {
|
||||
RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("key", user, user.getAuthorities());
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
|
||||
// ldap
|
||||
generatorByClassName.put(LdapAuthority.class,
|
||||
(r) -> new LdapAuthority("USER", "username", Map.of("attribute", List.of("value1", "value2"))));
|
||||
|
||||
// saml2-service-provider
|
||||
generatorByClassName.put(DefaultSaml2AuthenticatedPrincipal.class,
|
||||
(r) -> TestSaml2Authentications.authentication().getPrincipal());
|
||||
generatorByClassName.put(Saml2Authentication.class,
|
||||
(r) -> applyDetails(TestSaml2Authentications.authentication()));
|
||||
generatorByClassName.put(Saml2PostAuthenticationRequest.class,
|
||||
(r) -> TestSaml2PostAuthenticationRequests.create());
|
||||
generatorByClassName.put(Saml2RedirectAuthenticationRequest.class,
|
||||
(r) -> TestSaml2RedirectAuthenticationRequests.create());
|
||||
|
||||
// web
|
||||
generatorByClassName.put(AnonymousAuthenticationToken.class, (r) -> {
|
||||
Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
|
||||
return applyDetails(new AnonymousAuthenticationToken("key", "username", authorities));
|
||||
});
|
||||
generatorByClassName.put(PreAuthenticatedAuthenticationToken.class, (r) -> {
|
||||
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(user, "creds",
|
||||
@@ -214,16 +321,6 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(UsernamePasswordAuthenticationToken.class, (r) -> {
|
||||
var token = UsernamePasswordAuthenticationToken.unauthenticated(user, "creds");
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(JaasAuthenticationToken.class, (r) -> {
|
||||
var token = new JaasAuthenticationToken(user, "creds", null);
|
||||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@@ -277,6 +374,39 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
return Files.list(previousVersionFolder);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listClassesMissingSerialVersion() throws Exception {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.addIncludeFilter(new AssignableTypeFilter(Serializable.class));
|
||||
List<Class<?>> classes = new ArrayList<>();
|
||||
|
||||
Set<BeanDefinition> components = provider.findCandidateComponents("org/springframework/security");
|
||||
for (BeanDefinition component : components) {
|
||||
Class<?> clazz = Class.forName(component.getBeanClassName());
|
||||
boolean isAbstract = Modifier.isAbstract(clazz.getModifiers());
|
||||
if (isAbstract) {
|
||||
continue;
|
||||
}
|
||||
if (clazz.isEnum()) {
|
||||
continue;
|
||||
}
|
||||
if (clazz.getName().contains("Tests")) {
|
||||
continue;
|
||||
}
|
||||
boolean hasSerialVersion = Stream.of(clazz.getDeclaredFields())
|
||||
.map(Field::getName)
|
||||
.anyMatch((n) -> n.equals("serialVersionUID"));
|
||||
if (!hasSerialVersion) {
|
||||
classes.add(clazz);
|
||||
}
|
||||
}
|
||||
if (!classes.isEmpty()) {
|
||||
System.out
|
||||
.println("Found " + classes.size() + " Serializable classes that don't declare a seriallVersionUID");
|
||||
System.out.println(classes.stream().map(Class::getName).collect(Collectors.joining("\r\n")));
|
||||
}
|
||||
}
|
||||
|
||||
static Stream<Class<?>> getClassesToSerialize() throws Exception {
|
||||
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
|
||||
provider.addIncludeFilter(new AssignableTypeFilter(Serializable.class));
|
||||
@@ -286,9 +416,13 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
for (BeanDefinition component : components) {
|
||||
Class<?> clazz = Class.forName(component.getBeanClassName());
|
||||
boolean isAbstract = Modifier.isAbstract(clazz.getModifiers());
|
||||
if (isAbstract) {
|
||||
continue;
|
||||
}
|
||||
boolean matchesExpectedSerialVersion = ObjectStreamClass.lookup(clazz)
|
||||
.getSerialVersionUID() == securitySerialVersionUid;
|
||||
if (!isAbstract && matchesExpectedSerialVersion) {
|
||||
boolean isUnderTest = generatorByClassName.containsKey(clazz);
|
||||
if (matchesExpectedSerialVersion || isUnderTest) {
|
||||
classes.add(clazz);
|
||||
}
|
||||
}
|
||||
@@ -303,6 +437,12 @@ class SpringSecurityCoreVersionSerializableTests {
|
||||
return instancio;
|
||||
}
|
||||
|
||||
private static <T extends AbstractAuthenticationToken> T applyDetails(T authentication) {
|
||||
WebAuthenticationDetails details = new WebAuthenticationDetails("remote", "sessionId");
|
||||
authentication.setDetails(details);
|
||||
return authentication;
|
||||
}
|
||||
|
||||
private static String getCurrentVersion() {
|
||||
String version = System.getProperty("springSecurityVersion");
|
||||
String[] parts = version.split("\\.");
|
||||
|
||||
+25
-3
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.reactive;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -28,6 +32,7 @@ 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.AliasFor;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
@@ -404,11 +409,28 @@ public class EnableWebFluxSecurityTests {
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class AuthenticationPrincipalResolver {
|
||||
@Target({ ElementType.PARAMETER })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@AuthenticationPrincipal
|
||||
@interface Property {
|
||||
|
||||
@AliasFor(attribute = "expression", annotation = AuthenticationPrincipal.class)
|
||||
String value() default "id";
|
||||
|
||||
}
|
||||
|
||||
interface UsernameResolver {
|
||||
|
||||
String username(@Property("@principalBean.username(#this)") String username);
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class AuthenticationPrincipalResolver implements UsernameResolver {
|
||||
|
||||
@Override
|
||||
@GetMapping("/spel")
|
||||
String username(@AuthenticationPrincipal(expression = "@principalBean.username(#this)") String username) {
|
||||
public String username(String username) {
|
||||
return username;
|
||||
}
|
||||
|
||||
|
||||
+106
@@ -21,20 +21,27 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.net.URI;
|
||||
import java.util.Iterator;
|
||||
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationHandler;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
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.messaging.rsocket.annotation.support.RSocketMessageHandler;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.password.CompromisedPasswordDecision;
|
||||
import org.springframework.security.authentication.password.CompromisedPasswordException;
|
||||
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
|
||||
@@ -60,6 +67,12 @@ import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockAuthentication;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
|
||||
@@ -205,6 +218,55 @@ public class ServerHttpSecurityConfigurationTests {
|
||||
.isEqualTo("harold");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenUsingObservationRegistryThenObservesRequest() {
|
||||
this.spring.register(ObservationRegistryConfig.class).autowire();
|
||||
// @formatter:off
|
||||
this.webClient
|
||||
.get()
|
||||
.uri("/hello")
|
||||
.headers((headers) -> headers.setBasicAuth("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isNotFound();
|
||||
// @formatter:on
|
||||
ObservationHandler<Observation.Context> handler = this.spring.getContext().getBean(ObservationHandler.class);
|
||||
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
|
||||
verify(handler, times(6)).onStart(captor.capture());
|
||||
Iterator<Observation.Context> contexts = captor.getAllValues().iterator();
|
||||
assertThat(contexts.next().getContextualName()).isEqualTo("http get");
|
||||
assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain before");
|
||||
assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications");
|
||||
assertThat(contexts.next().getName()).isEqualTo("spring.security.authorizations");
|
||||
assertThat(contexts.next().getName()).isEqualTo("spring.security.http.secured.requests");
|
||||
assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after");
|
||||
}
|
||||
|
||||
// gh-16161
|
||||
@Test
|
||||
public void getWhenUsingRSocketThenObservesRequest() {
|
||||
this.spring.register(ObservationRegistryConfig.class, RSocketSecurityConfig.class).autowire();
|
||||
// @formatter:off
|
||||
this.webClient
|
||||
.get()
|
||||
.uri("/hello")
|
||||
.headers((headers) -> headers.setBasicAuth("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isNotFound();
|
||||
// @formatter:on
|
||||
ObservationHandler<Observation.Context> handler = this.spring.getContext().getBean(ObservationHandler.class);
|
||||
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
|
||||
verify(handler, times(6)).onStart(captor.capture());
|
||||
Iterator<Observation.Context> contexts = captor.getAllValues().iterator();
|
||||
assertThat(contexts.next().getContextualName()).isEqualTo("http get");
|
||||
assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain before");
|
||||
assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications");
|
||||
assertThat(contexts.next().getName()).isEqualTo("spring.security.authorizations");
|
||||
assertThat(contexts.next().getName()).isEqualTo("spring.security.http.secured.requests");
|
||||
assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SubclassConfig extends ServerHttpSecurityConfiguration {
|
||||
|
||||
@@ -368,4 +430,48 @@ public class ServerHttpSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class ObservationRegistryConfig {
|
||||
|
||||
private ObservationHandler<Observation.Context> handler = mock(ObservationHandler.class);
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain app(ServerHttpSecurity http) throws Exception {
|
||||
http.httpBasic(withDefaults()).authorizeExchange((authorize) -> authorize.anyExchange().authenticated());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveUserDetailsService userDetailsService() {
|
||||
return new MapReactiveUserDetailsService(
|
||||
User.withDefaultPasswordEncoder().username("user").password("password").authorities("app").build());
|
||||
}
|
||||
|
||||
@Bean
|
||||
ObservationHandler<Observation.Context> observationHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ObservationRegistry observationRegistry() {
|
||||
given(this.handler.supportsContext(any())).willReturn(true);
|
||||
ObservationRegistry registry = ObservationRegistry.create();
|
||||
registry.observationConfig().observationHandler(this.handler);
|
||||
return registry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableRSocketSecurity
|
||||
static class RSocketSecurityConfig {
|
||||
|
||||
@Bean
|
||||
RSocketMessageHandler messageHandler() {
|
||||
return new RSocketMessageHandler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
|
||||
+10
-4
@@ -51,6 +51,7 @@ 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.ParameterizedTypeReference;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
@@ -101,6 +102,7 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.hasValue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
@@ -199,7 +201,10 @@ public class OidcLogoutSpecTests {
|
||||
.body(BodyInserters.fromFormData("logout_token", "invalid"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isBadRequest();
|
||||
.isBadRequest()
|
||||
.expectBody(new ParameterizedTypeReference<Map<String, String>>() {
|
||||
})
|
||||
.value(hasValue("invalid_request"));
|
||||
this.test.get().uri("/token/logout").cookie("SESSION", session).exchange().expectStatus().isOk();
|
||||
}
|
||||
|
||||
@@ -266,9 +271,10 @@ public class OidcLogoutSpecTests {
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isBadRequest()
|
||||
.expectBody(String.class)
|
||||
.value(containsString("partial_logout"))
|
||||
.value(containsString("not all sessions were terminated"));
|
||||
.expectBody(new ParameterizedTypeReference<Map<String, String>>() {
|
||||
})
|
||||
.value(hasValue("partial_logout"))
|
||||
.value(hasValue(containsString("not all sessions were terminated")));
|
||||
this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isOk();
|
||||
}
|
||||
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user