Compare commits

..

No commits in common. "main" and "7.0.0-RC2" have entirely different histories.

54 changed files with 495 additions and 1298 deletions

View File

@ -111,3 +111,11 @@ updates:
labels:
- 'type: task'
- 'in: build'
- package-ecosystem: npm
target-branch: 6.3.x
directory: /docs
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'

View File

@ -35,6 +35,13 @@ jobs:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
default-publish-milestones-central: true
secrets: inherit
deploy-docs:
name: Deploy Docs
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1
with:
should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
deploy-schema:
name: Deploy Schema
needs: [ build ]
@ -44,7 +51,7 @@ jobs:
secrets: inherit
perform-release:
name: Perform Release
needs: [ deploy-artifacts, deploy-schema ]
needs: [ deploy-artifacts, deploy-docs, deploy-schema ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with:
should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }}

View File

@ -1,27 +0,0 @@
name: Finalize Release
on:
workflow_dispatch: # Manual trigger
inputs:
version:
description: The Spring Security release to finalize (e.g. 7.0.0-RC2)
required: true
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
permissions:
contents: read
jobs:
perform-release:
name: Perform Release
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with:
should-perform-release: true
project-version: ${{ inputs.version }}
milestone-repo-url: https://repo1.maven.org/maven2
release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing
secrets: inherit

View File

@ -42,7 +42,7 @@ springRelease {
weekOfMonth = 3
dayOfWeek = 1
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/reference/{version}/api/java/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
replaceSnapshotVersionInReferenceDocUrl = true
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.spring.gradle.convention
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.Plugin
import org.gradle.api.Project
public class DeployDocsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPluginManager().apply('org.hidetake.ssh')
project.ssh.settings {
knownHosts = allowAnyHosts
}
project.remotes {
docs {
role 'docs'
if (project.hasProperty('deployDocsHost')) {
host = project.findProperty('deployDocsHost')
} else {
host = 'docs.af.pivotal.io'
}
retryCount = 5 // retry 5 times (default is 0)
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
user = project.findProperty('deployDocsSshUsername')
if (project.hasProperty('deployDocsSshKeyPath')) {
identity = project.file(project.findProperty('deployDocsSshKeyPath'))
} else if (project.hasProperty('deployDocsSshKey')) {
identity = project.findProperty('deployDocsSshKey')
}
if(project.hasProperty('deployDocsSshPassphrase')) {
passphrase = project.findProperty('deployDocsSshPassphrase')
}
}
}
project.task('deployDocs') {
dependsOn 'docsZip'
doFirst {
project.ssh.run {
session(project.remotes.docs) {
def now = System.currentTimeMillis()
def name = project.rootProject.name
def version = project.rootProject.version
def tempPath = "/tmp/${name}-${now}-docs/".replaceAll(' ', '_')
execute "mkdir -p $tempPath"
project.tasks.docsZip.outputs.each { o ->
put from: o.files, into: tempPath
}
execute "unzip $tempPath*.zip -d $tempPath"
def extractPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/docs/${name}/${version}/"
execute "rm -rf $extractPath"
execute "mkdir -p $extractPath"
execute "mv $tempPath/docs/* $extractPath"
execute "chmod -R g+w $extractPath"
}
}
}
}
}
}

View File

@ -17,6 +17,7 @@ public class DocsPlugin implements Plugin<Project> {
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(BasePlugin);
pluginManager.apply(DeployDocsPlugin);
pluginManager.apply(JavadocApiPlugin);
Task docsZip = project.tasks.create('docsZip', Zip) {

View File

@ -30,6 +30,16 @@ ossrh: {
}
}
},
docs: {
stage('Deploy Docs') {
node {
checkout scm
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
}
}
}
},
schema: {
stage('Deploy Schema') {
node {
@ -39,4 +49,4 @@ schema: {
}
}
}
}
}

View File

