Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3e7d76b70 |
@@ -2,10 +2,6 @@ name: Finalize Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: # Manual trigger
|
workflow_dispatch: # Manual trigger
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: The Spring Security release to finalize (e.g. 7.0.0-RC2)
|
|
||||||
required: true
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||||
@@ -14,14 +10,32 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
project-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.project-version.outputs.version }}
|
||||||
|
steps:
|
||||||
|
- id: project-version
|
||||||
|
run: echo "version=$(grep '^version=' gradle.properties | cut -d'=' -f2)" >> $GITHUB_OUTPUT
|
||||||
perform-release:
|
perform-release:
|
||||||
name: Perform Release
|
name: Perform Release
|
||||||
|
needs: [ project-version ]
|
||||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
|
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
|
||||||
with:
|
with:
|
||||||
should-perform-release: true
|
should-perform-release: true
|
||||||
project-version: ${{ inputs.version }}
|
project-version: ${{ needs.project-version.outputs.version }}
|
||||||
milestone-repo-url: https://repo1.maven.org/maven2
|
milestone-repo-url: https://repo1.maven.org/maven2
|
||||||
release-repo-url: https://repo1.maven.org/maven2
|
release-repo-url: https://repo1.maven.org/maven2
|
||||||
artifact-path: org/springframework/security/spring-security-core
|
artifact-path: org/springframework/security/spring-security-core
|
||||||
slack-announcing-id: spring-security-announcing
|
slack-announcing-id: spring-security-announcing
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
send-notification:
|
||||||
|
name: Send Notification
|
||||||
|
needs: [ perform-release ]
|
||||||
|
if: ${{ !success() }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Send Notification
|
||||||
|
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
|
||||||
|
with:
|
||||||
|
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||||
|
|||||||
+10
-97
@@ -31,7 +31,6 @@ import org.junit.jupiter.api.AfterAll;
|
|||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.openqa.selenium.By;
|
import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.WebDriverException;
|
import org.openqa.selenium.WebDriverException;
|
||||||
@@ -56,7 +55,6 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
|||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||||
import org.springframework.security.web.FilterChainProxy;
|
import org.springframework.security.web.FilterChainProxy;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
@@ -69,7 +67,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
*
|
*
|
||||||
* @author Daniel Garnier-Moiroux
|
* @author Daniel Garnier-Moiroux
|
||||||
*/
|
*/
|
||||||
@Disabled
|
@org.junit.jupiter.api.Disabled
|
||||||
class WebAuthnWebDriverTests {
|
class WebAuthnWebDriverTests {
|
||||||
|
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
@@ -84,8 +82,6 @@ class WebAuthnWebDriverTests {
|
|||||||
|
|
||||||
private static final String PASSWORD = "password";
|
private static final String PASSWORD = "password";
|
||||||
|
|
||||||
private String authenticatorId = null;
|
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void startChromeDriverService() throws Exception {
|
static void startChromeDriverService() throws Exception {
|
||||||
driverService = new ChromeDriverService.Builder().usingAnyFreePort().build();
|
driverService = new ChromeDriverService.Builder().usingAnyFreePort().build();
|
||||||
@@ -148,7 +144,7 @@ class WebAuthnWebDriverTests {
|
|||||||
@Test
|
@Test
|
||||||
void loginWhenNoValidAuthenticatorCredentialsThenRejects() {
|
void loginWhenNoValidAuthenticatorCredentialsThenRejects() {
|
||||||
createVirtualAuthenticator(true);
|
createVirtualAuthenticator(true);
|
||||||
this.getAndWait("/", "/login");
|
this.driver.get(this.baseUrl);
|
||||||
this.driver.findElement(signinWithPasskeyButton()).click();
|
this.driver.findElement(signinWithPasskeyButton()).click();
|
||||||
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error"));
|
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error"));
|
||||||
}
|
}
|
||||||
@@ -157,7 +153,7 @@ class WebAuthnWebDriverTests {
|
|||||||
void registerWhenNoLabelThenRejects() {
|
void registerWhenNoLabelThenRejects() {
|
||||||
login();
|
login();
|
||||||
|
|
||||||
this.getAndWait("/webauthn/register");
|
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||||
|
|
||||||
this.driver.findElement(registerPasskeyButton()).click();
|
this.driver.findElement(registerPasskeyButton()).click();
|
||||||
assertHasAlertStartingWith("error", "Error: Passkey Label is required");
|
assertHasAlertStartingWith("error", "Error: Passkey Label is required");
|
||||||
@@ -167,7 +163,7 @@ class WebAuthnWebDriverTests {
|
|||||||
void registerWhenAuthenticatorNoUserVerificationThenRejects() {
|
void registerWhenAuthenticatorNoUserVerificationThenRejects() {
|
||||||
createVirtualAuthenticator(false);
|
createVirtualAuthenticator(false);
|
||||||
login();
|
login();
|
||||||
this.getAndWait("/webauthn/register");
|
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||||
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
|
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
|
||||||
this.driver.findElement(registerPasskeyButton()).click();
|
this.driver.findElement(registerPasskeyButton()).click();
|
||||||
|
|
||||||
@@ -182,8 +178,7 @@ class WebAuthnWebDriverTests {
|
|||||||
* <li>Step 1: Log in with username / password</li>
|
* <li>Step 1: Log in with username / password</li>
|
||||||
* <li>Step 2: Register a credential from the virtual authenticator</li>
|
* <li>Step 2: Register a credential from the virtual authenticator</li>
|
||||||
* <li>Step 3: Log out</li>
|
* <li>Step 3: Log out</li>
|
||||||
* <li>Step 4: Log in with the authenticator (no allowCredentials)</li>
|
* <li>Step 4: Log in with the authenticator</li>
|
||||||
* <li>Step 5: Log in again with the same authenticator (with allowCredentials)</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -195,7 +190,7 @@ class WebAuthnWebDriverTests {
|
|||||||
login();
|
login();
|
||||||
|
|
||||||
// Step 2: register a credential from the virtual authenticator
|
// Step 2: register a credential from the virtual authenticator
|
||||||
this.getAndWait("/webauthn/register");
|
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||||
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
|
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
|
||||||
this.driver.findElement(registerPasskeyButton()).click();
|
this.driver.findElement(registerPasskeyButton()).click();
|
||||||
|
|
||||||
@@ -217,58 +212,9 @@ class WebAuthnWebDriverTests {
|
|||||||
logout();
|
logout();
|
||||||
|
|
||||||
// Step 4: log in with the virtual authenticator
|
// Step 4: log in with the virtual authenticator
|
||||||
this.getAndWait("/webauthn/register", "/login");
|
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||||
this.driver.findElement(signinWithPasskeyButton()).click();
|
this.driver.findElement(signinWithPasskeyButton()).click();
|
||||||
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue"));
|
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue"));
|
||||||
|
|
||||||
// Step 5: authenticate while being already logged in
|
|
||||||
// This simulates some use-cases with MFA. Since the user is already logged in,
|
|
||||||
// the "allowCredentials" property is populated
|
|
||||||
this.getAndWait("/login");
|
|
||||||
this.driver.findElement(signinWithPasskeyButton()).click();
|
|
||||||
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void registerWhenAuthenticatorAlreadyRegisteredThenRejects() {
|
|
||||||
createVirtualAuthenticator(true);
|
|
||||||
login();
|
|
||||||
registerAuthenticator("Virtual authenticator");
|
|
||||||
|
|
||||||
// Cannot re-register the same authenticator because excludeCredentials
|
|
||||||
// is not empty and contains the given authenticator
|
|
||||||
this.driver.findElement(passkeyLabel()).sendKeys("Same authenticator");
|
|
||||||
this.driver.findElement(registerPasskeyButton()).click();
|
|
||||||
|
|
||||||
await(() -> assertHasAlertStartingWith("error", "Registration failed"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void registerSecondAuthenticatorThenSucceeds() {
|
|
||||||
createVirtualAuthenticator(true);
|
|
||||||
login();
|
|
||||||
|
|
||||||
registerAuthenticator("Virtual authenticator");
|
|
||||||
this.getAndWait("/webauthn/register");
|
|
||||||
List<WebElement> passkeyRows = this.driver.findElements(passkeyTableRows());
|
|
||||||
assertThat(passkeyRows).hasSize(1)
|
|
||||||
.first()
|
|
||||||
.extracting((row) -> row.findElement(firstCell()))
|
|
||||||
.extracting(WebElement::getText)
|
|
||||||
.isEqualTo("Virtual authenticator");
|
|
||||||
|
|
||||||
// Create second authenticator and register
|
|
||||||
removeAuthenticator();
|
|
||||||
createVirtualAuthenticator(true);
|
|
||||||
registerAuthenticator("Second virtual authenticator");
|
|
||||||
|
|
||||||
this.getAndWait("/webauthn/register");
|
|
||||||
|
|
||||||
passkeyRows = this.driver.findElements(passkeyTableRows());
|
|
||||||
assertThat(passkeyRows).hasSize(2)
|
|
||||||
.extracting((row) -> row.findElement(firstCell()))
|
|
||||||
.extracting(WebElement::getText)
|
|
||||||
.contains("Second virtual authenticator");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,14 +231,11 @@ class WebAuthnWebDriverTests {
|
|||||||
* "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a>
|
* "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a>
|
||||||
*/
|
*/
|
||||||
private void createVirtualAuthenticator(boolean userIsVerified) {
|
private void createVirtualAuthenticator(boolean userIsVerified) {
|
||||||
if (StringUtils.hasText(this.authenticatorId)) {
|
|
||||||
throw new IllegalStateException("Authenticator already exists, please remove it before re-creating one");
|
|
||||||
}
|
|
||||||
HasCdp cdpDriver = (HasCdp) this.driver;
|
HasCdp cdpDriver = (HasCdp) this.driver;
|
||||||
cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false));
|
cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false));
|
||||||
// this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions());
|
// this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions());
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
Map<String, Object> cmdResponse = cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
|
cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
|
||||||
Map.of(
|
Map.of(
|
||||||
"options",
|
"options",
|
||||||
Map.of(
|
Map.of(
|
||||||
@@ -305,38 +248,21 @@ class WebAuthnWebDriverTests {
|
|||||||
)
|
)
|
||||||
));
|
));
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
this.authenticatorId = cmdResponse.get("authenticatorId").toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeAuthenticator() {
|
|
||||||
HasCdp cdpDriver = (HasCdp) this.driver;
|
|
||||||
cdpDriver.executeCdpCommand("WebAuthn.removeVirtualAuthenticator",
|
|
||||||
Map.of("authenticatorId", this.authenticatorId));
|
|
||||||
this.authenticatorId = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void login() {
|
private void login() {
|
||||||
this.getAndWait("/", "/login");
|
this.driver.get(this.baseUrl);
|
||||||
this.driver.findElement(usernameField()).sendKeys(USERNAME);
|
this.driver.findElement(usernameField()).sendKeys(USERNAME);
|
||||||
this.driver.findElement(passwordField()).sendKeys(PASSWORD);
|
this.driver.findElement(passwordField()).sendKeys(PASSWORD);
|
||||||
this.driver.findElement(signinWithUsernamePasswordButton()).click();
|
this.driver.findElement(signinWithUsernamePasswordButton()).click();
|
||||||
// Ensure login has completed
|
|
||||||
await(() -> assertThat(this.driver.getCurrentUrl()).doesNotContain("/login"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logout() {
|
private void logout() {
|
||||||
this.getAndWait("/logout");
|
this.driver.get(this.baseUrl + "/logout");
|
||||||
this.driver.findElement(logoutButton()).click();
|
this.driver.findElement(logoutButton()).click();
|
||||||
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout"));
|
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerAuthenticator(String passkeyName) {
|
|
||||||
this.getAndWait("/webauthn/register");
|
|
||||||
this.driver.findElement(passkeyLabel()).sendKeys(passkeyName);
|
|
||||||
this.driver.findElement(registerPasskeyButton()).click();
|
|
||||||
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?success"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) {
|
private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) {
|
||||||
WebElement alert = this.driver.findElement(new By.ById(alertType));
|
WebElement alert = this.driver.findElement(new By.ById(alertType));
|
||||||
assertThat(alert.isDisplayed())
|
assertThat(alert.isDisplayed())
|
||||||
@@ -363,15 +289,6 @@ class WebAuthnWebDriverTests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getAndWait(String endpoint) {
|
|
||||||
this.getAndWait(endpoint, endpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void getAndWait(String endpoint, String redirectUrl) {
|
|
||||||
this.driver.get(this.baseUrl + endpoint);
|
|
||||||
this.await(() -> assertThat(this.driver.getCurrentUrl()).endsWith(redirectUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static By.ById passkeyLabel() {
|
private static By.ById passkeyLabel() {
|
||||||
return new By.ById("label");
|
return new By.ById("label");
|
||||||
}
|
}
|
||||||
@@ -408,10 +325,6 @@ class WebAuthnWebDriverTests {
|
|||||||
return new By.ByCssSelector("button");
|
return new By.ByCssSelector("button");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static By.ByCssSelector deletePasskeyButton() {
|
|
||||||
return new By.ByCssSelector("table > tbody > tr > button");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The configuration for WebAuthN tests. It accesses the Server's current port, so we
|
* The configuration for WebAuthN tests. It accesses the Server's current port, so we
|
||||||
* can configurer WebAuthnConfigurer#allowedOrigin
|
* can configurer WebAuthnConfigurer#allowedOrigin
|
||||||
|
|||||||
+1
@@ -2052,6 +2052,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||||||
* http
|
* http
|
||||||
* // ...
|
* // ...
|
||||||
* .webAuthn((webAuthn) -> webAuthn
|
* .webAuthn((webAuthn) -> webAuthn
|
||||||
|
* .rpName("Spring Security Relying Party")
|
||||||
* .rpId("example.com")
|
* .rpId("example.com")
|
||||||
* .allowedOrigins("https://example.com")
|
* .allowedOrigins("https://example.com")
|
||||||
* );
|
* );
|
||||||
|
|||||||
+3
-5
@@ -177,7 +177,6 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
|
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
|
||||||
webAuthnAuthnFilter.setAuthenticationManager(
|
webAuthnAuthnFilter.setAuthenticationManager(
|
||||||
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
|
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
|
||||||
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
|
|
||||||
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
|
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
|
||||||
rpOperations);
|
rpOperations);
|
||||||
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
|
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
|
||||||
@@ -257,10 +256,9 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
|
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
|
||||||
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
|
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
|
||||||
WebAuthnRelyingPartyOperations.class);
|
WebAuthnRelyingPartyOperations.class);
|
||||||
String rpName = (this.rpName != null) ? this.rpName : this.rpId;
|
return webauthnOperationsBean.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities,
|
||||||
return webauthnOperationsBean
|
userCredentials, PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(),
|
||||||
.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities, userCredentials,
|
this.allowedOrigins));
|
||||||
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-89
@@ -19,13 +19,10 @@ package org.springframework.security.config.annotation.web.configurers;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpOutputMessage;
|
import org.springframework.http.HttpOutputMessage;
|
||||||
@@ -45,7 +42,6 @@ import org.springframework.security.web.SecurityFilterChain;
|
|||||||
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
|
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
|
||||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
|
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
|
||||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
|
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
|
||||||
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
|
|
||||||
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
|
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
|
||||||
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
|
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
@@ -56,8 +52,6 @@ import static org.mockito.ArgumentMatchers.any;
|
|||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.BDDMockito.willAnswer;
|
import static org.mockito.BDDMockito.willAnswer;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
|
||||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
@@ -94,14 +88,6 @@ public class WebAuthnConfigurerTests {
|
|||||||
.andExpect(content().string(containsString("body {")));
|
.andExpect(content().string(containsString("body {")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// gh-18128
|
|
||||||
@Test
|
|
||||||
public void webAuthnAuthenticationFilterIsPostProcessed() throws Exception {
|
|
||||||
this.spring.register(DefaultWebauthnConfiguration.class, PostProcessorConfiguration.class).autowire();
|
|
||||||
PostProcessorConfiguration postProcess = this.spring.getContext().getBean(PostProcessorConfiguration.class);
|
|
||||||
assertThat(postProcess.webauthnFilter).isNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception {
|
public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception {
|
||||||
this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire();
|
this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire();
|
||||||
@@ -141,42 +127,6 @@ public class WebAuthnConfigurerTests {
|
|||||||
.hasSize(1);
|
.hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void webauthnWhenConfiguredDefaultsRpNameToRpId() throws Exception {
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
this.spring.register(DefaultWebauthnConfiguration.class).autowire();
|
|
||||||
String response = this.mvc
|
|
||||||
.perform(post("/webauthn/register/options").with(csrf())
|
|
||||||
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
|
|
||||||
.andExpect(status().is2xxSuccessful())
|
|
||||||
.andReturn()
|
|
||||||
.getResponse()
|
|
||||||
.getContentAsString();
|
|
||||||
|
|
||||||
JsonNode parsedResponse = mapper.readTree(response);
|
|
||||||
|
|
||||||
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
|
|
||||||
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("example.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void webauthnWhenRpNameConfiguredUsesRpName() throws Exception {
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
this.spring.register(CustomRpNameWebauthnConfiguration.class).autowire();
|
|
||||||
String response = this.mvc
|
|
||||||
.perform(post("/webauthn/register/options").with(csrf())
|
|
||||||
.with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user"))))
|
|
||||||
.andExpect(status().is2xxSuccessful())
|
|
||||||
.andReturn()
|
|
||||||
.getResponse()
|
|
||||||
.getContentAsString();
|
|
||||||
|
|
||||||
JsonNode parsedResponse = mapper.readTree(response);
|
|
||||||
|
|
||||||
assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com");
|
|
||||||
assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("Test RP Name");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
|
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
|
||||||
this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire();
|
this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire();
|
||||||
@@ -339,26 +289,6 @@ public class WebAuthnConfigurerTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
static class PostProcessorConfiguration {
|
|
||||||
|
|
||||||
WebAuthnAuthenticationFilter webauthnFilter;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
BeanPostProcessor beanPostProcessor() {
|
|
||||||
return new BeanPostProcessor() {
|
|
||||||
@Override
|
|
||||||
public Object postProcessAfterInitialization(Object bean, String beanName) {
|
|
||||||
if (bean instanceof WebAuthnAuthenticationFilter filter) {
|
|
||||||
PostProcessorConfiguration.this.webauthnFilter = filter;
|
|
||||||
}
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
static class DefaultWebauthnConfiguration {
|
static class DefaultWebauthnConfiguration {
|
||||||
@@ -374,7 +304,8 @@ public class WebAuthnConfigurerTests {
|
|||||||
http
|
http
|
||||||
.formLogin(Customizer.withDefaults())
|
.formLogin(Customizer.withDefaults())
|
||||||
.webAuthn((authn) -> authn
|
.webAuthn((authn) -> authn
|
||||||
.rpId("example.com")
|
.rpId("spring.io")
|
||||||
|
.rpName("spring")
|
||||||
);
|
);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
return http.build();
|
return http.build();
|
||||||
@@ -382,24 +313,6 @@ public class WebAuthnConfigurerTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
static class CustomRpNameWebauthnConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
UserDetailsService userDetailsService() {
|
|
||||||
return new InMemoryUserDetailsManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
|
||||||
return http.formLogin(Customizer.withDefaults())
|
|
||||||
.webAuthn((webauthn) -> webauthn.rpId("example.com").rpName("Test RP Name"))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
static class NoFormLoginAndDefaultRegistrationPageConfiguration {
|
static class NoFormLoginAndDefaultRegistrationPageConfiguration {
|
||||||
|
|||||||
-21
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.springframework.security.crypto.bcrypt;
|
package org.springframework.security.crypto.bcrypt;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -26,7 +25,6 @@ import org.springframework.security.crypto.password.AbstractPasswordEncoderValid
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
@@ -238,23 +236,4 @@ public class BCryptPasswordEncoderTests extends AbstractPasswordEncoderValidatio
|
|||||||
assertThat(getEncoder().matches(password73chars, encodedPassword73chars)).isTrue();
|
assertThat(getEncoder().matches(password73chars, encodedPassword73chars)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fixes gh-18133
|
|
||||||
* @author StringManolo
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void passwordLargerThan72BytesShouldThrowIllegalArgumentException() {
|
|
||||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
|
||||||
String singleByteChars = "a".repeat(68);
|
|
||||||
String password72Bytes = singleByteChars + "😀";
|
|
||||||
assertThat(password72Bytes.length()).isEqualTo(70);
|
|
||||||
assertThat(password72Bytes.getBytes(StandardCharsets.UTF_8).length).isEqualTo(72);
|
|
||||||
assertThatNoException().isThrownBy(() -> encoder.encode(password72Bytes));
|
|
||||||
String singleByteCharsTooLong = "a".repeat(69);
|
|
||||||
String password73Bytes = singleByteCharsTooLong + "😀";
|
|
||||||
assertThat(password73Bytes.getBytes(StandardCharsets.UTF_8).length).isEqualTo(73);
|
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> encoder.encode(password73Bytes))
|
|
||||||
.withMessageContaining("password cannot be more than 72 bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,6 @@
|
|||||||
**** xref:servlet/oauth2/resource-server/multitenancy.adoc[Multitenancy]
|
**** xref:servlet/oauth2/resource-server/multitenancy.adoc[Multitenancy]
|
||||||
**** xref:servlet/oauth2/resource-server/bearer-tokens.adoc[Bearer Tokens]
|
**** xref:servlet/oauth2/resource-server/bearer-tokens.adoc[Bearer Tokens]
|
||||||
**** xref:servlet/oauth2/resource-server/dpop-tokens.adoc[DPoP-bound Access Tokens]
|
**** xref:servlet/oauth2/resource-server/dpop-tokens.adoc[DPoP-bound Access Tokens]
|
||||||
**** xref:servlet/oauth2/resource-server/protected-resource-metadata.adoc[Protected Resource Metadata]
|
|
||||||
*** xref:servlet/oauth2/authorization-server/index.adoc[OAuth2 Authorization Server]
|
*** xref:servlet/oauth2/authorization-server/index.adoc[OAuth2 Authorization Server]
|
||||||
**** xref:servlet/oauth2/authorization-server/getting-started.adoc[Getting Started]
|
**** xref:servlet/oauth2/authorization-server/getting-started.adoc[Getting Started]
|
||||||
**** xref:servlet/oauth2/authorization-server/configuration-model.adoc[Configuration Model]
|
**** xref:servlet/oauth2/authorization-server/configuration-model.adoc[Configuration Model]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
[[webflux-cors]]
|
[[webflux-cors]]
|
||||||
= CORS
|
= CORS
|
||||||
|
|
||||||
@@ -74,11 +75,3 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
[WARNING]
|
|
||||||
====
|
|
||||||
CORS is a browser-based security feature.
|
|
||||||
By disabling CORS in Spring Security, you are not removing CORS protection from your browser.
|
|
||||||
Instead, you are removing CORS support from Spring Security, and users will not be able to interact with your Spring backend from a cross-origin browser application.
|
|
||||||
To fix CORS errors in your application, you must enable CORS support, and provide an appropriate configuration source.
|
|
||||||
====
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ SecurityFilterChain filterChain(HttpSecurity http) {
|
|||||||
// ...
|
// ...
|
||||||
.formLogin(withDefaults())
|
.formLogin(withDefaults())
|
||||||
.webAuthn((webAuthn) -> webAuthn
|
.webAuthn((webAuthn) -> webAuthn
|
||||||
|
.rpName("Spring Security Relying Party")
|
||||||
.rpId("example.com")
|
.rpId("example.com")
|
||||||
.allowedOrigins("https://example.com")
|
.allowedOrigins("https://example.com")
|
||||||
// optional properties
|
// optional properties
|
||||||
@@ -95,6 +96,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||||||
// ...
|
// ...
|
||||||
http {
|
http {
|
||||||
webAuthn {
|
webAuthn {
|
||||||
|
rpName = "Spring Security Relying Party"
|
||||||
rpId = "example.com"
|
rpId = "example.com"
|
||||||
allowedOrigins = setOf("https://example.com")
|
allowedOrigins = setOf("https://example.com")
|
||||||
// optional properties
|
// optional properties
|
||||||
|
|||||||
@@ -183,11 +183,3 @@ fun corsConfigurationSource(): UrlBasedCorsConfigurationSource {
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
[WARNING]
|
|
||||||
====
|
|
||||||
CORS is a browser-based security feature.
|
|
||||||
By disabling CORS in Spring Security with `.cors(CorsConfigurer::disable)`, you are not removing CORS protection from your browser.
|
|
||||||
Instead, you are removing CORS support from Spring Security, and users will not be able to interact with your Spring backend from a cross-origin browser application.
|
|
||||||
To fix CORS errors in your application, you must enable CORS support, and provide an appropriate configuration source.
|
|
||||||
====
|
|
||||||
|
|||||||
@@ -108,34 +108,6 @@ spring:
|
|||||||
require-authorization-consent: true
|
require-authorization-consent: true
|
||||||
----
|
----
|
||||||
|
|
||||||
If you want to customize the default `HttpSecurity` configuration, you may override Spring Boot's auto-configuration with the following example:
|
|
||||||
|
|
||||||
[[oauth2AuthorizationServer-minimal-sample-gettingstarted]]
|
|
||||||
.SecurityConfig.java
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
public class SecurityConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
|
|
||||||
http
|
|
||||||
.authorizeHttpRequests((authorize) ->
|
|
||||||
authorize
|
|
||||||
.anyRequest().authenticated()
|
|
||||||
)
|
|
||||||
.formLogin(Customizer.withDefaults())
|
|
||||||
.oauth2AuthorizationServer((authorizationServer) ->
|
|
||||||
authorizationServer
|
|
||||||
.oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0
|
|
||||||
);
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
TIP: Beyond the Getting Started experience, most users will want to customize the default configuration. The xref:servlet/oauth2/authorization-server/getting-started.adoc#oauth2AuthorizationServer-defining-required-components[next section] demonstrates providing all of the necessary beans yourself.
|
TIP: Beyond the Getting Started experience, most users will want to customize the default configuration. The xref:servlet/oauth2/authorization-server/getting-started.adoc#oauth2AuthorizationServer-defining-required-components[next section] demonstrates providing all of the necessary beans yourself.
|
||||||
|
|
||||||
[[oauth2AuthorizationServer-defining-required-components]]
|
[[oauth2AuthorizationServer-defining-required-components]]
|
||||||
|
|||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
[[oauth2resourceserver-protected-resource-metadata]]
|
|
||||||
= OAuth 2.0 Protected Resource Metadata
|
|
||||||
|
|
||||||
`OAuth2ResourceServerConfigurer.ProtectedResourceMetadataConfigurer` provides the ability to customize the https://www.rfc-editor.org/rfc/rfc9728.html#section-3[OAuth 2.0 Protected Resource Metadata endpoint].
|
|
||||||
It defines an extension point that lets you customize the https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2[OAuth 2.0 Protected Resource Metadata response].
|
|
||||||
|
|
||||||
`OAuth2ResourceServerConfigurer.ProtectedResourceMetadataConfigurer` provides the following configuration option:
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
@Bean
|
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
||||||
http
|
|
||||||
.oauth2ResourceServer((resourceServer) ->
|
|
||||||
resourceServer
|
|
||||||
.protectedResourceMetadata(protectedResourceMetadata ->
|
|
||||||
protectedResourceMetadata
|
|
||||||
.protectedResourceMetadataCustomizer(protectedResourceMetadataCustomizer) <1>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
----
|
|
||||||
<1> `protectedResourceMetadataCustomizer()`: The `Consumer` providing access to the `OAuth2ProtectedResourceMetadata.Builder` allowing the ability to customize the claims of the Resource Server's configuration.
|
|
||||||
|
|
||||||
`OAuth2ResourceServerConfigurer.ProtectedResourceMetadataConfigurer` configures the `OAuth2ProtectedResourceMetadataFilter` and registers it with the Resource Server `SecurityFilterChain` `@Bean`.
|
|
||||||
`OAuth2ProtectedResourceMetadataFilter` is the `Filter` that returns the https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2[OAuth2ProtectedResourceMetadata response].
|
|
||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
springBootVersion=4.0.0-SNAPSHOT
|
springBootVersion=4.0.0-SNAPSHOT
|
||||||
version=7.0.0
|
version=7.0.0-RC2
|
||||||
samplesBranch=main
|
samplesBranch=main
|
||||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ org-apache-maven-resolver = "1.9.24"
|
|||||||
org-aspectj = "1.9.24"
|
org-aspectj = "1.9.24"
|
||||||
org-bouncycastle = "1.80"
|
org-bouncycastle = "1.80"
|
||||||
org-eclipse-jetty = "11.0.26"
|
org-eclipse-jetty = "11.0.26"
|
||||||
org-jetbrains-kotlin = "2.2.21"
|
org-jetbrains-kotlin = "2.2.20"
|
||||||
org-jetbrains-kotlinx = "1.10.2"
|
org-jetbrains-kotlinx = "1.10.2"
|
||||||
org-mockito = "5.17.0"
|
org-mockito = "5.17.0"
|
||||||
org-opensaml5 = "5.1.6"
|
org-opensaml5 = "5.1.6"
|
||||||
org-springframework = "7.0.0"
|
org-springframework = "7.0.0-RC1"
|
||||||
com-password4j = "1.8.4"
|
com-password4j = "1.8.4"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
@@ -30,7 +30,7 @@ commons-collections = "commons-collections:commons-collections:3.2.2"
|
|||||||
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3"
|
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3"
|
||||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.12"
|
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.12"
|
||||||
io-mockk = "io.mockk:mockk:1.14.6"
|
io-mockk = "io.mockk:mockk:1.14.6"
|
||||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0"
|
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-RC1"
|
||||||
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
|
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
|
||||||
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
|
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
|
||||||
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
|
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
|
||||||
@@ -68,7 +68,7 @@ org-hamcrest = "org.hamcrest:hamcrest:2.2"
|
|||||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.0.10.Final"
|
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.0.10.Final"
|
||||||
org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
|
org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
|
||||||
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
|
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
|
||||||
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21"
|
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.20"
|
||||||
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" }
|
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" }
|
||||||
org-junit-junit-bom = "org.junit:junit-bom:6.0.0"
|
org-junit-junit-bom = "org.junit:junit-bom:6.0.0"
|
||||||
org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" }
|
org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" }
|
||||||
@@ -81,8 +81,8 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup
|
|||||||
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
|
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
|
||||||
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
|
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
|
||||||
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
|
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
|
||||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.0"
|
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.0-RC1"
|
||||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.0"
|
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.0-RC1"
|
||||||
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
|
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
|
||||||
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
|
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
|
||||||
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.0.1"
|
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.0.1"
|
||||||
|
|||||||
+58
-62
@@ -466,7 +466,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
|||||||
/**
|
/**
|
||||||
* The default {@link RowMapper} that maps the current row in
|
* The default {@link RowMapper} that maps the current row in
|
||||||
* {@code java.sql.ResultSet} to {@link OAuth2Authorization} using Jackson 3's
|
* {@code java.sql.ResultSet} to {@link OAuth2Authorization} using Jackson 3's
|
||||||
* {@link JsonMapper}.
|
* {@link JsonMapper} to read all {@code Map<String,Object>} within the result.
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 7.0
|
* @since 7.0
|
||||||
@@ -482,7 +482,6 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
|||||||
public JsonMapperOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository,
|
public JsonMapperOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository,
|
||||||
JsonMapper jsonMapper) {
|
JsonMapper jsonMapper) {
|
||||||
super(registeredClientRepository);
|
super(registeredClientRepository);
|
||||||
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
|
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,7 +544,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
|||||||
|
|
||||||
private LobHandler lobHandler = new DefaultLobHandler();
|
private LobHandler lobHandler = new DefaultLobHandler();
|
||||||
|
|
||||||
private AbstractOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
|
AbstractOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
|
||||||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
||||||
this.registeredClientRepository = registeredClientRepository;
|
this.registeredClientRepository = registeredClientRepository;
|
||||||
}
|
}
|
||||||
@@ -714,36 +713,42 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default {@code Function} that maps {@link OAuth2Authorization} to a
|
* Nested class to protect from getting {@link NoClassDefFoundError} when Jackson 2 is
|
||||||
* {@code List} of {@link SqlParameterValue} using an instance of Jackson 3's
|
* not on the classpath.
|
||||||
* {@link JsonMapper}.
|
*
|
||||||
|
* @deprecated This is used to allow transition to Jackson 3. Use {@link Jackson3}
|
||||||
|
* instead.
|
||||||
*/
|
*/
|
||||||
public static class JsonMapperOAuth2AuthorizationParametersMapper
|
@Deprecated(forRemoval = true, since = "7.0")
|
||||||
extends AbstractOAuth2AuthorizationParametersMapper {
|
private static final class Jackson2 {
|
||||||
|
|
||||||
private final JsonMapper jsonMapper;
|
static ObjectMapper createObjectMapper() {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
public JsonMapperOAuth2AuthorizationParametersMapper() {
|
ClassLoader classLoader = Jackson2.class.getClassLoader();
|
||||||
this(Jackson3.createJsonMapper());
|
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
|
||||||
}
|
objectMapper.registerModules(securityModules);
|
||||||
|
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
||||||
public JsonMapperOAuth2AuthorizationParametersMapper(JsonMapper jsonMapper) {
|
return objectMapper;
|
||||||
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
|
|
||||||
this.jsonMapper = jsonMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
String writeValueAsString(Map<String, Object> data) throws Exception {
|
|
||||||
return this.jsonMapper.writeValueAsString(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@code Function} that maps {@link OAuth2Authorization} to a {@code List} of
|
* Nested class used to get a common default instance of {@link JsonMapper}. It is in
|
||||||
* {@link SqlParameterValue} using an instance of Jackson 2's {@link ObjectMapper}.
|
* a nested class to protect from getting {@link NoClassDefFoundError} when Jackson 3
|
||||||
*
|
* is not on the classpath.
|
||||||
* @deprecated Use {@link JsonMapperOAuth2AuthorizationParametersMapper} to switch to
|
*/
|
||||||
|
private static final class Jackson3 {
|
||||||
|
|
||||||
|
static JsonMapper createJsonMapper() {
|
||||||
|
List<JacksonModule> modules = SecurityJacksonModules.getModules(Jackson3.class.getClassLoader());
|
||||||
|
return JsonMapper.builder().addModules(modules).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link JsonMapperOAuth2AuthorizationParametersMapper} to migrate to
|
||||||
* Jackson 3.
|
* Jackson 3.
|
||||||
*/
|
*/
|
||||||
@Deprecated(forRemoval = true, since = "7.0")
|
@Deprecated(forRemoval = true, since = "7.0")
|
||||||
@@ -767,6 +772,32 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default {@code Function} that maps {@link OAuth2Authorization} to a
|
||||||
|
* {@code List} of {@link SqlParameterValue} using an instance of Jackson 3's
|
||||||
|
* {@link JsonMapper}.
|
||||||
|
*/
|
||||||
|
public static final class JsonMapperOAuth2AuthorizationParametersMapper
|
||||||
|
extends AbstractOAuth2AuthorizationParametersMapper {
|
||||||
|
|
||||||
|
private final JsonMapper mapper;
|
||||||
|
|
||||||
|
public JsonMapperOAuth2AuthorizationParametersMapper() {
|
||||||
|
this(Jackson3.createJsonMapper());
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonMapperOAuth2AuthorizationParametersMapper(JsonMapper mapper) {
|
||||||
|
Assert.notNull(mapper, "mapper cannot be null");
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String writeValueAsString(Map<String, Object> data) throws Exception {
|
||||||
|
return this.mapper.writeValueAsString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base {@code Function} that maps {@link OAuth2Authorization} to a {@code List}
|
* The base {@code Function} that maps {@link OAuth2Authorization} to a {@code List}
|
||||||
* of {@link SqlParameterValue}.
|
* of {@link SqlParameterValue}.
|
||||||
@@ -774,7 +805,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
|||||||
private abstract static class AbstractOAuth2AuthorizationParametersMapper
|
private abstract static class AbstractOAuth2AuthorizationParametersMapper
|
||||||
implements Function<OAuth2Authorization, List<SqlParameterValue>> {
|
implements Function<OAuth2Authorization, List<SqlParameterValue>> {
|
||||||
|
|
||||||
private AbstractOAuth2AuthorizationParametersMapper() {
|
protected AbstractOAuth2AuthorizationParametersMapper() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -885,41 +916,6 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Nested class to protect from getting {@link NoClassDefFoundError} when Jackson 2 is
|
|
||||||
* not on the classpath.
|
|
||||||
*
|
|
||||||
* @deprecated This is used to allow transition to Jackson 3. Use {@link Jackson3}
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true, since = "7.0")
|
|
||||||
private static final class Jackson2 {
|
|
||||||
|
|
||||||
private static ObjectMapper createObjectMapper() {
|
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
ClassLoader classLoader = Jackson2.class.getClassLoader();
|
|
||||||
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
|
|
||||||
objectMapper.registerModules(securityModules);
|
|
||||||
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
|
||||||
return objectMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Nested class used to get a common default instance of {@link JsonMapper}. It is in
|
|
||||||
* a nested class to protect from getting {@link NoClassDefFoundError} when Jackson 3
|
|
||||||
* is not on the classpath.
|
|
||||||
*/
|
|
||||||
private static final class Jackson3 {
|
|
||||||
|
|
||||||
private static JsonMapper createJsonMapper() {
|
|
||||||
List<JacksonModule> modules = SecurityJacksonModules.getModules(Jackson3.class.getClassLoader());
|
|
||||||
return JsonMapper.builder().addModules(modules).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LobCreatorArgumentPreparedStatementSetter extends ArgumentPreparedStatementSetter {
|
private static final class LobCreatorArgumentPreparedStatementSetter extends ArgumentPreparedStatementSetter {
|
||||||
|
|
||||||
private final LobCreator lobCreator;
|
private final LobCreator lobCreator;
|
||||||
|
|||||||
+67
-148
@@ -33,7 +33,6 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
|
|||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.jackson.CoreJacksonModule;
|
|
||||||
import org.springframework.security.jackson2.CoreJackson2Module;
|
import org.springframework.security.jackson2.CoreJackson2Module;
|
||||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
@@ -49,11 +48,9 @@ import org.springframework.security.oauth2.server.authorization.JdbcOAuth2Author
|
|||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
|
||||||
import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
|
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||||
import org.springframework.security.web.jackson.WebServletJacksonModule;
|
|
||||||
import org.springframework.security.web.jackson2.WebServletJackson2Module;
|
import org.springframework.security.web.jackson2.WebServletJackson2Module;
|
||||||
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
|
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
@@ -70,18 +67,7 @@ import org.springframework.util.ClassUtils;
|
|||||||
*/
|
*/
|
||||||
class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
||||||
|
|
||||||
private static final boolean jackson2Present;
|
private boolean jackson2Contributed;
|
||||||
|
|
||||||
private static final boolean jackson3Present;
|
|
||||||
|
|
||||||
static {
|
|
||||||
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
|
|
||||||
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
|
|
||||||
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
|
|
||||||
jackson3Present = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean jacksonContributed;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||||
@@ -93,17 +79,17 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
|
|||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
if ((isJdbcBasedOAuth2AuthorizationService || isJdbcBasedRegisteredClientRepository)
|
if ((isJdbcBasedOAuth2AuthorizationService || isJdbcBasedRegisteredClientRepository)
|
||||||
&& !this.jacksonContributed) {
|
&& !this.jackson2Contributed) {
|
||||||
JacksonConfigurationBeanRegistrationAotContribution jacksonContribution =
|
Jackson2ConfigurationBeanRegistrationAotContribution jackson2Contribution =
|
||||||
new JacksonConfigurationBeanRegistrationAotContribution();
|
new Jackson2ConfigurationBeanRegistrationAotContribution();
|
||||||
this.jacksonContributed = true;
|
this.jackson2Contributed = true;
|
||||||
return jacksonContribution;
|
return jackson2Contribution;
|
||||||
}
|
}
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class JacksonConfigurationBeanRegistrationAotContribution
|
private static class Jackson2ConfigurationBeanRegistrationAotContribution
|
||||||
implements BeanRegistrationAotContribution {
|
implements BeanRegistrationAotContribution {
|
||||||
|
|
||||||
private final BindingReflectionHintsRegistrar reflectionHintsRegistrar = new BindingReflectionHintsRegistrar();
|
private final BindingReflectionHintsRegistrar reflectionHintsRegistrar = new BindingReflectionHintsRegistrar();
|
||||||
@@ -123,6 +109,7 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
|
|||||||
.registerType(HashSet.class, MemberCategory.DECLARED_FIELDS,
|
.registerType(HashSet.class, MemberCategory.DECLARED_FIELDS,
|
||||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||||
|
|
||||||
|
// Spring Security and Spring Authorization Server
|
||||||
hints.reflection()
|
hints.reflection()
|
||||||
.registerTypes(Arrays.asList(TypeReference.of(AbstractAuthenticationToken.class),
|
.registerTypes(Arrays.asList(TypeReference.of(AbstractAuthenticationToken.class),
|
||||||
TypeReference.of(DefaultSavedRequest.Builder.class),
|
TypeReference.of(DefaultSavedRequest.Builder.class),
|
||||||
@@ -141,143 +128,75 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
|
|||||||
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
|
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
|
||||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
|
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
|
||||||
|
|
||||||
// Jackson Modules
|
// Jackson Modules - Spring Security and Spring Authorization Server
|
||||||
if (jackson2Present) {
|
hints.reflection()
|
||||||
hints.reflection()
|
.registerTypes(
|
||||||
.registerTypes(
|
Arrays.asList(TypeReference.of(CoreJackson2Module.class),
|
||||||
Arrays.asList(TypeReference.of(CoreJackson2Module.class),
|
TypeReference.of(WebServletJackson2Module.class),
|
||||||
TypeReference.of(WebServletJackson2Module.class),
|
TypeReference.of(OAuth2AuthorizationServerJackson2Module.class)),
|
||||||
TypeReference.of(OAuth2AuthorizationServerJackson2Module.class)),
|
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
|
||||||
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
|
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
|
||||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
|
||||||
MemberCategory.INVOKE_DECLARED_METHODS));
|
|
||||||
}
|
|
||||||
if (jackson3Present) {
|
|
||||||
hints.reflection()
|
|
||||||
.registerTypes(
|
|
||||||
Arrays.asList(TypeReference.of(CoreJacksonModule.class),
|
|
||||||
TypeReference.of(WebServletJacksonModule.class),
|
|
||||||
TypeReference.of(OAuth2AuthorizationServerJacksonModule.class)),
|
|
||||||
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
|
|
||||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
|
||||||
MemberCategory.INVOKE_DECLARED_METHODS));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jackson Mixins
|
// Jackson Mixins - Spring Security and Spring Authorization Server
|
||||||
if (jackson2Present) {
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
loadClass("org.springframework.security.jackson2.UnmodifiableSetMixin"));
|
||||||
loadClass("org.springframework.security.jackson2.UnmodifiableSetMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
loadClass("org.springframework.security.jackson2.UnmodifiableListMixin"));
|
||||||
loadClass("org.springframework.security.jackson2.UnmodifiableListMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
loadClass("org.springframework.security.jackson2.UnmodifiableMapMixin"));
|
||||||
loadClass("org.springframework.security.jackson2.UnmodifiableMapMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
"org.springframework.security.oauth2.server.authorization.jackson2.UnmodifiableMapMixin"));
|
||||||
"org.springframework.security.oauth2.server.authorization.jackson2.UnmodifiableMapMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
loadClass("org.springframework.security.oauth2.server.authorization.jackson2.HashSetMixin"));
|
||||||
loadClass("org.springframework.security.oauth2.server.authorization.jackson2.HashSetMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
loadClass("org.springframework.security.web.jackson2.DefaultSavedRequestMixin"));
|
||||||
loadClass("org.springframework.security.web.jackson2.DefaultSavedRequestMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
loadClass("org.springframework.security.web.jackson2.WebAuthenticationDetailsMixin"));
|
||||||
loadClass("org.springframework.security.web.jackson2.WebAuthenticationDetailsMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
loadClass("org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenMixin"));
|
||||||
loadClass("org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
loadClass("org.springframework.security.jackson2.UserMixin"));
|
||||||
loadClass("org.springframework.security.jackson2.UserMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
loadClass("org.springframework.security.jackson2.SimpleGrantedAuthorityMixin"));
|
||||||
loadClass("org.springframework.security.jackson2.SimpleGrantedAuthorityMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeActorMixin"));
|
||||||
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeActorMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationRequestMixin"));
|
||||||
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationRequestMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeCompositeAuthenticationTokenMixin"));
|
||||||
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeCompositeAuthenticationTokenMixin"));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenFormatMixin"));
|
||||||
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenFormatMixin"));
|
|
||||||
}
|
|
||||||
if (jackson3Present) {
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.web.jackson.DefaultSavedRequestMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.web.jackson.WebAuthenticationDetailsMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.jackson.UsernamePasswordAuthenticationTokenMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.jackson.UserMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.jackson.SimpleGrantedAuthorityMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
|
||||||
"org.springframework.security.oauth2.server.authorization.jackson.OAuth2TokenExchangeActorMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
|
||||||
"org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationRequestMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
|
||||||
"org.springframework.security.oauth2.server.authorization.jackson.OAuth2TokenExchangeCompositeAuthenticationTokenMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
|
||||||
"org.springframework.security.oauth2.server.authorization.jackson.OAuth2TokenFormatMixin"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if OAuth2 Client is on classpath
|
// Check if Spring Security OAuth2 Client is on classpath
|
||||||
if (ClassUtils.isPresent("org.springframework.security.oauth2.client.registration.ClientRegistration",
|
if (ClassUtils.isPresent("org.springframework.security.oauth2.client.registration.ClientRegistration",
|
||||||
ClassUtils.getDefaultClassLoader())) {
|
ClassUtils.getDefaultClassLoader())) {
|
||||||
|
|
||||||
|
// Jackson Module (and required types) - Spring Security OAuth2 Client
|
||||||
hints.reflection()
|
hints.reflection()
|
||||||
.registerType(TypeReference
|
.registerTypes(Arrays.asList(
|
||||||
.of("org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken"),
|
TypeReference
|
||||||
|
.of("org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module"),
|
||||||
|
TypeReference
|
||||||
|
.of("org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken")),
|
||||||
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
|
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
|
||||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||||
MemberCategory.INVOKE_DECLARED_METHODS));
|
MemberCategory.INVOKE_DECLARED_METHODS));
|
||||||
|
|
||||||
// Jackson Module
|
// Jackson Mixins - Spring Security OAuth2 Client
|
||||||
if (jackson2Present) {
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
||||||
hints.reflection()
|
"org.springframework.security.oauth2.client.jackson2.OAuth2AuthenticationTokenMixin"));
|
||||||
.registerType(TypeReference
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
.of("org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module"),
|
loadClass("org.springframework.security.oauth2.client.jackson2.DefaultOidcUserMixin"));
|
||||||
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
loadClass("org.springframework.security.oauth2.client.jackson2.DefaultOAuth2UserMixin"));
|
||||||
MemberCategory.INVOKE_DECLARED_METHODS));
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
}
|
loadClass("org.springframework.security.oauth2.client.jackson2.OidcUserAuthorityMixin"));
|
||||||
if (jackson3Present) {
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
hints.reflection()
|
loadClass("org.springframework.security.oauth2.client.jackson2.OAuth2UserAuthorityMixin"));
|
||||||
.registerType(
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
TypeReference
|
loadClass("org.springframework.security.oauth2.client.jackson2.OidcIdTokenMixin"));
|
||||||
.of("org.springframework.security.oauth2.client.jackson.OAuth2ClientJacksonModule"),
|
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||||
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
|
loadClass("org.springframework.security.oauth2.client.jackson2.OidcUserInfoMixin"));
|
||||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
|
||||||
MemberCategory.INVOKE_DECLARED_METHODS));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jackson Mixins
|
|
||||||
if (jackson2Present) {
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
|
||||||
"org.springframework.security.oauth2.client.jackson2.OAuth2AuthenticationTokenMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson2.DefaultOidcUserMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson2.DefaultOAuth2UserMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson2.OidcUserAuthorityMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson2.OAuth2UserAuthorityMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson2.OidcIdTokenMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson2.OidcUserInfoMixin"));
|
|
||||||
}
|
|
||||||
if (jackson3Present) {
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
|
||||||
"org.springframework.security.oauth2.client.jackson.OAuth2AuthenticationTokenMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson.DefaultOidcUserMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson.DefaultOAuth2UserMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson.OidcUserAuthorityMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson.OAuth2UserAuthorityMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson.OidcIdTokenMixin"));
|
|
||||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
|
||||||
loadClass("org.springframework.security.oauth2.client.jackson.OidcUserInfoMixin"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-172
@@ -28,23 +28,19 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.Module;
|
import com.fasterxml.jackson.databind.Module;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import tools.jackson.databind.JacksonModule;
|
|
||||||
import tools.jackson.databind.json.JsonMapper;
|
|
||||||
|
|
||||||
import org.springframework.aot.hint.RuntimeHints;
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
|
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
|
||||||
import org.springframework.jdbc.core.JdbcOperations;
|
import org.springframework.jdbc.core.JdbcOperations;
|
||||||
import org.springframework.jdbc.core.PreparedStatementSetter;
|
import org.springframework.jdbc.core.PreparedStatementSetter;
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
import org.springframework.jdbc.core.SqlParameterValue;
|
import org.springframework.jdbc.core.SqlParameterValue;
|
||||||
import org.springframework.security.jackson.SecurityJacksonModules;
|
|
||||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
@@ -138,8 +134,8 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
|||||||
public JdbcRegisteredClientRepository(JdbcOperations jdbcOperations) {
|
public JdbcRegisteredClientRepository(JdbcOperations jdbcOperations) {
|
||||||
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
|
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
|
||||||
this.jdbcOperations = jdbcOperations;
|
this.jdbcOperations = jdbcOperations;
|
||||||
this.registeredClientRowMapper = new JsonMapperRegisteredClientRowMapper();
|
this.registeredClientRowMapper = new RegisteredClientRowMapper();
|
||||||
this.registeredClientParametersMapper = new JsonMapperRegisteredClientParametersMapper();
|
this.registeredClientParametersMapper = new RegisteredClientParametersMapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -210,7 +206,7 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
|||||||
/**
|
/**
|
||||||
* Sets the {@link RowMapper} used for mapping the current row in
|
* Sets the {@link RowMapper} used for mapping the current row in
|
||||||
* {@code java.sql.ResultSet} to {@link RegisteredClient}. The default is
|
* {@code java.sql.ResultSet} to {@link RegisteredClient}. The default is
|
||||||
* {@link JsonMapperRegisteredClientRowMapper}.
|
* {@link RegisteredClientRowMapper}.
|
||||||
* @param registeredClientRowMapper the {@link RowMapper} used for mapping the current
|
* @param registeredClientRowMapper the {@link RowMapper} used for mapping the current
|
||||||
* row in {@code ResultSet} to {@link RegisteredClient}
|
* row in {@code ResultSet} to {@link RegisteredClient}
|
||||||
*/
|
*/
|
||||||
@@ -222,7 +218,7 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
|||||||
/**
|
/**
|
||||||
* Sets the {@code Function} used for mapping {@link RegisteredClient} to a
|
* Sets the {@code Function} used for mapping {@link RegisteredClient} to a
|
||||||
* {@code List} of {@link SqlParameterValue}. The default is
|
* {@code List} of {@link SqlParameterValue}. The default is
|
||||||
* {@link JsonMapperRegisteredClientParametersMapper}.
|
* {@link RegisteredClientParametersMapper}.
|
||||||
* @param registeredClientParametersMapper the {@code Function} used for mapping
|
* @param registeredClientParametersMapper the {@code Function} used for mapping
|
||||||
* {@link RegisteredClient} to a {@code List} of {@link SqlParameterValue}
|
* {@link RegisteredClient} to a {@code List} of {@link SqlParameterValue}
|
||||||
*/
|
*/
|
||||||
@@ -246,76 +242,17 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The default {@link RowMapper} that maps the current row in
|
* The default {@link RowMapper} that maps the current row in
|
||||||
* {@code java.sql.ResultSet} to {@link RegisteredClient} using Jackson 3's
|
* {@code java.sql.ResultSet} to {@link RegisteredClient}.
|
||||||
* {@link JsonMapper}.
|
|
||||||
*
|
|
||||||
* @author Joe Grandja
|
|
||||||
* @since 7.0
|
|
||||||
*/
|
*/
|
||||||
public static class JsonMapperRegisteredClientRowMapper extends AbstractRegisteredClientRowMapper {
|
public static class RegisteredClientRowMapper implements RowMapper<RegisteredClient> {
|
||||||
|
|
||||||
private final JsonMapper jsonMapper;
|
private ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
public JsonMapperRegisteredClientRowMapper() {
|
public RegisteredClientRowMapper() {
|
||||||
this(Jackson3.createJsonMapper());
|
ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader();
|
||||||
}
|
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
|
||||||
|
this.objectMapper.registerModules(securityModules);
|
||||||
public JsonMapperRegisteredClientRowMapper(JsonMapper jsonMapper) {
|
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
||||||
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
|
|
||||||
this.jsonMapper = jsonMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Map<String, Object> readValue(String data) {
|
|
||||||
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
|
|
||||||
};
|
|
||||||
tools.jackson.databind.JavaType javaType = this.jsonMapper.getTypeFactory()
|
|
||||||
.constructType(typeReference.getType());
|
|
||||||
return this.jsonMapper.readValue(data, javaType);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link RowMapper} that maps the current row in {@code java.sql.ResultSet} to
|
|
||||||
* {@link RegisteredClient} using Jackson 2's {@link ObjectMapper}.
|
|
||||||
*
|
|
||||||
* @deprecated Use {@link JsonMapperRegisteredClientRowMapper} to switch to Jackson 3.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true, since = "7.0")
|
|
||||||
public static class RegisteredClientRowMapper extends AbstractRegisteredClientRowMapper {
|
|
||||||
|
|
||||||
private ObjectMapper objectMapper = Jackson2.createObjectMapper();
|
|
||||||
|
|
||||||
public final void setObjectMapper(ObjectMapper objectMapper) {
|
|
||||||
Assert.notNull(objectMapper, "objectMapper cannot be null");
|
|
||||||
this.objectMapper = objectMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final ObjectMapper getObjectMapper() {
|
|
||||||
return this.objectMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Map<String, Object> readValue(String data) throws JsonProcessingException {
|
|
||||||
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
|
|
||||||
};
|
|
||||||
com.fasterxml.jackson.databind.JavaType javaType = this.objectMapper.getTypeFactory()
|
|
||||||
.constructType(typeReference.getType());
|
|
||||||
return this.objectMapper.readValue(data, javaType);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The base {@link RowMapper} that maps the current row in {@code java.sql.ResultSet}
|
|
||||||
* to {@link RegisteredClient}. This is extracted to a distinct class so that
|
|
||||||
* {@link RegisteredClientRowMapper} can be deprecated in favor of
|
|
||||||
* {@link JsonMapperRegisteredClientRowMapper}.
|
|
||||||
*/
|
|
||||||
private abstract static class AbstractRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
|
|
||||||
|
|
||||||
private AbstractRegisteredClientRowMapper() {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -362,17 +299,25 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final void setObjectMapper(ObjectMapper objectMapper) {
|
||||||
|
Assert.notNull(objectMapper, "objectMapper cannot be null");
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final ObjectMapper getObjectMapper() {
|
||||||
|
return this.objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, Object> parseMap(String data) {
|
private Map<String, Object> parseMap(String data) {
|
||||||
try {
|
try {
|
||||||
return readValue(data);
|
return this.objectMapper.readValue(data, new TypeReference<>() {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract Map<String, Object> readValue(String data) throws Exception;
|
|
||||||
|
|
||||||
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
|
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
|
||||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
|
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
|
||||||
return AuthorizationGrantType.AUTHORIZATION_CODE;
|
return AuthorizationGrantType.AUTHORIZATION_CODE;
|
||||||
@@ -405,64 +350,18 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The default {@code Function} that maps {@link RegisteredClient} to a {@code List}
|
* The default {@code Function} that maps {@link RegisteredClient} to a {@code List}
|
||||||
* of {@link SqlParameterValue} using an instance of Jackson 3's {@link JsonMapper}.
|
* of {@link SqlParameterValue}.
|
||||||
*/
|
*/
|
||||||
public static class JsonMapperRegisteredClientParametersMapper extends AbstractRegisteredClientParametersMapper {
|
public static class RegisteredClientParametersMapper
|
||||||
|
|
||||||
private final JsonMapper jsonMapper;
|
|
||||||
|
|
||||||
public JsonMapperRegisteredClientParametersMapper() {
|
|
||||||
this(Jackson3.createJsonMapper());
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonMapperRegisteredClientParametersMapper(JsonMapper jsonMapper) {
|
|
||||||
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
|
|
||||||
this.jsonMapper = jsonMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
String writeValueAsString(Map<String, Object> data) throws Exception {
|
|
||||||
return this.jsonMapper.writeValueAsString(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@code Function} that maps {@link RegisteredClient} to a {@code List} of
|
|
||||||
* {@link SqlParameterValue} using an instance of Jackson 2's {@link ObjectMapper}.
|
|
||||||
*
|
|
||||||
* @deprecated Use {@link JsonMapperRegisteredClientParametersMapper} to switch to
|
|
||||||
* Jackson 3.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true, since = "7.0")
|
|
||||||
public static class RegisteredClientParametersMapper extends AbstractRegisteredClientParametersMapper {
|
|
||||||
|
|
||||||
private ObjectMapper objectMapper = Jackson2.createObjectMapper();
|
|
||||||
|
|
||||||
public final void setObjectMapper(ObjectMapper objectMapper) {
|
|
||||||
Assert.notNull(objectMapper, "objectMapper cannot be null");
|
|
||||||
this.objectMapper = objectMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final ObjectMapper getObjectMapper() {
|
|
||||||
return this.objectMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
String writeValueAsString(Map<String, Object> data) throws JsonProcessingException {
|
|
||||||
return this.objectMapper.writeValueAsString(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The base {@code Function} that maps {@link RegisteredClient} to a {@code List} of
|
|
||||||
* {@link SqlParameterValue}.
|
|
||||||
*/
|
|
||||||
private abstract static class AbstractRegisteredClientParametersMapper
|
|
||||||
implements Function<RegisteredClient, List<SqlParameterValue>> {
|
implements Function<RegisteredClient, List<SqlParameterValue>> {
|
||||||
|
|
||||||
private AbstractRegisteredClientParametersMapper() {
|
private ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
public RegisteredClientParametersMapper() {
|
||||||
|
ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader();
|
||||||
|
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
|
||||||
|
this.objectMapper.registerModules(securityModules);
|
||||||
|
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -504,52 +403,24 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
|||||||
new SqlParameterValue(Types.VARCHAR, writeMap(registeredClient.getTokenSettings().getSettings())));
|
new SqlParameterValue(Types.VARCHAR, writeMap(registeredClient.getTokenSettings().getSettings())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final void setObjectMapper(ObjectMapper objectMapper) {
|
||||||
|
Assert.notNull(objectMapper, "objectMapper cannot be null");
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final ObjectMapper getObjectMapper() {
|
||||||
|
return this.objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
private String writeMap(Map<String, Object> data) {
|
private String writeMap(Map<String, Object> data) {
|
||||||
try {
|
try {
|
||||||
return writeValueAsString(data);
|
return this.objectMapper.writeValueAsString(data);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract String writeValueAsString(Map<String, Object> data) throws Exception;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Nested class to protect from getting {@link NoClassDefFoundError} when Jackson 2 is
|
|
||||||
* not on the classpath.
|
|
||||||
*
|
|
||||||
* @deprecated This is used to allow transition to Jackson 3. Use {@link Jackson3}
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true, since = "7.0")
|
|
||||||
private static final class Jackson2 {
|
|
||||||
|
|
||||||
private static ObjectMapper createObjectMapper() {
|
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
ClassLoader classLoader = Jackson2.class.getClassLoader();
|
|
||||||
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
|
|
||||||
objectMapper.registerModules(securityModules);
|
|
||||||
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
|
||||||
return objectMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Nested class used to get a common default instance of {@link JsonMapper}. It is in
|
|
||||||
* a nested class to protect from getting {@link NoClassDefFoundError} when Jackson 3
|
|
||||||
* is not on the classpath.
|
|
||||||
*/
|
|
||||||
private static final class Jackson3 {
|
|
||||||
|
|
||||||
private static JsonMapper createJsonMapper() {
|
|
||||||
List<JacksonModule> modules = SecurityJacksonModules.getModules(Jackson3.class.getClassLoader());
|
|
||||||
return JsonMapper.builder().addModules(modules).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class JdbcRegisteredClientRepositoryRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
|
static class JdbcRegisteredClientRepositoryRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
|
||||||
|
|||||||
+17
-17
@@ -25,13 +25,13 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.Module;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import tools.jackson.databind.JacksonModule;
|
|
||||||
import tools.jackson.databind.json.JsonMapper;
|
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
|
||||||
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
|
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
|
||||||
import org.springframework.jdbc.core.JdbcOperations;
|
import org.springframework.jdbc.core.JdbcOperations;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
@@ -41,12 +41,13 @@ import org.springframework.jdbc.core.SqlParameterValue;
|
|||||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||||
import org.springframework.security.jackson.SecurityJacksonModules;
|
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.JsonMapperRegisteredClientParametersMapper;
|
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.JsonMapperRegisteredClientRowMapper;
|
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientRowMapper;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -221,9 +222,9 @@ public class JdbcRegisteredClientRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saveLoadRegisteredClientWhenCustomStrategiesSetThenCalled() throws Exception {
|
public void saveLoadRegisteredClientWhenCustomStrategiesSetThenCalled() throws Exception {
|
||||||
RowMapper<RegisteredClient> registeredClientRowMapper = spy(new JsonMapperRegisteredClientRowMapper());
|
RowMapper<RegisteredClient> registeredClientRowMapper = spy(new RegisteredClientRowMapper());
|
||||||
this.registeredClientRepository.setRegisteredClientRowMapper(registeredClientRowMapper);
|
this.registeredClientRepository.setRegisteredClientRowMapper(registeredClientRowMapper);
|
||||||
JsonMapperRegisteredClientParametersMapper clientParametersMapper = new JsonMapperRegisteredClientParametersMapper();
|
RegisteredClientParametersMapper clientParametersMapper = new RegisteredClientParametersMapper();
|
||||||
Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper = spy(
|
Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper = spy(
|
||||||
clientParametersMapper);
|
clientParametersMapper);
|
||||||
this.registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper);
|
this.registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper);
|
||||||
@@ -364,14 +365,16 @@ public class JdbcRegisteredClientRepositoryTests {
|
|||||||
return !result.isEmpty() ? result.get(0) : null;
|
return !result.isEmpty() ? result.get(0) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("removal")
|
||||||
private static final class CustomRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
|
private static final class CustomRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
|
||||||
|
|
||||||
private final JsonMapper jsonMapper;
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
private CustomRegisteredClientRowMapper() {
|
private CustomRegisteredClientRowMapper() {
|
||||||
List<JacksonModule> modules = SecurityJacksonModules
|
ClassLoader classLoader = CustomJdbcRegisteredClientRepository.class.getClassLoader();
|
||||||
.getModules(CustomRegisteredClientRowMapper.class.getClassLoader());
|
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
|
||||||
this.jsonMapper = JsonMapper.builder().addModules(modules).build();
|
this.objectMapper.registerModules(securityModules);
|
||||||
|
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -415,12 +418,9 @@ public class JdbcRegisteredClientRepositoryTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> parseMap(String data) {
|
private Map<String, Object> parseMap(String data) {
|
||||||
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
|
|
||||||
};
|
|
||||||
try {
|
try {
|
||||||
tools.jackson.databind.JavaType javaType = this.jsonMapper.getTypeFactory()
|
return this.objectMapper.readValue(data, new TypeReference<>() {
|
||||||
.constructType(typeReference.getType());
|
});
|
||||||
return this.jsonMapper.readValue(data, javaType);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||||
|
|||||||
+2
-2
@@ -58,7 +58,7 @@ public class RequestAttributeAuthenticationFilter extends AbstractPreAuthenticat
|
|||||||
* missing and {@code exceptionIfVariableMissing} is set to {@code true}.
|
* missing and {@code exceptionIfVariableMissing} is set to {@code true}.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
|
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
|
||||||
String principal = (String) request.getAttribute(this.principalEnvironmentVariable);
|
String principal = (String) request.getAttribute(this.principalEnvironmentVariable);
|
||||||
if (principal == null && this.exceptionIfVariableMissing) {
|
if (principal == null && this.exceptionIfVariableMissing) {
|
||||||
throw new PreAuthenticatedCredentialsNotFoundException(
|
throw new PreAuthenticatedCredentialsNotFoundException(
|
||||||
@@ -73,7 +73,7 @@ public class RequestAttributeAuthenticationFilter extends AbstractPreAuthenticat
|
|||||||
* credentials value. Otherwise a dummy value will be used.
|
* credentials value. Otherwise a dummy value will be used.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
||||||
if (this.credentialsEnvironmentVariable != null) {
|
if (this.credentialsEnvironmentVariable != null) {
|
||||||
return request.getAttribute(this.credentialsEnvironmentVariable);
|
return request.getAttribute(this.credentialsEnvironmentVariable);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -59,7 +59,7 @@ public class RequestHeaderAuthenticationFilter extends AbstractPreAuthenticatedP
|
|||||||
* {@code exceptionIfHeaderMissing} is set to {@code true}.
|
* {@code exceptionIfHeaderMissing} is set to {@code true}.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
|
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
|
||||||
String principal = request.getHeader(this.principalRequestHeader);
|
String principal = request.getHeader(this.principalRequestHeader);
|
||||||
if (principal == null && this.exceptionIfHeaderMissing) {
|
if (principal == null && this.exceptionIfHeaderMissing) {
|
||||||
throw new PreAuthenticatedCredentialsNotFoundException(
|
throw new PreAuthenticatedCredentialsNotFoundException(
|
||||||
@@ -74,7 +74,7 @@ public class RequestHeaderAuthenticationFilter extends AbstractPreAuthenticatedP
|
|||||||
* will be used.
|
* will be used.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
||||||
if (this.credentialsRequestHeader != null) {
|
if (this.credentialsRequestHeader != null) {
|
||||||
return request.getHeader(this.credentialsRequestHeader);
|
return request.getHeader(this.credentialsRequestHeader);
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-44
@@ -22,15 +22,15 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.webauthn4j.WebAuthnManager;
|
import com.webauthn4j.WebAuthnManager;
|
||||||
|
import com.webauthn4j.authenticator.Authenticator;
|
||||||
|
import com.webauthn4j.authenticator.AuthenticatorImpl;
|
||||||
import com.webauthn4j.converter.util.CborConverter;
|
import com.webauthn4j.converter.util.CborConverter;
|
||||||
import com.webauthn4j.converter.util.ObjectConverter;
|
import com.webauthn4j.converter.util.ObjectConverter;
|
||||||
import com.webauthn4j.credential.CredentialRecordImpl;
|
|
||||||
import com.webauthn4j.data.AuthenticationData;
|
import com.webauthn4j.data.AuthenticationData;
|
||||||
import com.webauthn4j.data.AuthenticationParameters;
|
import com.webauthn4j.data.AuthenticationParameters;
|
||||||
import com.webauthn4j.data.RegistrationData;
|
import com.webauthn4j.data.RegistrationData;
|
||||||
@@ -95,7 +95,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
|||||||
|
|
||||||
private final PublicKeyCredentialRpEntity rp;
|
private final PublicKeyCredentialRpEntity rp;
|
||||||
|
|
||||||
private ObjectConverter objectConverter = new ObjectConverter();
|
private final ObjectConverter objectConverter = new ObjectConverter();
|
||||||
|
|
||||||
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||||
|
|
||||||
@@ -137,15 +137,6 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
|||||||
this.webAuthnManager = webAuthnManager;
|
this.webAuthnManager = webAuthnManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link ObjectConverter} to use.
|
|
||||||
* @param objectConverter the {@link ObjectConverter} to use. Cannot be null.
|
|
||||||
*/
|
|
||||||
void setObjectConverter(ObjectConverter objectConverter) {
|
|
||||||
Assert.notNull(objectConverter, "objectConverter cannot be null");
|
|
||||||
this.objectConverter = objectConverter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a {@link Consumer} used to customize the
|
* Sets a {@link Consumer} used to customize the
|
||||||
* {@link PublicKeyCredentialCreationOptionsBuilder} for
|
* {@link PublicKeyCredentialCreationOptionsBuilder} for
|
||||||
@@ -257,7 +248,9 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
|||||||
byte[] attestationObject = response.getAttestationObject().getBytes();
|
byte[] attestationObject = response.getAttestationObject().getBytes();
|
||||||
byte[] clientDataJSON = response.getClientDataJSON().getBytes();
|
byte[] clientDataJSON = response.getClientDataJSON().getBytes();
|
||||||
Challenge challenge = new DefaultChallenge(base64Challenge);
|
Challenge challenge = new DefaultChallenge(base64Challenge);
|
||||||
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge);
|
byte[] tokenBindingId = null /* set tokenBindingId */; // FIXME:
|
||||||
|
// https://www.w3.org/TR/webauthn-1/#dom-collectedclientdata-tokenbinding
|
||||||
|
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge, tokenBindingId);
|
||||||
boolean userVerificationRequired = creationOptions.getAuthenticatorSelection()
|
boolean userVerificationRequired = creationOptions.getAuthenticatorSelection()
|
||||||
.getUserVerification() == UserVerificationRequirement.REQUIRED;
|
.getUserVerification() == UserVerificationRequirement.REQUIRED;
|
||||||
// requireUserPresence The constant Boolean value true
|
// requireUserPresence The constant Boolean value true
|
||||||
@@ -270,7 +263,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
|||||||
transports);
|
transports);
|
||||||
RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, pubKeyCredParams,
|
RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, pubKeyCredParams,
|
||||||
userVerificationRequired, userPresenceRequired);
|
userVerificationRequired, userPresenceRequired);
|
||||||
RegistrationData wa4jRegistrationData = this.webAuthnManager.verify(webauthn4jRegistrationRequest,
|
RegistrationData wa4jRegistrationData = this.webAuthnManager.validate(webauthn4jRegistrationRequest,
|
||||||
registrationParameters);
|
registrationParameters);
|
||||||
AttestationObject wa4jAttestationObject = wa4jRegistrationData.getAttestationObject();
|
AttestationObject wa4jAttestationObject = wa4jRegistrationData.getAttestationObject();
|
||||||
Assert.notNull(wa4jAttestationObject, "attestationObject cannot be null");
|
Assert.notNull(wa4jAttestationObject, "attestationObject cannot be null");
|
||||||
@@ -313,7 +306,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
|||||||
|
|
||||||
private List<com.webauthn4j.data.PublicKeyCredentialParameters> convertCredentialParamsToWebauthn4j(
|
private List<com.webauthn4j.data.PublicKeyCredentialParameters> convertCredentialParamsToWebauthn4j(
|
||||||
List<PublicKeyCredentialParameters> parameters) {
|
List<PublicKeyCredentialParameters> parameters) {
|
||||||
return parameters.stream().map(this::convertParamToWebauthn4j).toList();
|
return parameters.stream().map(this::convertParamToWebauthn4j).collect(Collectors.toUnmodifiableList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private com.webauthn4j.data.PublicKeyCredentialParameters convertParamToWebauthn4j(
|
private com.webauthn4j.data.PublicKeyCredentialParameters convertParamToWebauthn4j(
|
||||||
@@ -389,29 +382,28 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
|||||||
.getAuthenticatorData();
|
.getAuthenticatorData();
|
||||||
AttestedCredentialData wa4jCredData = wa4jAuthData.getAttestedCredentialData();
|
AttestedCredentialData wa4jCredData = wa4jAuthData.getAttestedCredentialData();
|
||||||
Assert.notNull(wa4jCredData, "attestedCredentialData cannot be null");
|
Assert.notNull(wa4jCredData, "attestedCredentialData cannot be null");
|
||||||
|
AttestedCredentialData data = new AttestedCredentialData(wa4jCredData.getAaguid(), keyId.getBytes(),
|
||||||
|
wa4jCredData.getCOSEKey());
|
||||||
|
|
||||||
|
Authenticator authenticator = new AuthenticatorImpl(data, wa4jAttestationObject.getAttestationStatement(),
|
||||||
|
credentialRecord.getSignatureCount());
|
||||||
Set<Origin> origins = toOrigins();
|
Set<Origin> origins = toOrigins();
|
||||||
Challenge challenge = new DefaultChallenge(requestOptions.getChallenge().getBytes());
|
Challenge challenge = new DefaultChallenge(requestOptions.getChallenge().getBytes());
|
||||||
|
// FIXME: should populate this
|
||||||
|
byte[] tokenBindingId = null /* set tokenBindingId */;
|
||||||
String rpId = requestOptions.getRpId();
|
String rpId = requestOptions.getRpId();
|
||||||
Assert.notNull(rpId, "rpId cannot be null");
|
Assert.notNull(rpId, "rpId cannot be null");
|
||||||
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge);
|
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge, tokenBindingId);
|
||||||
boolean userVerificationRequired = request.getRequestOptions()
|
boolean userVerificationRequired = request.getRequestOptions()
|
||||||
.getUserVerification() == UserVerificationRequirement.REQUIRED;
|
.getUserVerification() == UserVerificationRequirement.REQUIRED;
|
||||||
|
|
||||||
com.webauthn4j.data.AuthenticationRequest authenticationRequest = new com.webauthn4j.data.AuthenticationRequest(
|
com.webauthn4j.data.AuthenticationRequest authenticationRequest = new com.webauthn4j.data.AuthenticationRequest(
|
||||||
request.getPublicKey().getRawId().getBytes(), assertionResponse.getAuthenticatorData().getBytes(),
|
request.getPublicKey().getId().getBytes(), assertionResponse.getAuthenticatorData().getBytes(),
|
||||||
assertionResponse.getClientDataJSON().getBytes(), assertionResponse.getSignature().getBytes());
|
assertionResponse.getClientDataJSON().getBytes(), assertionResponse.getSignature().getBytes());
|
||||||
|
AuthenticationParameters authenticationParameters = new AuthenticationParameters(serverProperty, authenticator,
|
||||||
|
userVerificationRequired);
|
||||||
|
|
||||||
// CollectedClientData and ExtensionsClientOutputs is registration data, and can
|
AuthenticationData wa4jAuthenticationData = this.webAuthnManager.validate(authenticationRequest,
|
||||||
// be null at authentication time.
|
|
||||||
com.webauthn4j.credential.CredentialRecord wa4jCredentialRecord = new CredentialRecordImpl(
|
|
||||||
wa4jAttestationObject, null, null, convertTransportsToWebauthn4j(credentialRecord.getTransports()));
|
|
||||||
List<byte[]> allowCredentials = convertAllowedCredentialsToWebauthn4j(
|
|
||||||
request.getRequestOptions().getAllowCredentials());
|
|
||||||
AuthenticationParameters authenticationParameters = new AuthenticationParameters(serverProperty,
|
|
||||||
wa4jCredentialRecord, allowCredentials.isEmpty() ? null : allowCredentials, userVerificationRequired);
|
|
||||||
|
|
||||||
AuthenticationData wa4jAuthenticationData = this.webAuthnManager.verify(authenticationRequest,
|
|
||||||
authenticationParameters);
|
authenticationParameters);
|
||||||
|
|
||||||
AuthenticatorData<AuthenticationExtensionAuthenticatorOutput> wa4jValidatedAuthData = wa4jAuthenticationData
|
AuthenticatorData<AuthenticationExtensionAuthenticatorOutput> wa4jValidatedAuthData = wa4jAuthenticationData
|
||||||
@@ -432,21 +424,4 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
|||||||
return userEntity;
|
return userEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<com.webauthn4j.data.AuthenticatorTransport> convertTransportsToWebauthn4j(
|
|
||||||
Set<AuthenticatorTransport> transports) {
|
|
||||||
return transports.stream()
|
|
||||||
.map(AuthenticatorTransport::getValue)
|
|
||||||
.map(com.webauthn4j.data.AuthenticatorTransport::create)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<byte[]> convertAllowedCredentialsToWebauthn4j(
|
|
||||||
List<PublicKeyCredentialDescriptor> allowedCredentials) {
|
|
||||||
return allowedCredentials.stream()
|
|
||||||
.map(PublicKeyCredentialDescriptor::getId)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.map(Bytes::getBytes)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
-55
@@ -27,20 +27,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
|
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
|
||||||
import com.webauthn4j.WebAuthnManager;
|
|
||||||
import com.webauthn4j.converter.AttestationObjectConverter;
|
import com.webauthn4j.converter.AttestationObjectConverter;
|
||||||
import com.webauthn4j.converter.util.ObjectConverter;
|
import com.webauthn4j.converter.util.ObjectConverter;
|
||||||
import com.webauthn4j.data.AuthenticationData;
|
|
||||||
import com.webauthn4j.data.AuthenticationRequest;
|
|
||||||
import com.webauthn4j.data.attestation.AttestationObject;
|
import com.webauthn4j.data.attestation.AttestationObject;
|
||||||
import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData;
|
|
||||||
import com.webauthn4j.data.attestation.authenticator.AuthenticatorData;
|
import com.webauthn4j.data.attestation.authenticator.AuthenticatorData;
|
||||||
import com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput;
|
import com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput;
|
||||||
import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration;
|
import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
@@ -49,14 +44,12 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
|||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
|
|
||||||
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
|
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
|
||||||
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse.AuthenticatorAttestationResponseBuilder;
|
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse.AuthenticatorAttestationResponseBuilder;
|
||||||
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
|
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
|
||||||
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
|
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
|
||||||
import org.springframework.security.web.webauthn.api.Bytes;
|
import org.springframework.security.web.webauthn.api.Bytes;
|
||||||
import org.springframework.security.web.webauthn.api.CredentialRecord;
|
import org.springframework.security.web.webauthn.api.CredentialRecord;
|
||||||
import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord;
|
|
||||||
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
|
import org.springframework.security.web.webauthn.api.PublicKeyCredential;
|
||||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
|
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
|
||||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
|
import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
|
||||||
@@ -64,11 +57,9 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialParamete
|
|||||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
|
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
|
||||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
|
import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
|
||||||
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
|
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
|
||||||
import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses;
|
|
||||||
import org.springframework.security.web.webauthn.api.TestAuthenticatorAttestationResponses;
|
import org.springframework.security.web.webauthn.api.TestAuthenticatorAttestationResponses;
|
||||||
import org.springframework.security.web.webauthn.api.TestCredentialRecords;
|
import org.springframework.security.web.webauthn.api.TestCredentialRecords;
|
||||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
|
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
|
||||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions;
|
|
||||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities;
|
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities;
|
||||||
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentials;
|
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentials;
|
||||||
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
|
import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
|
||||||
@@ -76,9 +67,7 @@ import org.springframework.security.web.webauthn.api.UserVerificationRequirement
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
|
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verifyNoInteractions;
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
@@ -598,50 +587,6 @@ class Webauthn4jRelyingPartyOperationsTests {
|
|||||||
.containsExactly(credentialRecord.getCredentialId());
|
.containsExactly(credentialRecord.getCredentialId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// gh-18158
|
|
||||||
@Test
|
|
||||||
void authenticateThenWa4jRequestCredentialIdIsRawIdBytes() throws Exception {
|
|
||||||
PublicKeyCredentialRequestOptions options = TestPublicKeyCredentialRequestOptions.create().build();
|
|
||||||
AuthenticatorAssertionResponse response = TestAuthenticationAssertionResponses
|
|
||||||
.createAuthenticatorAssertionResponse()
|
|
||||||
.build();
|
|
||||||
PublicKeyCredential<AuthenticatorAssertionResponse> credentials = TestPublicKeyCredentials
|
|
||||||
.createPublicKeyCredential(response)
|
|
||||||
.build();
|
|
||||||
RelyingPartyAuthenticationRequest request = new RelyingPartyAuthenticationRequest(options, credentials);
|
|
||||||
PublicKeyCredential<AuthenticatorAssertionResponse> publicKey = request.getPublicKey();
|
|
||||||
|
|
||||||
ImmutableCredentialRecord credentialRecord = TestCredentialRecords.fullUserCredential().build();
|
|
||||||
given(this.userCredentials.findByCredentialId(publicKey.getRawId())).willReturn(credentialRecord);
|
|
||||||
ObjectMapper json = mock(ObjectMapper.class);
|
|
||||||
ObjectMapper cbor = mock(ObjectMapper.class);
|
|
||||||
given(cbor.getFactory()).willReturn(mock(CBORFactory.class));
|
|
||||||
AttestationObject attestationObject = mock(AttestationObject.class);
|
|
||||||
AuthenticatorData wa4jAuthData = mock(AuthenticatorData.class);
|
|
||||||
given(attestationObject.getAuthenticatorData()).willReturn(wa4jAuthData);
|
|
||||||
given(wa4jAuthData.getAttestedCredentialData()).willReturn(mock(AttestedCredentialData.class));
|
|
||||||
given(cbor.readValue(credentialRecord.getAttestationObject().getBytes(), AttestationObject.class))
|
|
||||||
.willReturn(attestationObject);
|
|
||||||
this.rpOperations.setObjectConverter(new ObjectConverter(json, cbor));
|
|
||||||
|
|
||||||
WebAuthnManager manager = mock(WebAuthnManager.class);
|
|
||||||
ArgumentCaptor<AuthenticationRequest> wa4jRequest = ArgumentCaptor.forClass(AuthenticationRequest.class);
|
|
||||||
AuthenticationData wa4jData = mock(AuthenticationData.class);
|
|
||||||
given(wa4jData.getAuthenticatorData()).willReturn(mock(AuthenticatorData.class));
|
|
||||||
given(manager.verify(wa4jRequest.capture(), any())).willReturn(wa4jData);
|
|
||||||
given(this.userEntities.findById(any())).willReturn(TestPublicKeyCredentialUserEntities.userEntity().build());
|
|
||||||
this.rpOperations.setWebAuthnManager(manager);
|
|
||||||
|
|
||||||
this.rpOperations.authenticate(request);
|
|
||||||
|
|
||||||
// this ensures that our next assertion is valid (we want the rawId bytes, not the
|
|
||||||
// id bytes to be used)
|
|
||||||
assertThat(publicKey.getRawId().getBytes()).isNotEqualTo(publicKey.getId().getBytes());
|
|
||||||
// ensure that the raw id bytes are passed into webauthn4j (not the id bytes which
|
|
||||||
// are base64 encoded)
|
|
||||||
assertThat(wa4jRequest.getValue().getCredentialId()).isEqualTo(publicKey.getRawId().getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AuthenticatorAttestationResponse setFlag(byte... flags) throws Exception {
|
private static AuthenticatorAttestationResponse setFlag(byte... flags) throws Exception {
|
||||||
AuthenticatorAttestationResponseBuilder authAttResponseBldr = TestAuthenticatorAttestationResponses
|
AuthenticatorAttestationResponseBuilder authAttResponseBldr = TestAuthenticatorAttestationResponses
|
||||||
.createAuthenticatorAttestationResponse();
|
.createAuthenticatorAttestationResponse();
|
||||||
|
|||||||
Reference in New Issue
Block a user