mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-12-26 18:13:37 +00:00
Compare commits
186 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d6552a602 | ||
|
|
a259e49380 | ||
|
|
d5b135ad0f | ||
|
|
5ca0d8027d | ||
|
|
ac9c0a4313 | ||
|
|
8a3e6a8fda | ||
|
|
811be0a927 | ||
|
|
40e11752e0 | ||
|
|
bc8d630bbc | ||
|
|
861c60a28f | ||
|
|
0514ee4cc5 | ||
|
|
9fd6d54268 | ||
|
|
3f04f42abb | ||
|
|
f585461427 | ||
|
|
a155c035e1 | ||
|
|
9095a1bffd | ||
|
|
9d08114c58 | ||
|
|
0155d4a345 | ||
|
|
29ad1e6b07 | ||
|
|
8651868708 | ||
|
|
5732f39da7 | ||
|
|
8bfa849a9d | ||
|
|
e033086ab0 | ||
|
|
964fcac086 | ||
|
|
1d1b3ff797 | ||
|
|
c8898f91fc | ||
|
|
dbf93acb05 | ||
|
|
ae5673b7a8 | ||
|
|
765abe534e | ||
|
|
afb0c59875 | ||
|
|
d5beb513cd | ||
|
|
d6a2603e85 | ||
|
|
a4810b7e15 | ||
|
|
054f2e9a87 | ||
|
|
00c7a5b201 | ||
|
|
310f82170f | ||
|
|
be2f2ec600 | ||
|
|
1bc90b5fd0 | ||
|
|
d2dd0fe5f6 | ||
|
|
7a85bf481a | ||
|
|
af960abe2d | ||
|
|
b7b859cd9a | ||
|
|
b83f682154 | ||
|
|
aca1643284 | ||
|
|
0c9c152a31 | ||
|
|
cf2114e36e | ||
|
|
ecd17a9ee0 | ||
|
|
2a763578f5 | ||
|
|
e978d4bf3d | ||
|
|
ef5cdb50cc | ||
|
|
b2e2d74cab | ||
|
|
c3a03a4834 | ||
|
|
0d5f42f852 | ||
|
|
4d9d40ead8 | ||
|
|
568378268e | ||
|
|
59ffb6f6d1 | ||
|
|
cf8d6a2ee7 | ||
|
|
fd0de94c1b | ||
|
|
29b9dc6f04 | ||
|
|
10edc14d7e | ||
|
|
7503d8018d | ||
|
|
c53e66a217 | ||
|
|
244b5a16be | ||
|
|
1ce73dd45a | ||
|
|
bb7fcb27ef | ||
|
|
19cbd9c570 | ||
|
|
a20724d30b | ||
|
|
3ca59af04f | ||
|
|
b37c5584f9 | ||
|
|
09e80aafe8 | ||
|
|
43ca71d7b8 | ||
|
|
3ecd4f3fde | ||
|
|
6cd43d38d5 | ||
|
|
3fbe972323 | ||
|
|
e582691996 | ||
|
|
0288b5e345 | ||
|
|
1cf75e710e | ||
|
|
2e55e0cdb3 | ||
|
|
e010d5e689 | ||
|
|
d3a55291bc | ||
|
|
c241ec5f03 | ||
|
|
8d799c3c6e | ||
|
|
9aa729f89a | ||
|
|
9126aaf19b | ||
|
|
eb5f9e0305 | ||
|
|
d84d0ca22e | ||
|
|
f1793f5047 | ||
|
|
4b227649f0 | ||
|
|
cfc27f8cc3 | ||
|
|
2f583fc15f | ||
|
|
e584196c1d | ||
|
|
5baff27ffb | ||
|
|
39aaf25b60 | ||
|
|
4327de8667 | ||
|
|
0a7ff3a18a | ||
|
|
f77c78b04a | ||
|
|
074c1c038f | ||
|
|
7abfcd3963 | ||
|
|
86d630265c | ||
|
|
7b78b0c723 | ||
|
|
e6a4ee03ff | ||
|
|
5cd3f535cf | ||
|
|
8ecc4a9157 | ||
|
|
af33ace82f | ||
|
|
7a614a535e | ||
|
|
ddebff043d | ||
|
|
de062c6724 | ||
|
|
a79354ead9 | ||
|
|
29c63bee69 | ||
|
|
ee7eb68471 | ||
|
|
f187e9a31d | ||
|
|
944322932a | ||
|
|
affa36b9bb | ||
|
|
5cd10088be | ||
|
|
568ce80d94 | ||
|
|
8809bc3782 | ||
|
|
79fc29382f | ||
|
|
4825680eb9 | ||
|
|
9ff2d96088 | ||
|
|
ffa89b749b | ||
|
|
644901fed5 | ||
|
|
1d99a7fb14 | ||
|
|
e5a379cc91 | ||
|
|
0400e29df1 | ||
|
|
7ad28772bc | ||
|
|
2678925c21 | ||
|
|
87472c9ab4 | ||
|
|
ea036702fc | ||
|
|
09805317e6 | ||
|
|
4ab933803f | ||
|
|
d2b1cb572f | ||
|
|
3e936ebe75 | ||
|
|
ecc2775796 | ||
|
|
cf8e3ee2ab | ||
|
|
12ba56bbf2 | ||
|
|
2f8638d867 | ||
|
|
17aad2cea3 | ||
|
|
9184ad3ad5 | ||
|
|
70076188af | ||
|
|
57c9b1365c | ||
|
|
1c3d28f14d | ||
|
|
aaadb43ef8 | ||
|
|
a6c1a02afa | ||
|
|
baebd04df7 | ||
|
|
d0166004aa | ||
|
|
9f96fbcda0 | ||
|
|
ccffb48fd1 | ||
|
|
d0fcdebe88 | ||
|
|
26991bbe5f | ||
|
|
e4106ecf68 | ||
|
|
7cb57ab940 | ||
|
|
b130e728b7 | ||
|
|
e6db56ab4f | ||
|
|
af47cc2abe | ||
|
|
f997e22d9d | ||
|
|
b7fb2892ed | ||
|
|
608b1484e4 | ||
|
|
5fb2875f47 | ||
|
|
27ae318992 | ||
|
|
73840663b9 | ||
|
|
c85cb2d1ef | ||
|
|
bf26dd9b33 | ||
|
|
ff908c4d7c | ||
|
|
521f533fc4 | ||
|
|
5662e17370 | ||
|
|
63f28a7e1f | ||
|
|
f988272fff | ||
|
|
532d0bef14 | ||
|
|
571bd60d82 | ||
|
|
6471a32d66 | ||
|
|
c1e9e10bf0 | ||
|
|
fed6df5167 | ||
|
|
20ae9dc6bc | ||
|
|
03eadb846c | ||
|
|
0928a60cd2 | ||
|
|
322634ca6a | ||
|
|
5213cc44fc | ||
|
|
8fa2fc0e1e | ||
|
|
4feeb0f843 | ||
|
|
ea88671f4c | ||
|
|
ee49c18ce2 | ||
|
|
f0afca7610 | ||
|
|
8b0689cbb8 | ||
|
|
28e158d1cb | ||
|
|
36f1f2ca4f | ||
|
|
46b6744b42 |
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
@ -111,11 +111,3 @@ updates:
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
- package-ecosystem: npm
|
||||
target-branch: 6.3.x
|
||||
directory: /docs
|
||||
schedule:
|
||||
interval: weekly
|
||||
labels:
|
||||
- 'type: task'
|
||||
- 'in: build'
|
||||
|
||||
@ -35,13 +35,6 @@ 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 ]
|
||||
@ -51,7 +44,7 @@ jobs:
|
||||
secrets: inherit
|
||||
perform-release:
|
||||
name: Perform Release
|
||||
needs: [ deploy-artifacts, deploy-docs, deploy-schema ]
|
||||
needs: [ deploy-artifacts, 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 }}
|
||||
|
||||
27
.github/workflows/finalize-release.yml
vendored
Normal file
27
.github/workflows/finalize-release.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
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
|
||||
@ -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/site/docs/{version}/api/"
|
||||
apiDocUrl = "https://docs.spring.io/spring-security/reference/{version}/api/java/index.html"
|
||||
replaceSnapshotVersionInReferenceDocUrl = true
|
||||
}
|
||||
|
||||
|
||||
@ -1,82 +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 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,6 @@ 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) {
|
||||
|
||||
@ -30,16 +30,6 @@ 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 {
|
||||
@ -49,4 +39,4 @@ schema: {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@ 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;
|
||||
@ -55,6 +56,7 @@ 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;
|
||||
@ -67,7 +69,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
@org.junit.jupiter.api.Disabled
|
||||
@Disabled
|
||||
class WebAuthnWebDriverTests {
|
||||
|
||||
private String baseUrl;
|
||||
@ -82,6 +84,8 @@ class WebAuthnWebDriverTests {
|
||||
|
||||
private static final String PASSWORD = "password";
|
||||
|
||||
private String authenticatorId = null;
|
||||
|
||||
@BeforeAll
|
||||
static void startChromeDriverService() throws Exception {
|
||||
driverService = new ChromeDriverService.Builder().usingAnyFreePort().build();
|
||||
@ -144,7 +148,7 @@ class WebAuthnWebDriverTests {
|
||||
@Test
|
||||
void loginWhenNoValidAuthenticatorCredentialsThenRejects() {
|
||||
createVirtualAuthenticator(true);
|
||||
this.driver.get(this.baseUrl);
|
||||
this.getAndWait("/", "/login");
|
||||
this.driver.findElement(signinWithPasskeyButton()).click();
|
||||
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error"));
|
||||
}
|
||||
@ -153,7 +157,7 @@ class WebAuthnWebDriverTests {
|
||||
void registerWhenNoLabelThenRejects() {
|
||||
login();
|
||||
|
||||
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||
this.getAndWait("/webauthn/register");
|
||||
|
||||
this.driver.findElement(registerPasskeyButton()).click();
|
||||
assertHasAlertStartingWith("error", "Error: Passkey Label is required");
|
||||
@ -163,7 +167,7 @@ class WebAuthnWebDriverTests {
|
||||
void registerWhenAuthenticatorNoUserVerificationThenRejects() {
|
||||
createVirtualAuthenticator(false);
|
||||
login();
|
||||
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||
this.getAndWait("/webauthn/register");
|
||||
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
|
||||
this.driver.findElement(registerPasskeyButton()).click();
|
||||
|
||||
@ -178,7 +182,8 @@ 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</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>
|
||||
* </ul>
|
||||
*/
|
||||
@Test
|
||||
@ -190,7 +195,7 @@ class WebAuthnWebDriverTests {
|
||||
login();
|
||||
|
||||
// Step 2: register a credential from the virtual authenticator
|
||||
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||
this.getAndWait("/webauthn/register");
|
||||
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
|
||||
this.driver.findElement(registerPasskeyButton()).click();
|
||||
|
||||
@ -212,9 +217,58 @@ class WebAuthnWebDriverTests {
|
||||
logout();
|
||||
|
||||
// Step 4: log in with the virtual authenticator
|
||||
this.driver.get(this.baseUrl + "/webauthn/register");
|
||||
this.getAndWait("/webauthn/register", "/login");
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -231,11 +285,14 @@ 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
|
||||
cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
|
||||
Map<String, Object> cmdResponse = cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
|
||||
Map.of(
|
||||
"options",
|
||||
Map.of(
|
||||
@ -248,21 +305,38 @@ 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.driver.get(this.baseUrl);
|
||||
this.getAndWait("/", "/login");
|
||||
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.driver.get(this.baseUrl + "/logout");
|
||||
this.getAndWait("/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())
|
||||
@ -289,6 +363,15 @@ 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");
|
||||
}
|
||||
@ -325,6 +408,10 @@ 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
|
||||
|
||||
@ -42,7 +42,8 @@ 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);
|
||||
.isPresent("org.springframework.web.servlet.DispatcherServlet", null)
|
||||
&& ClassUtils.isPresent("org.springframework.security.web.util.ThrowableAnalyzer", null);
|
||||
|
||||
private static final boolean isObservabilityPresent = ClassUtils
|
||||
.isPresent("io.micrometer.observation.ObservationRegistry", null);
|
||||
|
||||
@ -2052,7 +2052,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
||||
* http
|
||||
* // ...
|
||||
* .webAuthn((webAuthn) -> webAuthn
|
||||
* .rpName("Spring Security Relying Party")
|
||||
* .rpId("example.com")
|
||||
* .allowedOrigins("https://example.com")
|
||||
* );
|
||||
|
||||
@ -177,6 +177,7 @@ 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(
|
||||
@ -256,9 +257,10 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
|
||||
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
|
||||
WebAuthnRelyingPartyOperations.class);
|
||||
return webauthnOperationsBean.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities,
|
||||
userCredentials, PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(),
|
||||
this.allowedOrigins));
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -16,10 +16,12 @@
|
||||
|
||||
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;
|
||||
@ -36,10 +38,12 @@ 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;
|
||||
@ -50,6 +54,7 @@ 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;
|
||||
|
||||
/**
|
||||
@ -83,6 +88,8 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
|
||||
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator;
|
||||
|
||||
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidatorComposite;
|
||||
|
||||
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
|
||||
|
||||
/**
|
||||
@ -248,8 +255,16 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
authenticationProviders.addAll(0, this.authenticationProviders);
|
||||
}
|
||||
this.authenticationProvidersConsumer.accept(authenticationProviders);
|
||||
authenticationProviders.forEach(
|
||||
(authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -282,7 +297,18 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
if (this.sessionAuthenticationStrategy != null) {
|
||||
authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy);
|
||||
}
|
||||
httpSecurity.addFilterBefore(postProcess(authorizationEndpointFilter),
|
||||
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),
|
||||
AbstractPreAuthenticatedProcessingFilter.class);
|
||||
}
|
||||
|
||||
|
||||
@ -19,10 +19,13 @@ 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;
|
||||
@ -42,6 +45,7 @@ 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;
|
||||
@ -52,6 +56,8 @@ 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;
|
||||
@ -88,6 +94,14 @@ 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();
|
||||
@ -127,6 +141,42 @@ 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();
|
||||
@ -289,6 +339,26 @@ 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 {
|
||||
@ -304,8 +374,7 @@ public class WebAuthnConfigurerTests {
|
||||
http
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.webAuthn((authn) -> authn
|
||||
.rpId("spring.io")
|
||||
.rpName("spring")
|
||||
.rpId("example.com")
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
@ -313,6 +382,24 @@ 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 {
|
||||
|
||||
@ -307,8 +307,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
|
||||
this.mvc
|
||||
.perform(
|
||||
get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient)))
|
||||
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||
.queryParams(getAuthorizationRequestParameters(registeredClient)))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andReturn();
|
||||
}
|
||||
@ -851,21 +851,31 @@ 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(authorizationCodeRequestAuthenticationResult);
|
||||
given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication);
|
||||
given(authorizationRequestAuthenticationProvider
|
||||
.supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true);
|
||||
given(authorizationRequestAuthenticationProvider.authenticate(any()))
|
||||
.willReturn(authorizationCodeRequestAuthenticationResult);
|
||||
|
||||
this.mvc
|
||||
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient))
|
||||
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||
.queryParams(getAuthorizationRequestParameters(registeredClient))
|
||||
.with(user("user")))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
@ -880,8 +890,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
|| converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter
|
||||
|| converter instanceof OAuth2AuthorizationConsentAuthenticationConverter);
|
||||
|
||||
verify(authorizationRequestAuthenticationProvider)
|
||||
.authenticate(eq(authorizationCodeRequestAuthenticationResult));
|
||||
verify(authorizationRequestAuthenticationProvider).authenticate(eq(authorizationCodeRequestAuthentication));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<AuthenticationProvider>> authenticationProvidersCaptor = ArgumentCaptor
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.crypto.bcrypt;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@ -25,6 +26,7 @@ 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
|
||||
@ -236,4 +238,23 @@ 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ urls:
|
||||
redirect_facility: httpd
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
|
||||
snapshot: true
|
||||
runtime:
|
||||
log:
|
||||
|
||||
@ -90,6 +90,7 @@
|
||||
**** 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]
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
[[webflux-cors]]
|
||||
= CORS
|
||||
|
||||
@ -75,3 +74,11 @@ 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.
|
||||
====
|
||||
|
||||
@ -39,7 +39,7 @@ Gradle::
|
||||
+
|
||||
[source,groovy,role="secondary",subs="verbatim,attributes"]
|
||||
----
|
||||
depenendencies {
|
||||
dependencies {
|
||||
implementation "org.springframework.security:spring-security-web"
|
||||
implementation "com.webauthn4j:webauthn4j-core:{webauthn4j-core-version}"
|
||||
}
|
||||
@ -65,7 +65,6 @@ SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
// ...
|
||||
.formLogin(withDefaults())
|
||||
.webAuthn((webAuthn) -> webAuthn
|
||||
.rpName("Spring Security Relying Party")
|
||||
.rpId("example.com")
|
||||
.allowedOrigins("https://example.com")
|
||||
// optional properties
|
||||
@ -96,7 +95,6 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
// ...
|
||||
http {
|
||||
webAuthn {
|
||||
rpName = "Spring Security Relying Party"
|
||||
rpId = "example.com"
|
||||
allowedOrigins = setOf("https://example.com")
|
||||
// optional properties
|
||||
|
||||
@ -38,7 +38,7 @@ Gradle::
|
||||
+
|
||||
[source,groovy,role="secondary"]
|
||||
----
|
||||
depenendencies {
|
||||
dependencies {
|
||||
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"]
|
||||
----
|
||||
depenendencies {
|
||||
dependencies {
|
||||
runtimeOnly "com.unboundid:unboundid-ldapsdk:{unboundid-ldapsdk-version}"
|
||||
}
|
||||
----
|
||||
|
||||
@ -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/autoconfigure/security/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/security/autoconfigure/web/servlet/PathRequest.html[`PathRequest#toStaticResources#atCommonLocations`]
|
||||
=====
|
||||
|
||||
[[match-by-custom]]
|
||||
|
||||
@ -493,14 +493,14 @@ public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurit
|
||||
private boolean flag;
|
||||
|
||||
@Override
|
||||
public void init(HttpSecurity http) throws Exception {
|
||||
public void init(HttpSecurity http) {
|
||||
// any method that adds another configurer
|
||||
// must be done in the init method
|
||||
http.csrf().disable();
|
||||
http.csrf(csrf -> csrf.disable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception {
|
||||
public void configure(HttpSecurity http) {
|
||||
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
|
||||
|
||||
// here we lookup from the ApplicationContext. You can also just create a new instance.
|
||||
|
||||
@ -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/autoconfigure/security/servlet/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/security/autoconfigure/SecurityAutoConfiguration.html[Boot's security auto configuration], it does the following (simplified for illustration):
|
||||
|
||||
.Spring Boot Security Auto Configuration
|
||||
[source,java]
|
||||
|
||||
@ -183,3 +183,11 @@ 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.
|
||||
====
|
||||
|
||||
@ -370,7 +370,7 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
public AnnotationTemplateExpressionDefaults templateDefaults() {
|
||||
return new AnnotationTemplateExpressionDeafults();
|
||||
return new AnnotationTemplateExpressionDefaults();
|
||||
}
|
||||
----
|
||||
|
||||
@ -380,7 +380,7 @@ Kotlin::
|
||||
----
|
||||
@Bean
|
||||
fun templateDefaults(): AnnotationTemplateExpressionDefaults {
|
||||
return AnnotationTemplateExpressionDeafults()
|
||||
return AnnotationTemplateExpressionDefaults()
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
@ -108,6 +108,34 @@ 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]]
|
||||
|
||||
@ -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 Client with Spring Boot
|
||||
.OAuth2 Resource Server with Spring Boot
|
||||
[tabs]
|
||||
======
|
||||
Gradle::
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
[[oauth2resourceserver-protected-resource-metadata]]
|
||||
= OAuth 2.0 Protected Resource Metadata
|
||||
|
||||
`OAuth2ResourceServerConfigurer.ProtectedResourceMetadataConfigurer` provides the ability to customize the https://www.rfc-editor.org/rfc/rfc9728.html#section-3[OAuth 2.0 Protected Resource Metadata endpoint].
|
||||
It defines an extension point that lets you customize the https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2[OAuth 2.0 Protected Resource Metadata response].
|
||||
|
||||
`OAuth2ResourceServerConfigurer.ProtectedResourceMetadataConfigurer` provides the following configuration option:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2ResourceServer((resourceServer) ->
|
||||
resourceServer
|
||||
.protectedResourceMetadata(protectedResourceMetadata ->
|
||||
protectedResourceMetadata
|
||||
.protectedResourceMetadataCustomizer(protectedResourceMetadataCustomizer) <1>
|
||||
)
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `protectedResourceMetadataCustomizer()`: The `Consumer` providing access to the `OAuth2ProtectedResourceMetadata.Builder` allowing the ability to customize the claims of the Resource Server's configuration.
|
||||
|
||||
`OAuth2ResourceServerConfigurer.ProtectedResourceMetadataConfigurer` configures the `OAuth2ProtectedResourceMetadataFilter` and registers it with the Resource Server `SecurityFilterChain` `@Bean`.
|
||||
`OAuth2ProtectedResourceMetadataFilter` is the `Filter` that returns the https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2[OAuth2ProtectedResourceMetadata response].
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"antora": "3.2.0-alpha.10",
|
||||
"antora": "3.2.0-alpha.11",
|
||||
"@antora/atlas-extension": "1.0.0-alpha.5",
|
||||
"@antora/collector-extension": "1.0.2",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
springBootVersion=4.0.0-SNAPSHOT
|
||||
version=7.0.0-RC2
|
||||
version=7.0.3-SNAPSHOT
|
||||
samplesBranch=main
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
|
||||
@ -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.24"
|
||||
org-aspectj = "1.9.24"
|
||||
org-apache-maven-resolver = "1.9.25"
|
||||
org-aspectj = "1.9.25.1"
|
||||
org-bouncycastle = "1.80"
|
||||
org-eclipse-jetty = "11.0.26"
|
||||
org-jetbrains-kotlin = "2.2.20"
|
||||
org-jetbrains-kotlin = "2.2.21"
|
||||
org-jetbrains-kotlinx = "1.10.2"
|
||||
org-mockito = "5.17.0"
|
||||
org-opensaml5 = "5.1.6"
|
||||
org-springframework = "7.0.0-RC1"
|
||||
org-springframework = "7.0.2"
|
||||
com-password4j = "1.8.4"
|
||||
|
||||
[libraries]
|
||||
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"
|
||||
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"
|
||||
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.3"
|
||||
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.4"
|
||||
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.12"
|
||||
io-mockk = "io.mockk:mockk:1.14.6"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-RC1"
|
||||
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-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.10"
|
||||
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.13"
|
||||
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.0'
|
||||
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.11"
|
||||
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-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.20"
|
||||
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21"
|
||||
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" }
|
||||
org-junit-junit-bom = "org.junit:junit-bom:6.0.0"
|
||||
org-junit-junit-bom = "org.junit:junit-bom:6.0.1"
|
||||
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.0-RC1"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.0-RC1"
|
||||
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-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.1"
|
||||
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.0.3"
|
||||
|
||||
com-google-code-gson-gson = "com.google.code.gson:gson:2.13.2"
|
||||
com-thaiopensource-trag = "com.thaiopensource:trang:20091111"
|
||||
|
||||
12
javascript/package-lock.json
generated
12
javascript/package-lock.json
generated
@ -1944,9 +1944,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@ -4524,9 +4524,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
|
||||
@ -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} to read all {@code Map<String,Object>} within the result.
|
||||
* {@link JsonMapper}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
@ -482,6 +482,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
||||
public JsonMapperOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository,
|
||||
JsonMapper jsonMapper) {
|
||||
super(registeredClientRepository);
|
||||
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
|
||||
this.jsonMapper = jsonMapper;
|
||||
}
|
||||
|
||||
@ -544,7 +545,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
||||
|
||||
private LobHandler lobHandler = new DefaultLobHandler();
|
||||
|
||||
AbstractOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
|
||||
private AbstractOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
|
||||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
||||
this.registeredClientRepository = registeredClientRepository;
|
||||
}
|
||||
@ -713,42 +714,36 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested class to protect from getting {@link NoClassDefFoundError} when Jackson 2 is
|
||||
* not on the classpath.
|
||||
* 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 class JsonMapperOAuth2AuthorizationParametersMapper
|
||||
extends AbstractOAuth2AuthorizationParametersMapper {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Function} that maps {@link OAuth2Authorization} to a {@code List} of
|
||||
* {@link SqlParameterValue} using an instance of Jackson 2's {@link ObjectMapper}.
|
||||
*
|
||||
* @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 {
|
||||
|
||||
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 {
|
||||
|
||||
static JsonMapper createJsonMapper() {
|
||||
List<JacksonModule> modules = SecurityJacksonModules.getModules(Jackson3.class.getClassLoader());
|
||||
return JsonMapper.builder().addModules(modules).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link JsonMapperOAuth2AuthorizationParametersMapper} to migrate to
|
||||
* @deprecated Use {@link JsonMapperOAuth2AuthorizationParametersMapper} to switch to
|
||||
* Jackson 3.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "7.0")
|
||||
@ -772,32 +767,6 @@ 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}.
|
||||
@ -805,7 +774,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
|
||||
private abstract static class AbstractOAuth2AuthorizationParametersMapper
|
||||
implements Function<OAuth2Authorization, List<SqlParameterValue>> {
|
||||
|
||||
protected AbstractOAuth2AuthorizationParametersMapper() {
|
||||
private AbstractOAuth2AuthorizationParametersMapper() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -916,6 +885,41 @@ 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;
|
||||
|
||||
@ -33,6 +33,7 @@ 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;
|
||||
@ -48,9 +49,11 @@ 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;
|
||||
@ -67,7 +70,18 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
||||
|
||||
private boolean jackson2Contributed;
|
||||
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;
|
||||
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
@ -79,17 +93,17 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
|
||||
|
||||
// @formatter:off
|
||||
if ((isJdbcBasedOAuth2AuthorizationService || isJdbcBasedRegisteredClientRepository)
|
||||
&& !this.jackson2Contributed) {
|
||||
Jackson2ConfigurationBeanRegistrationAotContribution jackson2Contribution =
|
||||
new Jackson2ConfigurationBeanRegistrationAotContribution();
|
||||
this.jackson2Contributed = true;
|
||||
return jackson2Contribution;
|
||||
&& !this.jacksonContributed) {
|
||||
JacksonConfigurationBeanRegistrationAotContribution jacksonContribution =
|
||||
new JacksonConfigurationBeanRegistrationAotContribution();
|
||||
this.jacksonContributed = true;
|
||||
return jacksonContribution;
|
||||
}
|
||||
// @formatter:on
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class Jackson2ConfigurationBeanRegistrationAotContribution
|
||||
private static class JacksonConfigurationBeanRegistrationAotContribution
|
||||
implements BeanRegistrationAotContribution {
|
||||
|
||||
private final BindingReflectionHintsRegistrar reflectionHintsRegistrar = new BindingReflectionHintsRegistrar();
|
||||
@ -109,7 +123,6 @@ 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),
|
||||
@ -128,75 +141,143 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
|
||||
(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 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 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"));
|
||||
// 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"));
|
||||
}
|
||||
|
||||
// Check if Spring Security OAuth2 Client is on classpath
|
||||
// Check if 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()
|
||||
.registerTypes(Arrays.asList(
|
||||
TypeReference
|
||||
.of("org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module"),
|
||||
TypeReference
|
||||
.of("org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken")),
|
||||
.registerType(TypeReference
|
||||
.of("org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken"),
|
||||
(builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS,
|
||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_DECLARED_METHODS));
|
||||
|
||||
// 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"));
|
||||
// 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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -190,51 +190,55 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
OAuth2AuthorizationCodeRequestAuthenticationContext.Builder authenticationContextBuilder = OAuth2AuthorizationCodeRequestAuthenticationContext
|
||||
.with(authorizationCodeRequestAuthentication)
|
||||
.registeredClient(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder
|
||||
.build();
|
||||
|
||||
// grant_type
|
||||
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
|
||||
.accept(authenticationContext);
|
||||
if (!authorizationCodeRequestAuthentication.isValidated()) {
|
||||
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder
|
||||
.build();
|
||||
|
||||
// redirect_uri and scope
|
||||
this.authenticationValidator.accept(authenticationContext);
|
||||
// grant_type
|
||||
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
|
||||
.accept(authenticationContext);
|
||||
|
||||
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
|
||||
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR
|
||||
.accept(authenticationContext);
|
||||
// redirect_uri and scope
|
||||
this.authenticationValidator.accept(authenticationContext);
|
||||
|
||||
// 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, " ")));
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Did not authenticate authorization code request since principal not authenticated");
|
||||
else {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, "principal", authorizationCodeRequestAuthentication,
|
||||
registeredClient);
|
||||
}
|
||||
// Return the authorization request as-is where isAuthenticated() is false
|
||||
return authorizationCodeRequestAuthentication;
|
||||
}
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
@ -400,6 +404,13 @@ 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()) {
|
||||
|
||||
@ -42,6 +42,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken
|
||||
|
||||
private final OAuth2AuthorizationCode authorizationCode;
|
||||
|
||||
private boolean validated;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the
|
||||
* provided parameters.
|
||||
@ -89,4 +91,12 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken
|
||||
return this.authorizationCode;
|
||||
}
|
||||
|
||||
final boolean isValidated() {
|
||||
return this.validated;
|
||||
}
|
||||
|
||||
final void setValidated(boolean validated) {
|
||||
this.validated = validated;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -28,19 +28,23 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
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;
|
||||
@ -134,8 +138,8 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
||||
public JdbcRegisteredClientRepository(JdbcOperations jdbcOperations) {
|
||||
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
|
||||
this.jdbcOperations = jdbcOperations;
|
||||
this.registeredClientRowMapper = new RegisteredClientRowMapper();
|
||||
this.registeredClientParametersMapper = new RegisteredClientParametersMapper();
|
||||
this.registeredClientRowMapper = new JsonMapperRegisteredClientRowMapper();
|
||||
this.registeredClientParametersMapper = new JsonMapperRegisteredClientParametersMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -206,7 +210,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 RegisteredClientRowMapper}.
|
||||
* {@link JsonMapperRegisteredClientRowMapper}.
|
||||
* @param registeredClientRowMapper the {@link RowMapper} used for mapping the current
|
||||
* row in {@code ResultSet} to {@link RegisteredClient}
|
||||
*/
|
||||
@ -218,7 +222,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 RegisteredClientParametersMapper}.
|
||||
* {@link JsonMapperRegisteredClientParametersMapper}.
|
||||
* @param registeredClientParametersMapper the {@code Function} used for mapping
|
||||
* {@link RegisteredClient} to a {@code List} of {@link SqlParameterValue}
|
||||
*/
|
||||
@ -242,17 +246,76 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
||||
|
||||
/**
|
||||
* The default {@link RowMapper} that maps the current row in
|
||||
* {@code java.sql.ResultSet} to {@link RegisteredClient}.
|
||||
* {@code java.sql.ResultSet} to {@link RegisteredClient} using Jackson 3's
|
||||
* {@link JsonMapper}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class RegisteredClientRowMapper implements RowMapper<RegisteredClient> {
|
||||
public static class JsonMapperRegisteredClientRowMapper extends AbstractRegisteredClientRowMapper {
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final JsonMapper jsonMapper;
|
||||
|
||||
public RegisteredClientRowMapper() {
|
||||
ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader();
|
||||
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
|
||||
this.objectMapper.registerModules(securityModules);
|
||||
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
||||
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() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -299,25 +362,17 @@ 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 this.objectMapper.readValue(data, new TypeReference<>() {
|
||||
});
|
||||
return readValue(data);
|
||||
}
|
||||
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;
|
||||
@ -350,18 +405,64 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
||||
|
||||
/**
|
||||
* The default {@code Function} that maps {@link RegisteredClient} to a {@code List}
|
||||
* of {@link SqlParameterValue}.
|
||||
* of {@link SqlParameterValue} using an instance of Jackson 3's {@link JsonMapper}.
|
||||
*/
|
||||
public static class RegisteredClientParametersMapper
|
||||
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
|
||||
implements Function<RegisteredClient, List<SqlParameterValue>> {
|
||||
|
||||
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());
|
||||
private AbstractRegisteredClientParametersMapper() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -403,24 +504,52 @@ 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 this.objectMapper.writeValueAsString(data);
|
||||
return 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 {
|
||||
|
||||
@ -17,11 +17,14 @@
|
||||
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;
|
||||
@ -38,14 +41,18 @@ 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;
|
||||
@ -64,6 +71,7 @@ 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;
|
||||
@ -180,21 +188,18 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
|
||||
}
|
||||
|
||||
try {
|
||||
Authentication authentication = this.authenticationConverter.convert(request);
|
||||
if (authentication instanceof AbstractAuthenticationToken authenticationToken) {
|
||||
authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
|
||||
// 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 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");
|
||||
@ -401,4 +406,109 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,2 +1,5 @@
|
||||
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
|
||||
|
||||
@ -428,7 +428,7 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenPrincipalNotAuthenticatedThenReturnAuthorizationCodeRequest() {
|
||||
public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.willReturn(registeredClient);
|
||||
@ -438,12 +438,10 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE,
|
||||
registeredClient.getScopes(), createPkceParameters());
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
|
||||
assertThat(authenticationResult).isSameAs(authentication);
|
||||
assertThat(authenticationResult.isAuthenticated()).isFalse();
|
||||
assertThatExceptionOfType(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.satisfies((ex) -> assertAuthenticationException(ex, OAuth2ErrorCodes.INVALID_REQUEST, "principal",
|
||||
authentication.getRedirectUri()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -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,13 +41,12 @@ 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.jackson2.SecurityJackson2Modules;
|
||||
import org.springframework.security.jackson.SecurityJacksonModules;
|
||||
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.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.client.JdbcRegisteredClientRepository.JsonMapperRegisteredClientParametersMapper;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.JsonMapperRegisteredClientRowMapper;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||
import org.springframework.util.StringUtils;
|
||||
@ -222,9 +221,9 @@ public class JdbcRegisteredClientRepositoryTests {
|
||||
|
||||
@Test
|
||||
public void saveLoadRegisteredClientWhenCustomStrategiesSetThenCalled() throws Exception {
|
||||
RowMapper<RegisteredClient> registeredClientRowMapper = spy(new RegisteredClientRowMapper());
|
||||
RowMapper<RegisteredClient> registeredClientRowMapper = spy(new JsonMapperRegisteredClientRowMapper());
|
||||
this.registeredClientRepository.setRegisteredClientRowMapper(registeredClientRowMapper);
|
||||
RegisteredClientParametersMapper clientParametersMapper = new RegisteredClientParametersMapper();
|
||||
JsonMapperRegisteredClientParametersMapper clientParametersMapper = new JsonMapperRegisteredClientParametersMapper();
|
||||
Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper = spy(
|
||||
clientParametersMapper);
|
||||
this.registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper);
|
||||
@ -365,16 +364,14 @@ public class JdbcRegisteredClientRepositoryTests {
|
||||
return !result.isEmpty() ? result.get(0) : null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private static final class CustomRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final JsonMapper jsonMapper;
|
||||
|
||||
private CustomRegisteredClientRowMapper() {
|
||||
ClassLoader classLoader = CustomJdbcRegisteredClientRepository.class.getClassLoader();
|
||||
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
|
||||
this.objectMapper.registerModules(securityModules);
|
||||
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
||||
List<JacksonModule> modules = SecurityJacksonModules
|
||||
.getModules(CustomRegisteredClientRowMapper.class.getClassLoader());
|
||||
this.jsonMapper = JsonMapper.builder().addModules(modules).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -418,9 +415,12 @@ public class JdbcRegisteredClientRepositoryTests {
|
||||
}
|
||||
|
||||
private Map<String, Object> parseMap(String data) {
|
||||
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
|
||||
};
|
||||
try {
|
||||
return this.objectMapper.readValue(data, new TypeReference<>() {
|
||||
});
|
||||
tools.jackson.databind.JavaType javaType = this.jsonMapper.getTypeFactory()
|
||||
.constructType(typeReference.getType());
|
||||
return this.jsonMapper.readValue(data, javaType);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||
|
||||
@ -372,7 +372,11 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
given(authenticationConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication);
|
||||
this.filter.setAuthenticationConverter(authenticationConverter);
|
||||
|
||||
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthentication);
|
||||
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);
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@ -382,7 +386,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
|
||||
verify(authenticationConverter).convert(any());
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
verifyNoInteractions(filterChain);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -461,9 +465,6 @@ 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(
|
||||
@ -472,7 +473,11 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
given(authenticationDetailsSource.buildDetails(request)).willReturn(webAuthenticationDetails);
|
||||
this.filter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
||||
|
||||
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthentication);
|
||||
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);
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
@ -481,27 +486,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
|
||||
verify(authenticationDetailsSource).buildDetails(any());
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
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));
|
||||
verifyNoInteractions(filterChain);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -705,9 +705,6 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -319,21 +319,17 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Whether to use Nimbus's {@code typ} header verification. This is {@code false}
|
||||
* by default.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the
|
||||
* {@code typ} header to Nimbus by using Nimbus's default
|
||||
* {@link JOSEObjectTypeVerifier}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This is done for you when you use {@link JwtValidators} to construct a
|
||||
* validator.
|
||||
*
|
||||
* <p>
|
||||
* That means that this: <code>
|
||||
* When this is set to {@code false}, this: <code>
|
||||
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
|
||||
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
|
||||
* </code>
|
||||
@ -600,21 +596,17 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Whether to use Nimbus's {@code typ} header verification. This is {@code false}
|
||||
* by default.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the
|
||||
* {@code typ} header to Nimbus by using Nimbus's default
|
||||
* {@link JOSEObjectTypeVerifier}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This is done for you when you use {@link JwtValidators} to construct a
|
||||
* validator.
|
||||
*
|
||||
* <p>
|
||||
* That means that this: <code>
|
||||
* When this is set to {@code false}, this: <code>
|
||||
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
|
||||
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
|
||||
* </code>
|
||||
@ -729,21 +721,17 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Whether to use Nimbus's {@code typ} header verification. This is {@code false}
|
||||
* by default.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the
|
||||
* {@code typ} header to Nimbus by using Nimbus's default
|
||||
* {@link JOSEObjectTypeVerifier}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This is done for you when you use {@link JwtValidators} to construct a
|
||||
* validator.
|
||||
*
|
||||
* <p>
|
||||
* That means that this: <code>
|
||||
* When this is set to {@code false}, this: <code>
|
||||
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
|
||||
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
|
||||
* </code>
|
||||
|
||||
@ -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(jwk.getKeyType().getValue()).keyId(jwk.getKeyID());
|
||||
JwsHeader.Builder builder = JwsHeader.with(algorithm).type("JWT").keyId(jwk.getKeyID());
|
||||
URI x509Url = jwk.getX509CertURL();
|
||||
if (x509Url != null) {
|
||||
builder.x509Url(jwk.getX509CertURL().toASCIIString());
|
||||
|
||||
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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");
|
||||
}
|
||||
|
||||
}
|
||||
@ -58,7 +58,7 @@ public class RequestAttributeAuthenticationFilter extends AbstractPreAuthenticat
|
||||
* missing and {@code exceptionIfVariableMissing} is set to {@code true}.
|
||||
*/
|
||||
@Override
|
||||
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
|
||||
protected @Nullable 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 Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
||||
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
||||
if (this.credentialsEnvironmentVariable != null) {
|
||||
return request.getAttribute(this.credentialsEnvironmentVariable);
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ public class RequestHeaderAuthenticationFilter extends AbstractPreAuthenticatedP
|
||||
* {@code exceptionIfHeaderMissing} is set to {@code true}.
|
||||
*/
|
||||
@Override
|
||||
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
|
||||
protected @Nullable 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 Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
||||
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
||||
if (this.credentialsRequestHeader != null) {
|
||||
return request.getHeader(this.credentialsRequestHeader);
|
||||
}
|
||||
|
||||
@ -146,6 +146,14 @@ 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);
|
||||
|
||||
@ -174,6 +174,12 @@ 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);
|
||||
@ -206,6 +212,11 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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 final ObjectConverter objectConverter = new ObjectConverter();
|
||||
private ObjectConverter objectConverter = new ObjectConverter();
|
||||
|
||||
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||
|
||||
@ -137,6 +137,15 @@ 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
|
||||
@ -248,9 +257,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
||||
byte[] attestationObject = response.getAttestationObject().getBytes();
|
||||
byte[] clientDataJSON = response.getClientDataJSON().getBytes();
|
||||
Challenge challenge = new DefaultChallenge(base64Challenge);
|
||||
byte[] tokenBindingId = null /* set tokenBindingId */; // FIXME:
|
||||
// https://www.w3.org/TR/webauthn-1/#dom-collectedclientdata-tokenbinding
|
||||
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge, tokenBindingId);
|
||||
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge);
|
||||
boolean userVerificationRequired = creationOptions.getAuthenticatorSelection()
|
||||
.getUserVerification() == UserVerificationRequirement.REQUIRED;
|
||||
// requireUserPresence The constant Boolean value true
|
||||
@ -263,7 +270,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
||||
transports);
|
||||
RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, pubKeyCredParams,
|
||||
userVerificationRequired, userPresenceRequired);
|
||||
RegistrationData wa4jRegistrationData = this.webAuthnManager.validate(webauthn4jRegistrationRequest,
|
||||
RegistrationData wa4jRegistrationData = this.webAuthnManager.verify(webauthn4jRegistrationRequest,
|
||||
registrationParameters);
|
||||
AttestationObject wa4jAttestationObject = wa4jRegistrationData.getAttestationObject();
|
||||
Assert.notNull(wa4jAttestationObject, "attestationObject cannot be null");
|
||||
@ -306,7 +313,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
||||
|
||||
private List<com.webauthn4j.data.PublicKeyCredentialParameters> convertCredentialParamsToWebauthn4j(
|
||||
List<PublicKeyCredentialParameters> parameters) {
|
||||
return parameters.stream().map(this::convertParamToWebauthn4j).collect(Collectors.toUnmodifiableList());
|
||||
return parameters.stream().map(this::convertParamToWebauthn4j).toList();
|
||||
}
|
||||
|
||||
private com.webauthn4j.data.PublicKeyCredentialParameters convertParamToWebauthn4j(
|
||||
@ -382,28 +389,29 @@ 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, tokenBindingId);
|
||||
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge);
|
||||
boolean userVerificationRequired = request.getRequestOptions()
|
||||
.getUserVerification() == UserVerificationRequirement.REQUIRED;
|
||||
|
||||
com.webauthn4j.data.AuthenticationRequest authenticationRequest = new com.webauthn4j.data.AuthenticationRequest(
|
||||
request.getPublicKey().getId().getBytes(), assertionResponse.getAuthenticatorData().getBytes(),
|
||||
request.getPublicKey().getRawId().getBytes(), assertionResponse.getAuthenticatorData().getBytes(),
|
||||
assertionResponse.getClientDataJSON().getBytes(), assertionResponse.getSignature().getBytes());
|
||||
AuthenticationParameters authenticationParameters = new AuthenticationParameters(serverProperty, authenticator,
|
||||
userVerificationRequired);
|
||||
|
||||
AuthenticationData wa4jAuthenticationData = this.webAuthnManager.validate(authenticationRequest,
|
||||
// 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,
|
||||
authenticationParameters);
|
||||
|
||||
AuthenticatorData<AuthenticationExtensionAuthenticatorOutput> wa4jValidatedAuthData = wa4jAuthenticationData
|
||||
@ -424,4 +432,21 @@ 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -27,15 +27,20 @@ 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;
|
||||
|
||||
@ -44,12 +49,14 @@ 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;
|
||||
@ -57,9 +64,11 @@ 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;
|
||||
@ -67,7 +76,9 @@ 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)
|
||||
@ -587,6 +598,50 @@ 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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user