@ -31,7 +31,6 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
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.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@ -69,7 +67,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Daniel Garnier-Moiroux
*/
@Disabled
@org.junit.jupiter.api.Disabled
class WebAuthnWebDriverTests {
private String baseUrl;
@ -84,8 +82,6 @@ class WebAuthnWebDriverTests {
private static final String PASSWORD = "password";
private String authenticatorId = null;
@BeforeAll
static void startChromeDriverService() throws Exception {
driverService = new ChromeDriverService.Builder().usingAnyFreePort().build();
@ -148,7 +144,7 @@ class WebAuthnWebDriverTests {
@Test
void loginWhenNoValidAuthenticatorCredentialsThenRejects() {
createVirtualAuthenticator(true);
this.getAndWait("/", "/login");
this.driver.get(this.baseUrl);
this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error"));
}
@ -157,7 +153,7 @@ class WebAuthnWebDriverTests {
void registerWhenNoLabelThenRejects() {
login();
this.getAndWait("/webauthn/register");
this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(registerPasskeyButton()).click();
assertHasAlertStartingWith("error", "Error: Passkey Label is required");
@ -167,7 +163,7 @@ class WebAuthnWebDriverTests {
void registerWhenAuthenticatorNoUserVerificationThenRejects() {
createVirtualAuthenticator(false);
login();
this.getAndWait("/webauthn/register");
this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click();
@ -182,8 +178,7 @@ class WebAuthnWebDriverTests {
* <li>Step 1: Log in with username / password</li>
* <li>Step 2: Register a credential from the virtual authenticator</li>
* <li>Step 3: Log out</li>
* <li>Step 4: Log in with the authenticator (no allowCredentials)</li>
* <li>Step 5: Log in again with the same authenticator (with allowCredentials)</li>
* <li>Step 4: Log in with the authenticator</li>
* </ul>
*/
@Test
@ -195,7 +190,7 @@ class WebAuthnWebDriverTests {
login();
// 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(registerPasskeyButton()).click();
@ -217,58 +212,9 @@ class WebAuthnWebDriverTests {
logout();
// 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();
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>
*/
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;
cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false));
// this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions());
//@formatter:off
Map<String, Object> cmdResponse = cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
Map.of(
"options",
Map.of(
@ -305,38 +248,21 @@ class WebAuthnWebDriverTests {
)
));
//@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() {
this.getAndWait("/", "/login");
this.driver.get(this.baseUrl);
this.driver.findElement(usernameField()).sendKeys(USERNAME);
this.driver.findElement(passwordField()).sendKeys(PASSWORD);
this.driver.findElement(signinWithUsernamePasswordButton()).click();
// Ensure login has completed
await(() -> assertThat(this.driver.getCurrentUrl()).doesNotContain("/login"));
}
private void logout() {
this.getAndWait("/logout");
this.driver.get(this.baseUrl + "/logout");
this.driver.findElement(logoutButton()).click();
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) {
WebElement alert = this.driver.findElement(new By.ById(alertType));
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() {
return new By.ById("label");
}
@ -408,10 +325,6 @@ class WebAuthnWebDriverTests {
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
* can configurer WebAuthnConfigurer#allowedOrigin

View File

@ -42,8 +42,7 @@ final class MethodSecuritySelector implements ImportSelector {
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
private static final boolean isWebPresent = ClassUtils
.isPresent("org.springframework.web.servlet.DispatcherServlet", null)
&& ClassUtils.isPresent("org.springframework.security.web.util.ThrowableAnalyzer", null);
.isPresent("org.springframework.web.servlet.DispatcherServlet", null);
private static final boolean isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null);

View File

@ -2052,6 +2052,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* http
* // ...
* .webAuthn((webAuthn) -&gt; webAuthn
* .rpName("Spring Security Relying Party")
* .rpId("example.com")
* .allowedOrigins("https://example.com")
* );

View File

@ -177,7 +177,6 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
webAuthnAuthnFilter.setAuthenticationManager(
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
rpOperations);
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
@ -257,10 +256,9 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
WebAuthnRelyingPartyOperations.class);
String rpName = (this.rpName != null) ? this.rpName : this.rpId;
return webauthnOperationsBean
.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities, userCredentials,
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins));
return webauthnOperationsBean.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities,
userCredentials, PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(),
this.allowedOrigins));
}
}

View File

@ -16,12 +16,10 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
@ -38,12 +36,10 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@ -54,7 +50,6 @@ import org.springframework.security.web.servlet.util.matcher.PathPatternRequestM
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
@ -88,8 +83,6 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator;
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidatorComposite;
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
/**
@ -255,16 +248,8 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach((authenticationProvider) -> {
httpSecurity.authenticationProvider(postProcess(authenticationProvider));
if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) {
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationCodeRequestAuthenticationProvider.class,
"getAuthenticationValidatorComposite");
ReflectionUtils.makeAccessible(method);
this.authorizationCodeRequestAuthenticationValidatorComposite = (Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext>) ReflectionUtils
.invokeMethod(method, authenticationProvider);
}
});
authenticationProviders.forEach(
(authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
@ -297,18 +282,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
if (this.sessionAuthenticationStrategy != null) {
authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy);
}
httpSecurity.addFilterAfter(postProcess(authorizationEndpointFilter), AuthorizationFilter.class);
// Create and add
// OAuth2AuthorizationEndpointFilter.OAuth2AuthorizationCodeRequestValidatingFilter
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationEndpointFilter.class,
"createAuthorizationCodeRequestValidatingFilter", RegisteredClientRepository.class, Consumer.class);
ReflectionUtils.makeAccessible(method);
RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils
.getRegisteredClientRepository(httpSecurity);
Filter authorizationCodeRequestValidatingFilter = (Filter) ReflectionUtils.invokeMethod(method,
authorizationEndpointFilter, registeredClientRepository,
this.authorizationCodeRequestAuthenticationValidatorComposite);
httpSecurity.addFilterBefore(postProcess(authorizationCodeRequestValidatingFilter),
httpSecurity.addFilterBefore(postProcess(authorizationEndpointFilter),
AbstractPreAuthenticatedProcessingFilter.class);
}

View File

@ -19,13 +19,10 @@ package org.springframework.security.config.annotation.web.configurers;
import java.nio.charset.StandardCharsets;
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.extension.ExtendWith;
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.Configuration;
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.webauthn.api.PublicKeyCredentialCreationOptions;
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.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
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.willAnswer;
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.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@ -94,14 +88,6 @@ public class WebAuthnConfigurerTests {
.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
public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception {
this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire();
@ -141,42 +127,6 @@ public class WebAuthnConfigurerTests {
.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
public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception {
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
@EnableWebSecurity
static class DefaultWebauthnConfiguration {
@ -374,7 +304,8 @@ public class WebAuthnConfigurerTests {
http
.formLogin(Customizer.withDefaults())
.webAuthn((authn) -> authn
.rpId("example.com")
.rpId("spring.io")
.rpName("spring")
);
// @formatter:on
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
@EnableWebSecurity
static class NoFormLoginAndDefaultRegistrationPageConfiguration {

View File

@ -307,8 +307,8 @@ public class OAuth2AuthorizationCodeGrantTests {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
.queryParams(getAuthorizationRequestParameters(registeredClient)))
.perform(
get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient)))
.andExpect(status().isBadRequest())
.andReturn();
}
@ -851,31 +851,21 @@ public class OAuth2AuthorizationCodeGrantTests {
this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(registeredClient);
TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password");
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, registeredClient.getScopes(),
additionalParameters);
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(),
Instant.now().plus(5, ChronoUnit.MINUTES));
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED,
registeredClient.getScopes());
given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication);
given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthenticationResult);
given(authorizationRequestAuthenticationProvider
.supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true);
given(authorizationRequestAuthenticationProvider.authenticate(any()))
.willReturn(authorizationCodeRequestAuthenticationResult);
this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
.queryParams(getAuthorizationRequestParameters(registeredClient))
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient))
.with(user("user")))
.andExpect(status().isOk());
@ -890,7 +880,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|| converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter
|| converter instanceof OAuth2AuthorizationConsentAuthenticationConverter);
verify(authorizationRequestAuthenticationProvider).authenticate(eq(authorizationCodeRequestAuthentication));
verify(authorizationRequestAuthenticationProvider)
.authenticate(eq(authorizationCodeRequestAuthenticationResult));
@SuppressWarnings("unchecked")
ArgumentCaptor<List<AuthenticationProvider>> authenticationProvidersCaptor = ArgumentCaptor

View File

@ -275,7 +275,7 @@ class ServerJwtDslTests {
}
class NullConverter: Converter<Jwt, Mono<AbstractAuthenticationToken>> {
override fun convert(source: Jwt): Mono<AbstractAuthenticationToken> {
override fun convert(source: Jwt): Mono<AbstractAuthenticationToken>? {
return Mono.empty()
}

View File

@ -16,7 +16,6 @@
package org.springframework.security.crypto.bcrypt;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
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.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
* @author Dave Syer
@ -238,23 +236,4 @@ public class BCryptPasswordEncoderTests extends AbstractPasswordEncoderValidatio
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");
}
}

View File

@ -31,7 +31,7 @@ urls:
redirect_facility: httpd
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
snapshot: true
runtime:
log:

View File

@ -90,7 +90,6 @@
**** xref:servlet/oauth2/resource-server/multitenancy.adoc[Multitenancy]
**** 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/protected-resource-metadata.adoc[Protected Resource Metadata]
*** 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/configuration-model.adoc[Configuration Model]

View File

@ -1,3 +1,4 @@
[[webflux-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.
====

View File

@ -39,7 +39,7 @@ Gradle::
+
[source,groovy,role="secondary",subs="verbatim,attributes"]
----
dependencies {
depenendencies {
implementation "org.springframework.security:spring-security-web"
implementation "com.webauthn4j:webauthn4j-core:{webauthn4j-core-version}"
}
@ -65,6 +65,7 @@ SecurityFilterChain filterChain(HttpSecurity http) {
// ...
.formLogin(withDefaults())
.webAuthn((webAuthn) -> webAuthn
.rpName("Spring Security Relying Party")
.rpId("example.com")
.allowedOrigins("https://example.com")
// optional properties
@ -95,6 +96,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
// ...
http {
webAuthn {
rpName = "Spring Security Relying Party"
rpId = "example.com"
allowedOrigins = setOf("https://example.com")
// optional properties

View File

@ -38,7 +38,7 @@ Gradle::
+
[source,groovy,role="secondary"]
----
dependencies {
depenendencies {
implementation "org.springframework.boot:spring-boot-starter-data-ldap"
implementation "org.springframework.security:spring-security-ldap"
}
@ -150,7 +150,7 @@ Gradle::
+
[source,groovy,role="secondary",subs="verbatim,attributes"]
----
dependencies {
depenendencies {
runtimeOnly "com.unboundid:unboundid-ldapsdk:{unboundid-ldapsdk-version}"
}
----

View File

@ -639,7 +639,7 @@ This is because Spring Security requires all URIs to be absolute (minus the cont
[TIP]
=====
There are several other components that create request matchers for you like {spring-boot-api-url}org/springframework/boot/security/autoconfigure/web/servlet/PathRequest.html[`PathRequest#toStaticResources#atCommonLocations`]
There are several other components that create request matchers for you like {spring-boot-api-url}org/springframework/boot/autoconfigure/security/servlet/PathRequest.html[`PathRequest#toStaticResources#atCommonLocations`]
=====
[[match-by-custom]]

View File

@ -493,14 +493,14 @@ public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurit
private boolean flag;
@Override
public void init(HttpSecurity http) {
public void init(HttpSecurity http) throws Exception {
// any method that adds another configurer
// must be done in the init method
http.csrf(csrf -> csrf.disable());
http.csrf().disable();
}
@Override
public void configure(HttpSecurity http) {
public void configure(HttpSecurity http) throws Exception {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
// here we lookup from the ApplicationContext. You can also just create a new instance.

View File

@ -118,7 +118,7 @@ The default arrangement of Spring Boot and Spring Security affords the following
* Publishes xref:servlet/authentication/events.adoc[authentication success and failure events]
It can be helpful to understand how Spring Boot is coordinating with Spring Security to achieve this.
Taking a look at {spring-boot-api-url}org/springframework/boot/security/autoconfigure/SecurityAutoConfiguration.html[Boot's security auto configuration], it does the following (simplified for illustration):
Taking a look at {spring-boot-api-url}org/springframework/boot/autoconfigure/security/servlet/SecurityAutoConfiguration.html[Boot's security auto configuration], it does the following (simplified for illustration):
.Spring Boot Security Auto Configuration
[source,java]

View File

@ -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.
====

View File

@ -370,7 +370,7 @@ Java::
----
@Bean
public AnnotationTemplateExpressionDefaults templateDefaults() {
return new AnnotationTemplateExpressionDefaults();
return new AnnotationTemplateExpressionDeafults();
}
----
@ -380,7 +380,7 @@ Kotlin::
----
@Bean
fun templateDefaults(): AnnotationTemplateExpressionDefaults {
return AnnotationTemplateExpressionDefaults()
return AnnotationTemplateExpressionDeafults()
}
----

View File

@ -108,34 +108,6 @@ spring:
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.
[[oauth2AuthorizationServer-defining-required-components]]

View File

@ -41,7 +41,7 @@ See xref:servlet/oauth2/resource-server/index.adoc[OAuth 2.0 Resource Server] fo
To get started, add the `spring-security-oauth2-resource-server` dependency to your project.
When using Spring Boot, add the following starter:
.OAuth2 Resource Server with Spring Boot
.OAuth2 Client with Spring Boot
[tabs]
======
Gradle::

View File

@ -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].

View File

@ -1,6 +1,6 @@
{
"dependencies": {
"antora": "3.2.0-alpha.11",
"antora": "3.2.0-alpha.10",
"@antora/atlas-extension": "1.0.0-alpha.5",
"@antora/collector-extension": "1.0.2",
"@asciidoctor/tabs": "1.0.0-beta.6",

View File

@ -14,7 +14,7 @@
# limitations under the License.
#
springBootVersion=4.0.0-SNAPSHOT
version=7.0.3-SNAPSHOT
version=7.0.0-RC2
samplesBranch=main
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true

View File

@ -4,39 +4,39 @@ io-rsocket = "1.1.5"
io-spring-javaformat = "0.0.47"
io-spring-nohttp = "0.0.11"
jakarta-websocket = "2.2.0"
org-apache-maven-resolver = "1.9.25"
org-aspectj = "1.9.25.1"
org-apache-maven-resolver = "1.9.24"
org-aspectj = "1.9.24"
org-bouncycastle = "1.80"
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-mockito = "5.17.0"
org-opensaml5 = "5.1.6"
org-springframework = "7.0.2"
org-springframework = "7.0.0-RC1"
com-password4j = "1.8.4"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.22"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.20.1"
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.20"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.20.0"
com-google-inject-guice = "com.google.inject:guice:3.0"
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.4"
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.26.1"
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.4"
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.3"
com-jayway-jsonpath-json-path = "com.jayway.jsonpath:json-path:2.9.0"
commons-collections = "commons-collections:commons-collections:3.2.2"
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.14"
io-mockk = "io.mockk:mockk:1.14.7"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.1"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.12"
io-mockk = "io.mockk:mockk:1.14.6"
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-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-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.13"
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.10"
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:3.0.0"
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.2.0"
@ -50,8 +50,8 @@ ldapsdk = "ldapsdk:ldapsdk:4.1"
net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0"
org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.11.1"
org-apache-httpcomponents-httpclient = "org.apache.httpcomponents.client5:httpclient5:5.5.1"
org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.1'
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.12"
org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.0'
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.11"
org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" }
@ -68,9 +68,9 @@ org-hamcrest = "org.hamcrest:hamcrest:2.2"
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.0.10.Final"
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-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-junit-junit-bom = "org.junit:junit-bom:6.0.1"
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-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml5" }
org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" }
@ -81,11 +81,11 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
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-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.1"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.1"
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-RC1"
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"
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.0.3"
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.0.1"
com-google-code-gson-gson = "com.google.code.gson:gson:2.13.2"
com-thaiopensource-trag = "com.thaiopensource:trang:20091111"

View File

@ -1944,9 +1944,9 @@
"dev": true
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
@ -4524,9 +4524,9 @@
"dev": true
},
"js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"

View File

@ -466,7 +466,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
/**
* The default {@link RowMapper} that maps the current row in
* {@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
* @since 7.0
@ -482,7 +482,6 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
public JsonMapperOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository,
JsonMapper jsonMapper) {
super(registeredClientRepository);
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
this.jsonMapper = jsonMapper;
}
@ -545,7 +544,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
private LobHandler lobHandler = new DefaultLobHandler();
private AbstractOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
AbstractOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.registeredClientRepository = registeredClientRepository;
}
@ -714,36 +713,42 @@ 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}.
* 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.
*/
public static class JsonMapperOAuth2AuthorizationParametersMapper
extends AbstractOAuth2AuthorizationParametersMapper {
@Deprecated(forRemoval = true, since = "7.0")
private static final class Jackson2 {
private final JsonMapper jsonMapper;
public JsonMapperOAuth2AuthorizationParametersMapper() {
this(Jackson3.createJsonMapper());
}
public JsonMapperOAuth2AuthorizationParametersMapper(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);
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;
}
}
/**
* A {@code Function} that maps {@link OAuth2Authorization} to a {@code List} of
* {@link SqlParameterValue} using an instance of Jackson 2's {@link ObjectMapper}.
*
* @deprecated Use {@link JsonMapperOAuth2AuthorizationParametersMapper} to switch to
* 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 {
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.
*/
@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}
* of {@link SqlParameterValue}.
@ -774,7 +805,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
private abstract static class AbstractOAuth2AuthorizationParametersMapper
implements Function<OAuth2Authorization, List<SqlParameterValue>> {
private AbstractOAuth2AuthorizationParametersMapper() {
protected AbstractOAuth2AuthorizationParametersMapper() {
}
@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 final LobCreator lobCreator;

View File

@ -33,7 +33,6 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.jackson.CoreJacksonModule;
import org.springframework.security.jackson2.CoreJackson2Module;
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
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.OAuth2TokenExchangeCompositeAuthenticationToken;
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.settings.OAuth2TokenFormat;
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.savedrequest.DefaultSavedRequest;
import org.springframework.util.ClassUtils;
@ -70,18 +67,7 @@ import org.springframework.util.ClassUtils;
*/
class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
private static final boolean jackson2Present;
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;
private boolean jackson2Contributed;
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
@ -93,17 +79,17 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
// @formatter:off
if ((isJdbcBasedOAuth2AuthorizationService || isJdbcBasedRegisteredClientRepository)
&& !this.jacksonContributed) {
JacksonConfigurationBeanRegistrationAotContribution jacksonContribution =
new JacksonConfigurationBeanRegistrationAotContribution();
this.jacksonContributed = true;
return jacksonContribution;
&& !this.jackson2Contributed) {
Jackson2ConfigurationBeanRegistrationAotContribution jackson2Contribution =
new Jackson2ConfigurationBeanRegistrationAotContribution();
this.jackson2Contributed = true;
return jackson2Contribution;
}
// @formatter:on
return null;
}
private static class JacksonConfigurationBeanRegistrationAotContribution
private static class Jackson2ConfigurationBeanRegistrationAotContribution
implements BeanRegistrationAotContribution {
private final BindingReflectionHintsRegistrar reflectionHintsRegistrar = new BindingReflectionHintsRegistrar();
@ -123,6 +109,7 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
.registerType(HashSet.class, MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
// Spring Security and Spring Authorization Server
hints.reflection()
.registerTypes(Arrays.asList(TypeReference.of(AbstractAuthenticationToken.class),
TypeReference.of(DefaultSavedRequest.Builder.class),
@ -141,143 +128,75 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
// Jackson Modules
if (jackson2Present) {
hints.reflection()
.registerTypes(
Arrays.asList(TypeReference.of(CoreJackson2Module.class),
TypeReference.of(WebServletJackson2Module.class),
TypeReference.of(OAuth2AuthorizationServerJackson2Module.class)),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
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 Modules - Spring Security and Spring Authorization Server
hints.reflection()
.registerTypes(
Arrays.asList(TypeReference.of(CoreJackson2Module.class),
TypeReference.of(WebServletJackson2Module.class),
TypeReference.of(OAuth2AuthorizationServerJackson2Module.class)),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
// Jackson Mixins
if (jackson2Present) {
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableSetMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableListMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableMapMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.UnmodifiableMapMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.server.authorization.jackson2.HashSetMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.web.jackson2.DefaultSavedRequestMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.web.jackson2.WebAuthenticationDetailsMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.SimpleGrantedAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeActorMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationRequestMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeCompositeAuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"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"));
}
// Jackson Mixins - Spring Security and Spring Authorization Server
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableSetMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableListMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UnmodifiableMapMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.UnmodifiableMapMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.oauth2.server.authorization.jackson2.HashSetMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.web.jackson2.DefaultSavedRequestMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.web.jackson2.WebAuthenticationDetailsMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.UserMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
loadClass("org.springframework.security.jackson2.SimpleGrantedAuthorityMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeActorMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationRequestMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeCompositeAuthenticationTokenMixin"));
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
"org.springframework.security.oauth2.server.authorization.jackson2.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",
ClassUtils.getDefaultClassLoader())) {
// Jackson Module (and required types) - Spring Security OAuth2 Client
hints.reflection()
.registerType(TypeReference
.of("org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken"),
.registerTypes(Arrays.asList(
TypeReference
.of("org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module"),
TypeReference
.of("org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken")),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS));
// Jackson Module
if (jackson2Present) {
hints.reflection()
.registerType(TypeReference
.of("org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module"),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS));
}
if (jackson3Present) {
hints.reflection()
.registerType(
TypeReference
.of("org.springframework.security.oauth2.client.jackson.OAuth2ClientJacksonModule"),
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
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"));
}
// Jackson Mixins - Spring Security OAuth2 Client
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"));
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.aot.hint;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
/**
* {@link RuntimeHintsRegistrar} that contributes the required {@link RuntimeHints} for
* OAuth 2.1 Authorization Server. Statically registered via
* META-INF/spring/aot.factories.
*
* @author Joe Grandja
* @since 7.0
*/
class OAuth2AuthorizationServerRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection()
.registerType(OAuth2AuthorizationCodeRequestAuthenticationProvider.class,
MemberCategory.INVOKE_DECLARED_METHODS);
hints.reflection()
.registerType(OAuth2AuthorizationEndpointFilter.class, MemberCategory.INVOKE_DECLARED_METHODS);
hints.reflection()
.registerType(TypeReference
.of("org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter$OAuth2AuthorizationCodeRequestValidatingFilter"),
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
hints.reflection()
.registerType(OAuth2AuthorizationCodeRequestAuthenticationToken.class, MemberCategory.DECLARED_FIELDS);
}
}

View File

@ -190,55 +190,51 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
OAuth2AuthorizationCodeRequestAuthenticationContext.Builder authenticationContextBuilder = OAuth2AuthorizationCodeRequestAuthenticationContext
.with(authorizationCodeRequestAuthentication)
.registeredClient(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder
.build();
if (!authorizationCodeRequestAuthentication.isValidated()) {
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder
.build();
// grant_type
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
.accept(authenticationContext);
// grant_type
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
.accept(authenticationContext);
// redirect_uri and scope
this.authenticationValidator.accept(authenticationContext);
// redirect_uri and scope
this.authenticationValidator.accept(authenticationContext);
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR
.accept(authenticationContext);
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR
.accept(authenticationContext);
// prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR
.accept(authenticationContext);
authorizationCodeRequestAuthentication.setValidated(true);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated authorization code request parameters");
// prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
Set<String> promptValues = Collections.emptySet();
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) {
String prompt = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get("prompt");
if (StringUtils.hasText(prompt)) {
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR
.accept(authenticationContext);
promptValues = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(prompt, " ")));
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated authorization code request parameters");
}
// ---------------
// The request is valid - ensure the resource owner is authenticated
// ---------------
Authentication principal = (Authentication) authorizationCodeRequestAuthentication.getPrincipal();
Set<String> promptValues = Collections.emptySet();
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) {
String prompt = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get("prompt");
if (StringUtils.hasText(prompt)) {
promptValues = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(prompt, " ")));
}
}
if (!isPrincipalAuthenticated(principal)) {
if (promptValues.contains(OidcPrompt.NONE)) {
// Return an error instead of displaying the login page (via the
// configured AuthenticationEntryPoint)
throwError("login_required", "prompt", authorizationCodeRequestAuthentication, registeredClient);
}
else {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, "principal", authorizationCodeRequestAuthentication,
registeredClient);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate authorization code request since principal not authenticated");
}
// Return the authorization request as-is where isAuthenticated() is false
return authorizationCodeRequestAuthentication;
}
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
@ -404,13 +400,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
this.authorizationConsentRequired = authorizationConsentRequired;
}
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> getAuthenticationValidatorComposite() {
return OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
.andThen(this.authenticationValidator)
.andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR)
.andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR);
}
private static boolean isAuthorizationConsentRequired(
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
if (!authenticationContext.getRegisteredClient().getClientSettings().isRequireAuthorizationConsent()) {

View File

@ -42,8 +42,6 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken
private final OAuth2AuthorizationCode authorizationCode;
private boolean validated;
/**
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the
* provided parameters.
@ -91,12 +89,4 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken
return this.authorizationCode;
}
final boolean isValidated() {
return this.validated;
}
final void setValidated(boolean validated) {
this.validated = validated;
}
}

View File

@ -28,23 +28,19 @@ import java.util.Map;
import java.util.Set;
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.ObjectMapper;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameterValue;
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.ClientAuthenticationMethod;
@ -138,8 +134,8 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
public JdbcRegisteredClientRepository(JdbcOperations jdbcOperations) {
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
this.jdbcOperations = jdbcOperations;
this.registeredClientRowMapper = new JsonMapperRegisteredClientRowMapper();
this.registeredClientParametersMapper = new JsonMapperRegisteredClientParametersMapper();
this.registeredClientRowMapper = new RegisteredClientRowMapper();
this.registeredClientParametersMapper = new RegisteredClientParametersMapper();
}
@Override
@ -210,7 +206,7 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
/**
* Sets the {@link RowMapper} used for mapping the current row in
* {@code java.sql.ResultSet} to {@link RegisteredClient}. The default is
* {@link JsonMapperRegisteredClientRowMapper}.
* {@link RegisteredClientRowMapper}.
* @param registeredClientRowMapper the {@link RowMapper} used for mapping the current
* 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
* {@code List} of {@link SqlParameterValue}. The default is
* {@link JsonMapperRegisteredClientParametersMapper}.
* {@link RegisteredClientParametersMapper}.
* @param registeredClientParametersMapper the {@code Function} used for mapping
* {@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
* {@code java.sql.ResultSet} to {@link RegisteredClient} using Jackson 3's
* {@link JsonMapper}.
*
* @author Joe Grandja
* @since 7.0
* {@code java.sql.ResultSet} to {@link RegisteredClient}.
*/
public static class JsonMapperRegisteredClientRowMapper extends AbstractRegisteredClientRowMapper {
public static class RegisteredClientRowMapper implements RowMapper<RegisteredClient> {
private final JsonMapper jsonMapper;
private ObjectMapper objectMapper = new ObjectMapper();
public JsonMapperRegisteredClientRowMapper() {
this(Jackson3.createJsonMapper());
}
public JsonMapperRegisteredClientRowMapper(JsonMapper jsonMapper) {
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() {
public RegisteredClientRowMapper() {
ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
@ -362,17 +299,25 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
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) {
try {
return readValue(data);
return this.objectMapper.readValue(data, new TypeReference<>() {
});
}
catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
abstract Map<String, Object> readValue(String data) throws Exception;
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
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}
* of {@link SqlParameterValue} using an instance of Jackson 3's {@link JsonMapper}.
* of {@link SqlParameterValue}.
*/
public static class JsonMapperRegisteredClientParametersMapper extends AbstractRegisteredClientParametersMapper {
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
public static class RegisteredClientParametersMapper
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
@ -504,52 +403,24 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
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) {
try {
return writeValueAsString(data);
return this.objectMapper.writeValueAsString(data);
}
catch (Exception 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 {

View File

@ -17,14 +17,11 @@
package org.springframework.security.oauth2.server.authorization.web;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.function.Consumer;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@ -41,18 +38,14 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
import org.springframework.security.web.DefaultRedirectStrategy;
@ -71,7 +64,6 @@ import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponentsBuilder;
@ -188,18 +180,21 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
}
try {
// Get the pre-validated authorization code request (if available),
// which was set by OAuth2AuthorizationCodeRequestValidatingFilter
Authentication authentication = (Authentication) request
.getAttribute(OAuth2AuthorizationCodeRequestAuthenticationToken.class.getName());
if (authentication == null) {
authentication = this.authenticationConverter.convert(request);
if (authentication instanceof AbstractAuthenticationToken authenticationToken) {
authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
Authentication authentication = this.authenticationConverter.convert(request);
if (authentication instanceof AbstractAuthenticationToken authenticationToken) {
authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
Authentication authenticationResult = this.authenticationManager.authenticate(authentication);
if (!authenticationResult.isAuthenticated()) {
// If the Principal (Resource Owner) is not authenticated then pass
// through the chain
// with the expectation that the authentication process will commence via
// AuthenticationEntryPoint
filterChain.doFilter(request, response);
return;
}
if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationToken) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authorization consent is required");
@ -406,109 +401,4 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
this.redirectStrategy.sendRedirect(request, response, redirectUri);
}
Filter createAuthorizationCodeRequestValidatingFilter(RegisteredClientRepository registeredClientRepository,
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
return new OAuth2AuthorizationCodeRequestValidatingFilter(registeredClientRepository, authenticationValidator);
}
/**
* A {@code Filter} that is applied before {@code OAuth2AuthorizationEndpointFilter}
* and handles the pre-validation of an OAuth 2.0 Authorization Code Request.
*/
private final class OAuth2AuthorizationCodeRequestValidatingFilter extends OncePerRequestFilter {
private final RegisteredClientRepository registeredClientRepository;
private final Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator;
private final Field setValidatedField;
private OAuth2AuthorizationCodeRequestValidatingFilter(RegisteredClientRepository registeredClientRepository,
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
this.registeredClientRepository = registeredClientRepository;
this.authenticationValidator = authenticationValidator;
this.setValidatedField = ReflectionUtils.findField(OAuth2AuthorizationCodeRequestAuthenticationToken.class,
"validated");
ReflectionUtils.makeAccessible(this.setValidatedField);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (!OAuth2AuthorizationEndpointFilter.this.authorizationEndpointMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
try {
Authentication authentication = OAuth2AuthorizationEndpointFilter.this.authenticationConverter
.convert(request);
if (!(authentication instanceof OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication)) {
filterChain.doFilter(request, response);
return;
}
String requestUri = (String) authorizationCodeRequestAuthentication.getAdditionalParameters()
.get(OAuth2ParameterNames.REQUEST_URI);
if (StringUtils.hasText(requestUri)) {
filterChain.doFilter(request, response);
return;
}
authorizationCodeRequestAuthentication.setDetails(
OAuth2AuthorizationEndpointFilter.this.authenticationDetailsSource.buildDetails(request));
RegisteredClient registeredClient = this.registeredClientRepository
.findByClientId(authorizationCodeRequestAuthentication.getClientId());
if (registeredClient == null) {
String redirectUri = null; // Prevent redirect
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
authorizationCodeRequestAuthentication.getAuthorizationUri(),
authorizationCodeRequestAuthentication.getClientId(),
(Authentication) authorizationCodeRequestAuthentication.getPrincipal(), redirectUri,
authorizationCodeRequestAuthentication.getState(),
authorizationCodeRequestAuthentication.getScopes(),
authorizationCodeRequestAuthentication.getAdditionalParameters());
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST,
"OAuth 2.0 Parameter: " + OAuth2ParameterNames.CLIENT_ID,
"https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1");
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error,
authorizationCodeRequestAuthenticationResult);
}
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = OAuth2AuthorizationCodeRequestAuthenticationContext
.with(authorizationCodeRequestAuthentication)
.registeredClient(registeredClient)
.build();
this.authenticationValidator.accept(authenticationContext);
ReflectionUtils.setField(this.setValidatedField, authorizationCodeRequestAuthentication, true);
// Set the validated authorization code request as a request
// attribute
// to be used upstream by OAuth2AuthorizationEndpointFilter
request.setAttribute(OAuth2AuthorizationCodeRequestAuthenticationToken.class.getName(),
authorizationCodeRequestAuthentication);
filterChain.doFilter(request, response);
}
catch (OAuth2AuthenticationException ex) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorization request failed: %s", ex.getError()), ex);
}
OAuth2AuthorizationEndpointFilter.this.authenticationFailureHandler.onAuthenticationFailure(request,
response, ex);
}
finally {
request.removeAttribute(OAuth2AuthorizationCodeRequestAuthenticationToken.class.getName());
}
}
}
}

View File

@ -1,5 +1,2 @@
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.security.oauth2.server.authorization.aot.hint.OAuth2AuthorizationServerBeanRegistrationAotProcessor
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.security.oauth2.server.authorization.aot.hint.OAuth2AuthorizationServerRuntimeHints

View File

@ -428,7 +428,7 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
}
@Test
public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
public void authenticateWhenPrincipalNotAuthenticatedThenReturnAuthorizationCodeRequest() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.willReturn(registeredClient);
@ -438,10 +438,12 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE,
registeredClient.getScopes(), createPkceParameters());
assertThatExceptionOfType(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.satisfies((ex) -> assertAuthenticationException(ex, OAuth2ErrorCodes.INVALID_REQUEST, "principal",
authentication.getRedirectUri()));
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider
.authenticate(authentication);
assertThat(authenticationResult).isSameAs(authentication);
assertThat(authenticationResult.isAuthenticated()).isFalse();
}
@Test

View File

@ -25,13 +25,13 @@ import java.util.Map;
import java.util.Set;
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.BeforeEach;
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.JdbcOperations;
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.EmbeddedDatabaseBuilder;
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.ClientAuthenticationMethod;
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.JsonMapperRegisteredClientRowMapper;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
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.TokenSettings;
import org.springframework.util.StringUtils;
@ -221,9 +222,9 @@ public class JdbcRegisteredClientRepositoryTests {
@Test
public void saveLoadRegisteredClientWhenCustomStrategiesSetThenCalled() throws Exception {
RowMapper<RegisteredClient> registeredClientRowMapper = spy(new JsonMapperRegisteredClientRowMapper());
RowMapper<RegisteredClient> registeredClientRowMapper = spy(new RegisteredClientRowMapper());
this.registeredClientRepository.setRegisteredClientRowMapper(registeredClientRowMapper);
JsonMapperRegisteredClientParametersMapper clientParametersMapper = new JsonMapperRegisteredClientParametersMapper();
RegisteredClientParametersMapper clientParametersMapper = new RegisteredClientParametersMapper();
Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper = spy(
clientParametersMapper);
this.registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper);
@ -364,14 +365,16 @@ public class JdbcRegisteredClientRepositoryTests {
return !result.isEmpty() ? result.get(0) : null;
}
@SuppressWarnings("removal")
private static final class CustomRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
private final JsonMapper jsonMapper;
private final ObjectMapper objectMapper = new ObjectMapper();
private CustomRegisteredClientRowMapper() {
List<JacksonModule> modules = SecurityJacksonModules
.getModules(CustomRegisteredClientRowMapper.class.getClassLoader());
this.jsonMapper = JsonMapper.builder().addModules(modules).build();
ClassLoader classLoader = CustomJdbcRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
@ -415,12 +418,9 @@ public class JdbcRegisteredClientRepositoryTests {
}
private Map<String, Object> parseMap(String data) {
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
};
try {
tools.jackson.databind.JavaType javaType = this.jsonMapper.getTypeFactory()
.constructType(typeReference.getType());
return this.jsonMapper.readValue(data, javaType);
return this.objectMapper.readValue(data, new TypeReference<>() {
});
}
catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);

View File

@ -372,11 +372,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
given(authenticationConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication);
this.filter.setAuthenticationConverter(authenticationConverter);
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes());
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthentication);
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
MockHttpServletResponse response = new MockHttpServletResponse();
@ -386,7 +382,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
verify(authenticationConverter).convert(any());
verify(this.authenticationManager).authenticate(any());
verifyNoInteractions(filterChain);
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
@ -465,6 +461,9 @@ public class OAuth2AuthorizationEndpointFilterTests {
@Test
public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource = mock(
@ -473,11 +472,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
given(authenticationDetailsSource.buildDetails(request)).willReturn(webAuthenticationDetails);
this.filter.setAuthenticationDetailsSource(authenticationDetailsSource);
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes());
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthentication);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
@ -486,7 +481,27 @@ public class OAuth2AuthorizationEndpointFilterTests {
verify(authenticationDetailsSource).buildDetails(any());
verify(this.authenticationManager).authenticate(any());
verifyNoInteractions(filterChain);
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void doFilterWhenAuthorizationRequestPrincipalNotAuthenticatedThenCommenceAuthentication() throws Exception {
this.principal.setAuthenticated(false);
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthenticationResult.setAuthenticated(false);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult);
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verify(this.authenticationManager).authenticate(any());
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test

View File

@ -705,6 +705,9 @@ public final class ClientRegistration implements Serializable {
if (!AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)
&& this.clientSettings.isRequireProofKey()) {
this.clientSettings = ClientSettings.builder().requireProofKey(false).build();
logger.warn(LogMessage.format(
"clientSettings.isRequireProofKey=true is only valid with authorizationGrantType=%s. Got authorizationGrantType=%s. Resetting to clientSettings.isRequireProofKey=false",
AuthorizationGrantType.AUTHORIZATION_CODE, this.authorizationGrantType));
}
}

View File

@ -319,17 +319,21 @@ public final class NimbusJwtDecoder implements JwtDecoder {
}
/**
* Whether to use Nimbus's {@code typ} header verification. This is {@code false}
* by default.
* Whether to use Nimbus's typ header verification. This is {@code true} by
* default, however it may change to {@code false} in a future major release.
*
* <p>
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the
* {@code typ} header to Nimbus by using Nimbus's default
* {@link JOSEObjectTypeVerifier}.
* By turning off this feature, {@link NimbusJwtDecoder} expects applications to
* check the {@code typ} header themselves in order to determine what kind of
* validation is needed
* </p>
*
* <p>
* When this is set to {@code false}, this: <code>
* This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code>
@ -596,17 +600,21 @@ public final class NimbusJwtDecoder implements JwtDecoder {
}
/**
* Whether to use Nimbus's {@code typ} header verification. This is {@code false}
* by default.
* Whether to use Nimbus's typ header verification. This is {@code true} by
* default, however it may change to {@code false} in a future major release.
*
* <p>
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the
* {@code typ} header to Nimbus by using Nimbus's default
* {@link JOSEObjectTypeVerifier}.
* By turning off this feature, {@link NimbusJwtDecoder} expects applications to
* check the {@code typ} header themselves in order to determine what kind of
* validation is needed
* </p>
*
* <p>
* When this is set to {@code false}, this: <code>
* This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code>
@ -721,17 +729,21 @@ public final class NimbusJwtDecoder implements JwtDecoder {
}
/**
* Whether to use Nimbus's {@code typ} header verification. This is {@code false}
* by default.
* Whether to use Nimbus's typ header verification. This is {@code true} by
* default, however it may change to {@code false} in a future major release.
*
* <p>
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the
* {@code typ} header to Nimbus by using Nimbus's default
* {@link JOSEObjectTypeVerifier}.
* By turning off this feature, {@link NimbusJwtDecoder} expects applications to
* check the {@code typ} header themselves in order to determine what kind of
* validation is needed
* </p>
*
* <p>
* When this is set to {@code false}, this: <code>
* This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code>

View File

@ -136,7 +136,7 @@ public final class NimbusJwtEncoder implements JwtEncoder {
algorithm = MacAlgorithm.from(jwk.getAlgorithm().getName());
}
Assert.notNull(algorithm, "Failed to derive supported algorithm from " + jwk.getAlgorithm());
JwsHeader.Builder builder = JwsHeader.with(algorithm).type("JWT").keyId(jwk.getKeyID());
JwsHeader.Builder builder = JwsHeader.with(algorithm).type(jwk.getKeyType().getValue()).keyId(jwk.getKeyID());
URI x509Url = jwk.getX509CertURL();
if (x509Url != null) {
builder.x509Url(jwk.getX509CertURL().toASCIIString());

View File

@ -1,114 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.jwt;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.util.Objects;
import java.util.Set;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.crypto.impl.ECDSA;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Use {@link NimbusJwtDecoder} to decode JWT's encoded with {@link NimbusJwtEncoder}
*
* @author Ziqin Wang
*/
class NimbusJwtEncoderDecoderTests {
@Test
void encodeAndDecodeHS256() throws GeneralSecurityException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA256");
SecretKey secretKey = keyGenerator.generateKey();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withSecretKey(secretKey).build();
JwtClaimsSet claims = TestJwtClaimsSets.jwtClaimsSet().build();
String jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(secretKey).build();
Jwt decodedJwt = jwtDecoder.decode(jwt);
assertThat(decodedJwt.getSubject()).isEqualTo("subject");
}
@Test
void encodeAndDecodeRS256() throws GeneralSecurityException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(publicKey, privateKey).build();
JwtClaimsSet claims = TestJwtClaimsSets.jwtClaimsSet().build();
String jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(publicKey).build();
Jwt decodedJwt = jwtDecoder.decode(jwt);
assertThat(decodedJwt.getSubject()).isEqualTo("subject");
}
@Test
void encodeAndDecodeES256() throws GeneralSecurityException, JOSEException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"));
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(publicKey, privateKey).build();
JwtClaimsSet claims = TestJwtClaimsSets.jwtClaimsSet().build();
String jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
JWK jwk = new ECKey.Builder(curve, publicKey).keyOperations(Set.of(KeyOperation.VERIFY))
.keyUse(KeyUse.SIGNATURE)
.algorithm(ECDSA.resolveAlgorithm(curve))
.keyIDFromThumbprint()
.build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSource(new ImmutableJWKSet<>(new JWKSet(jwk)))
.jwsAlgorithm(Objects.requireNonNull(SignatureAlgorithm.from(jwk.getAlgorithm().getName())))
.build();
Jwt decodedJwt = jwtDecoder.decode(jwt);
assertThat(decodedJwt.getSubject()).isEqualTo("subject");
}
}

View File

@ -58,7 +58,7 @@ public class RequestAttributeAuthenticationFilter extends AbstractPreAuthenticat
* missing and {@code exceptionIfVariableMissing} is set to {@code true}.
*/
@Override
protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String principal = (String) request.getAttribute(this.principalEnvironmentVariable);
if (principal == null && this.exceptionIfVariableMissing) {
throw new PreAuthenticatedCredentialsNotFoundException(
@ -73,7 +73,7 @@ public class RequestAttributeAuthenticationFilter extends AbstractPreAuthenticat
* credentials value. Otherwise a dummy value will be used.
*/
@Override
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
if (this.credentialsEnvironmentVariable != null) {
return request.getAttribute(this.credentialsEnvironmentVariable);
}

View File

@ -59,7 +59,7 @@ public class RequestHeaderAuthenticationFilter extends AbstractPreAuthenticatedP
* {@code exceptionIfHeaderMissing} is set to {@code true}.
*/
@Override
protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String principal = request.getHeader(this.principalRequestHeader);
if (principal == null && this.exceptionIfHeaderMissing) {
throw new PreAuthenticatedCredentialsNotFoundException(
@ -74,7 +74,7 @@ public class RequestHeaderAuthenticationFilter extends AbstractPreAuthenticatedP
* will be used.
*/
@Override
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
if (this.credentialsRequestHeader != null) {
return request.getHeader(this.credentialsRequestHeader);
}

View File

@ -146,14 +146,6 @@ public class PathPatternRequestMatcherTests {
assertThat(matcher.matches(mock)).isTrue();
}
@Test
void matcherWhenRequestMethodIsNullThenNoNullPointerException() {
RequestMatcher matcher = pathPattern(HttpMethod.GET, "/");
MockHttpServletRequest mock = new MockHttpServletRequest(null, "/");
ServletRequestPathUtils.parseAndCache(mock);
assertThat(matcher.matches(mock)).isFalse();
}
MockHttpServletRequest request(String uri) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
ServletRequestPathUtils.parseAndCache(request);

View File

@ -174,12 +174,6 @@ public class WebAuthnAuthenticationFilter extends AbstractAuthenticationProcessi
this.delegate = delegate;
}
@Override
public boolean canRead(ResolvableType type, @Nullable MediaType mediaType) {
Class<?> clazz = type.resolve();
return (clazz != null) ? canRead(clazz, mediaType) : canRead(mediaType);
}
@Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return this.delegate.canRead(clazz, mediaType);
@ -212,11 +206,6 @@ public class WebAuthnAuthenticationFilter extends AbstractAuthenticationProcessi
return this.delegate.read(type.getType(), null, inputMessage);
}
@Override
public boolean canWrite(ResolvableType targetType, Class<?> valueClass, @Nullable MediaType mediaType) {
return this.delegate.canWrite(targetType.getType(), valueClass, mediaType);
}
}
}

View File

@ -22,15 +22,15 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
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.ObjectConverter;
import com.webauthn4j.credential.CredentialRecordImpl;
import com.webauthn4j.data.AuthenticationData;
import com.webauthn4j.data.AuthenticationParameters;
import com.webauthn4j.data.RegistrationData;
@ -95,7 +95,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
private final PublicKeyCredentialRpEntity rp;
private ObjectConverter objectConverter = new ObjectConverter();
private final ObjectConverter objectConverter = new ObjectConverter();
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
@ -137,15 +137,6 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
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
* {@link PublicKeyCredentialCreationOptionsBuilder} for
@ -257,7 +248,9 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
byte[] attestationObject = response.getAttestationObject().getBytes();
byte[] clientDataJSON = response.getClientDataJSON().getBytes();
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()
.getUserVerification() == UserVerificationRequirement.REQUIRED;
// requireUserPresence The constant Boolean value true
@ -270,7 +263,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
transports);
RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, pubKeyCredParams,
userVerificationRequired, userPresenceRequired);
RegistrationData wa4jRegistrationData = this.webAuthnManager.verify(webauthn4jRegistrationRequest,
RegistrationData wa4jRegistrationData = this.webAuthnManager.validate(webauthn4jRegistrationRequest,
registrationParameters);
AttestationObject wa4jAttestationObject = wa4jRegistrationData.getAttestationObject();
Assert.notNull(wa4jAttestationObject, "attestationObject cannot be null");
@ -313,7 +306,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
private List<com.webauthn4j.data.PublicKeyCredentialParameters> convertCredentialParamsToWebauthn4j(
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(
@ -389,29 +382,28 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
.getAuthenticatorData();
AttestedCredentialData wa4jCredData = wa4jAuthData.getAttestedCredentialData();
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();
Challenge challenge = new DefaultChallenge(requestOptions.getChallenge().getBytes());
// FIXME: should populate this
byte[] tokenBindingId = null /* set tokenBindingId */;
String rpId = requestOptions.getRpId();
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()
.getUserVerification() == UserVerificationRequirement.REQUIRED;
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());
AuthenticationParameters authenticationParameters = new AuthenticationParameters(serverProperty, authenticator,
userVerificationRequired);
// CollectedClientData and ExtensionsClientOutputs is registration data, and can
// 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,
AuthenticationData wa4jAuthenticationData = this.webAuthnManager.validate(authenticationRequest,
authenticationParameters);
AuthenticatorData<AuthenticationExtensionAuthenticatorOutput> wa4jValidatedAuthData = wa4jAuthenticationData
@ -432,21 +424,4 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
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());
}
}

View File

@ -27,20 +27,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.converter.AttestationObjectConverter;
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.authenticator.AttestedCredentialData;
import com.webauthn4j.data.attestation.authenticator.AuthenticatorData;
import com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput;
import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
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.userdetails.PasswordEncodedUser;
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.AuthenticatorAttestationResponseBuilder;
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
import org.springframework.security.web.webauthn.api.Bytes;
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.PublicKeyCredentialCreationOptions;
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.PublicKeyCredentialRpEntity;
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.TestCredentialRecords;
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.TestPublicKeyCredentials;
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.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
@ExtendWith(MockitoExtension.class)
@ -598,50 +587,6 @@ class Webauthn4jRelyingPartyOperationsTests {
.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 {
AuthenticatorAttestationResponseBuilder authAttResponseBldr = TestAuthenticatorAttestationResponses
.createAuthenticatorAttestationResponse();