Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 928ad9600c | |||
| f9b2c86e1d | |||
| d77e48f9ef | |||
| 25da472d67 | |||
| 9d3e217b79 | |||
| 7bbfc09f49 | |||
| 85e2d9298b | |||
| b804da974d | |||
| b9bb5e0b52 | |||
| 4fd8e1d596 | |||
| c59fb0cd35 | |||
| bc6ce0d346 | |||
| 50aba3aaf3 | |||
| 25aec8c5e0 | |||
| 6eb0af9912 | |||
| 6cbbf6c561 | |||
| 10cb6f7003 | |||
| 7fdff6a907 | |||
| 117af3bc2b | |||
| c7f781423f | |||
| fb2f0d5c38 | |||
| 10b9cc8c2b | |||
| 5240878272 | |||
| ba4bd61c5b | |||
| c25ec70374 | |||
| 8e1e0ca9d2 | |||
| 705fa60a01 | |||
| f2b7cb2de5 | |||
| 8652950fb2 | |||
| 07ba3e623f | |||
| 252c69460e | |||
| 3131642aae | |||
| 552d8d1d29 | |||
| f240f29433 | |||
| f91b5f33fc | |||
| 5418ab2081 | |||
| e8e4110334 | |||
| 17e368435d |
@@ -1,25 +1,16 @@
|
||||
name: Dependabot Auto-Merge
|
||||
name: Merge Dependabot PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
- main
|
||||
- '*.x'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
run-name: Merge Dependabot PR ${{ github.ref_name }}
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security'
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@d7267f607e9d3fb96fc2fbe83e0af444713e90b7
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
- name: Enable auto-merge for Dependabot PRs
|
||||
run: gh pr merge --auto --merge "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
merge-dependabot-pr:
|
||||
permissions: write-all
|
||||
uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@v7
|
||||
with:
|
||||
mergeArguments: --auto --rebase
|
||||
@@ -31,6 +31,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -17,7 +17,7 @@ permissions:
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
@@ -30,22 +30,24 @@ jobs:
|
||||
deploy-artifacts:
|
||||
name: Deploy Artifacts
|
||||
needs: [ build]
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
with:
|
||||
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
|
||||
default-publish-milestones-central: true
|
||||
java-version: 25
|
||||
secrets: inherit
|
||||
deploy-schema:
|
||||
name: Deploy Schema
|
||||
needs: [ build ]
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
with:
|
||||
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
|
||||
java-version: 25
|
||||
secrets: inherit
|
||||
perform-release:
|
||||
name: Perform Release
|
||||
needs: [ deploy-artifacts, deploy-schema ]
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
with:
|
||||
should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }}
|
||||
project-version: ${{ needs.deploy-artifacts.outputs.project-version }}
|
||||
@@ -53,6 +55,7 @@ jobs:
|
||||
release-repo-url: https://repo1.maven.org/maven2
|
||||
artifact-path: org/springframework/security/spring-security-core
|
||||
slack-announcing-id: spring-security-announcing
|
||||
java-version: 25:q
|
||||
secrets: inherit
|
||||
send-notification:
|
||||
name: Send Notification
|
||||
@@ -61,6 +64,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -16,7 +16,7 @@ permissions:
|
||||
jobs:
|
||||
perform-release:
|
||||
name: Perform Release
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
with:
|
||||
should-perform-release: true
|
||||
project-version: ${{ inputs.version }}
|
||||
|
||||
@@ -30,6 +30,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -46,6 +46,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
|
||||
@@ -9,7 +9,7 @@ permissions:
|
||||
jobs:
|
||||
update-scheduled-release-version:
|
||||
name: Update Scheduled Release Version
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
secrets: inherit
|
||||
send-notification:
|
||||
name: Send Notification
|
||||
@@ -18,6 +18,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send Notification
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@7d42d82298553f123a9dad622e0eac725aaf52ef # v1.0.13
|
||||
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
|
||||
with:
|
||||
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
|
||||
+2
-2
@@ -31,7 +31,7 @@ If you have a question, check Stack Overflow using
|
||||
https://stackoverflow.com/questions/tagged/spring-security+or+spring-ldap+or+spring-authorization-server+or+spring-session?tab=Newest[this list of tags].
|
||||
Find an existing discussion, or start a new one if necessary.
|
||||
|
||||
If you believe there is an issue, search through https://github.com/spring-projects/spring-security/issues[existing issues] trying a few different ways to find discussions, past or current, that are related to the issue.
|
||||
If you believe there is an issue, search through https://github.com/spring-projects/spring-security/issues[existing issues] trying a few different ways to find discussions, past or current, that are related to the issue.
|
||||
Reading those discussions helps you to learn about the issue, and helps us to make a decision.
|
||||
|
||||
[[find-an-issue]]
|
||||
@@ -94,7 +94,7 @@ Don't worry if you don't get them all correct the first time, we will help you.
|
||||
|
||||
1. [[sign-cla]] All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin.
|
||||
For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring].
|
||||
2. [[create-an-issue-list]] Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier discuss with the team first to determine the right fix or enhancement.
|
||||
2. [[create-an-issue-list]] Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier to discuss with the team first to determine the right fix or enhancement.
|
||||
For typos and straightforward bug fixes, starting with a pull request is encouraged.
|
||||
Please include a description for context and motivation.
|
||||
Note that the team may close your pull request if it's not a fit for the project.
|
||||
|
||||
@@ -949,6 +949,21 @@ fun getMessages(): List<Message> { }
|
||||
----
|
||||
======
|
||||
|
||||
[[method-security-has-scope]]
|
||||
=== Using `hasScope` in Method Security
|
||||
|
||||
Because method security expressions can evaluation `AuthorizationManager` instances, you can also use the `hasScope` API by publishing a `DefaultOAuth2AuthorizationManagerFactory` `@Bean`:
|
||||
|
||||
include-code::./MethodSecurityHasScopeConfiguration[tag=declare-factory,indent=0]
|
||||
|
||||
and then doing:
|
||||
|
||||
include-code::./MessageService[tag=protected-method,indent=0]
|
||||
|
||||
If you are using xref:servlet/authentication/mfa.adoc[Spring Security's MFA feature], then you can supply its `AuthorizationManagerFactory` instance to ensure that your authentication factors are automatically checked as well by including it in your `DefaultOAuth2AuthorizationManagerFactory` constructor as follows:
|
||||
|
||||
include-code::./MethodSecurityHasScopeMfaConfiguration[tag=declare-factory,indent=0]
|
||||
|
||||
[[oauth2resourceserver-jwt-authorization-extraction]]
|
||||
=== Extracting Authorities Manually
|
||||
|
||||
|
||||
@@ -638,6 +638,21 @@ fun getMessages(): List<Message?> {}
|
||||
----
|
||||
======
|
||||
|
||||
[[method-security-has-scope]]
|
||||
=== Using `hasScope` in Method Security
|
||||
|
||||
Because method security expressions can evaluation `AuthorizationManager` instances, you can also use the `hasScope` API by publishing a `DefaultOAuth2AuthorizationManagerFactory` `@Bean`:
|
||||
|
||||
include-code::./MethodSecurityHasScopeConfiguration[tag=declare-factory,indent=0]
|
||||
|
||||
and then doing:
|
||||
|
||||
include-code::./MessageService[tag=protected-method,indent=0]
|
||||
|
||||
If you are using xref:servlet/authentication/mfa.adoc[Spring Security's MFA feature], then you can supply its `AuthorizationManagerFactory` instance to ensure that your authentication factors are automatically checked as well by including it in your `DefaultOAuth2AuthorizationManagerFactory` constructor as follows:
|
||||
|
||||
include-code::./MethodSecurityHasScopeMfaConfiguration[tag=declare-factory,indent=0]
|
||||
|
||||
[[oauth2resourceserver-opaque-authorization-extraction]]
|
||||
=== Extracting Authorities Manually
|
||||
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
|
||||
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
class MessageService {
|
||||
|
||||
// tag::protected-method[]
|
||||
@PreAuthorize("@oauth2.hasScope('message:read')")
|
||||
String readMessage() {
|
||||
return "message";
|
||||
}
|
||||
// end::protected-method[]
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory;
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory;
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
class MethodSecurityHasScopeConfiguration {
|
||||
// tag::declare-factory[]
|
||||
@Bean
|
||||
OAuth2AuthorizationManagerFactory<?> oauth2() {
|
||||
return new DefaultOAuth2AuthorizationManagerFactory<>();
|
||||
}
|
||||
// end::declare-factory[]
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SecurityTestExecutionListeners
|
||||
public class MethodSecurityHasScopeConfigurationTests {
|
||||
public final SpringTestContext spring = new SpringTestContext(this).mockMvcAfterSpringSecurityOk();
|
||||
|
||||
@Autowired
|
||||
private MessageService messages;
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = "SCOPE_message:read")
|
||||
void readMessageWhenMessageReadThenAllowed() {
|
||||
this.spring.register(MethodSecurityHasScopeConfiguration.class, MessageService.class).autowire();
|
||||
this.messages.readMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void readMessageWhenNoScopeThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeConfiguration.class, MessageService.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messages::readMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = { "SCOPE_message:read", "FACTOR_BEARER", "FACTOR_X509" })
|
||||
void mfaReadMessageWhenMessageReadAndFactorsThenAllowed() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration.class, MessageService.class).autowire();
|
||||
this.messages.readMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = { "SCOPE_message:read" })
|
||||
void mfaReadMessageWhenMessageReadThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration.class, MessageService.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messages::readMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void mfaReadMessageWhenNoScopeThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration.class, MessageService.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messages::readMessage);
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.methodsecurityhasscope;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactory;
|
||||
import org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory;
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory;
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
@EnableMultiFactorAuthentication(authorities = { "FACTOR_BEARER", "FACTOR_X509" })
|
||||
class MethodSecurityHasScopeMfaConfiguration {
|
||||
// tag::declare-factory[]
|
||||
@Bean
|
||||
OAuth2AuthorizationManagerFactory<?> oauth2(AuthorizationManagerFactory<?> authz) {
|
||||
return new DefaultOAuth2AuthorizationManagerFactory<>(authz);
|
||||
}
|
||||
// end::declare-factory[]
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
|
||||
@Service
|
||||
open class MessageService {
|
||||
// tag::protected-method[]
|
||||
@PreAuthorize("@oauth2.hasScope('message:read')")
|
||||
open fun readMessage(): String {
|
||||
return "message"
|
||||
}
|
||||
// end::protected-method[]
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
open class MethodSecurityHasScopeConfiguration {
|
||||
// tag::declare-factory[]
|
||||
@Bean
|
||||
open fun oauth2(): OAuth2AuthorizationManagerFactory<Any> {
|
||||
return DefaultOAuth2AuthorizationManagerFactory()
|
||||
}
|
||||
// end::declare-factory[]
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
|
||||
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.security.access.AccessDeniedException
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners
|
||||
import org.springframework.security.test.context.support.WithMockUser
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
@ExtendWith(SpringTestContextExtension::class)
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@SecurityTestExecutionListeners
|
||||
class MethodSecurityHasScopeConfigurationTests {
|
||||
@JvmField
|
||||
val spring: SpringTestContext = SpringTestContext(this).mockMvcAfterSpringSecurityOk()
|
||||
|
||||
@Autowired
|
||||
var messages: MessageService? = null
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = ["SCOPE_message:read"])
|
||||
fun readMessageWhenMessageReadThenAllowed() {
|
||||
this.spring.register(MethodSecurityHasScopeConfiguration::class.java, MessageService::class.java).autowire()
|
||||
this.messages!!.readMessage()
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
fun readMessageWhenNoScopeThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeConfiguration::class.java, MessageService::class.java).autowire()
|
||||
Assertions.assertThatExceptionOfType<AccessDeniedException?>(AccessDeniedException::class.java)
|
||||
.isThrownBy({ this.messages!!.readMessage() })
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = ["SCOPE_message:read", "FACTOR_BEARER", "FACTOR_X509"])
|
||||
fun mfaReadMessageWhenMessageReadAndFactorsThenAllowed() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration::class.java, MessageService::class.java).autowire()
|
||||
this.messages!!.readMessage()
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = ["SCOPE_message:read"])
|
||||
fun mfaReadMessageWhenMessageReadThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration::class.java, MessageService::class.java).autowire()
|
||||
Assertions.assertThatExceptionOfType<AccessDeniedException?>(AccessDeniedException::class.java)
|
||||
.isThrownBy({ this.messages!!.readMessage() })
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
fun mfaReadMessageWhenNoScopeThenDenied() {
|
||||
this.spring.register(MethodSecurityHasScopeMfaConfiguration::class.java, MessageService::class.java).autowire()
|
||||
Assertions.assertThatExceptionOfType<AccessDeniedException?>(AccessDeniedException::class.java)
|
||||
.isThrownBy({ this.messages!!.readMessage() })
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.methodsecurityhasscope
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactory
|
||||
import org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.oauth2.core.authorization.DefaultOAuth2AuthorizationManagerFactory
|
||||
import org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagerFactory
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
@EnableMultiFactorAuthentication(authorities = ["FACTOR_BEARER", "FACTOR_X509"])
|
||||
open class MethodSecurityHasScopeMfaConfiguration {
|
||||
// tag::declare-factory[]
|
||||
@Bean
|
||||
open fun oauth2(authz: AuthorizationManagerFactory<Any>): OAuth2AuthorizationManagerFactory<Any> {
|
||||
return DefaultOAuth2AuthorizationManagerFactory(authz)
|
||||
} // end::declare-factory[]
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
springBootVersion=4.1.0-SNAPSHOT
|
||||
version=7.1.0-SNAPSHOT
|
||||
version=7.1.0-M2
|
||||
samplesBranch=main
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
|
||||
+10
-10
@@ -12,31 +12,31 @@ org-jetbrains-kotlin = "2.3.10"
|
||||
org-jetbrains-kotlinx = "1.10.2"
|
||||
org-mockito = "5.21.0"
|
||||
org-opensaml5 = "5.2.0"
|
||||
org-springframework = "7.0.3"
|
||||
org-springframework = "7.0.4"
|
||||
com-password4j = "1.8.4"
|
||||
|
||||
[libraries]
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.28"
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.29"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.21.0"
|
||||
com-google-inject-guice = "com.google.inject:guice:3.0"
|
||||
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
|
||||
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.4"
|
||||
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.26.1"
|
||||
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.6"
|
||||
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.33"
|
||||
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
|
||||
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
|
||||
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.4"
|
||||
com-jayway-jsonpath-json-path = "com.jayway.jsonpath:json-path:2.10.0"
|
||||
commons-collections = "commons-collections:commons-collections:3.2.2"
|
||||
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.2.1"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.16.2"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.16.3"
|
||||
io-mockk = "io.mockk:mockk:1.14.9"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.2"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.3"
|
||||
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
|
||||
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
|
||||
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
|
||||
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
|
||||
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
|
||||
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.13"
|
||||
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.14"
|
||||
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"
|
||||
@@ -81,8 +81,8 @@ 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.2"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.1"
|
||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.3"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.2"
|
||||
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.4"
|
||||
@@ -102,7 +102,7 @@ org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanne
|
||||
org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1"
|
||||
|
||||
spring-nullability = 'io.spring.nullability:io.spring.nullability.gradle.plugin:0.0.11'
|
||||
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.29.7.RELEASE'
|
||||
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.31.0.RELEASE'
|
||||
com-password4j-password4j = { module = "com.password4j:password4j", version.ref = "com-password4j" }
|
||||
|
||||
[plugins]
|
||||
|
||||
+10
-1
@@ -105,7 +105,7 @@ public final class ClientRegistrations {
|
||||
* @return the {@link ClientRegistration} built from the configuration
|
||||
*/
|
||||
public static ClientRegistration.Builder fromOidcConfiguration(Map<String, Object> configuration) {
|
||||
OIDCProviderMetadata metadata = parse(configuration, OIDCProviderMetadata::parse);
|
||||
OIDCProviderMetadata metadata = parseInput(configuration, OIDCProviderMetadata::parse);
|
||||
ClientRegistration.Builder builder = withProviderConfiguration(metadata, metadata.getIssuer().getValue());
|
||||
builder.jwkSetUri(metadata.getJWKSetURI().toASCIIString());
|
||||
if (metadata.getUserInfoEndpointURI() != null) {
|
||||
@@ -292,6 +292,15 @@ public final class ClientRegistrations {
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
|
||||
private static <T> T parseInput(Map<String, Object> body, ThrowingFunction<JSONObject, T, ParseException> parser) {
|
||||
try {
|
||||
return parse(body, parser);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T parse(Map<String, Object> body, ThrowingFunction<JSONObject, T, ParseException> parser) {
|
||||
try {
|
||||
return parser.apply(new JSONObject(body));
|
||||
|
||||
+1
-2
@@ -40,7 +40,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
@@ -475,7 +474,7 @@ public class ClientRegistrationsTests {
|
||||
@Test
|
||||
public void issuerWhenOidcConfigurationResponseMissingJwksUriThenThrowsIllegalArgumentException() throws Exception {
|
||||
this.response.remove("jwks_uri");
|
||||
assertThatNullPointerException().isThrownBy(() -> registration(this.response).build());
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> registration(this.response).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2025-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.core.authorization;
|
||||
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactory;
|
||||
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A factory for creating different kinds of {@link AuthorizationManager} instances.
|
||||
*
|
||||
* @param <T> the type of object that the authorization check is being done on
|
||||
* @author Ngoc Nhan
|
||||
* @since 7.1
|
||||
*/
|
||||
public final class DefaultOAuth2AuthorizationManagerFactory<T> implements OAuth2AuthorizationManagerFactory<T> {
|
||||
|
||||
private String scopePrefix = "SCOPE_";
|
||||
|
||||
private final AuthorizationManagerFactory<T> authorizationManagerFactory;
|
||||
|
||||
public DefaultOAuth2AuthorizationManagerFactory() {
|
||||
this(new DefaultAuthorizationManagerFactory<>());
|
||||
}
|
||||
|
||||
public DefaultOAuth2AuthorizationManagerFactory(AuthorizationManagerFactory<T> authorizationManagerFactory) {
|
||||
Assert.notNull(authorizationManagerFactory, "authorizationManagerFactory can not be null");
|
||||
this.authorizationManagerFactory = authorizationManagerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the prefix used to create an authority name from a scope name. Can be an empty
|
||||
* string.
|
||||
* @param scopePrefix the scope prefix to use
|
||||
*/
|
||||
public void setScopePrefix(String scopePrefix) {
|
||||
Assert.notNull(scopePrefix, "scopePrefix can not be null");
|
||||
this.scopePrefix = scopePrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationManager<T> hasScope(String scope) {
|
||||
Assert.notNull(scope, "scope can not be null");
|
||||
assertScope(scope);
|
||||
return this.authorizationManagerFactory.hasAuthority(this.scopePrefix + scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationManager<T> hasAnyScope(String... scopes) {
|
||||
return this.authorizationManagerFactory.hasAnyAuthority(this.mappedScopes(scopes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationManager<T> hasAllScopes(String... scopes) {
|
||||
return this.authorizationManagerFactory.hasAllAuthorities(this.mappedScopes(scopes));
|
||||
}
|
||||
|
||||
private String[] mappedScopes(String... scopes) {
|
||||
Assert.notNull(scopes, "scopes can not be null");
|
||||
String[] mappedScopes = new String[scopes.length];
|
||||
for (int i = 0; i < scopes.length; i++) {
|
||||
assertScope(scopes[i]);
|
||||
mappedScopes[i] = this.scopePrefix + scopes[i];
|
||||
}
|
||||
return mappedScopes;
|
||||
}
|
||||
|
||||
private void assertScope(String scope) {
|
||||
Assert.isTrue(!scope.startsWith(this.scopePrefix), () -> scope + " should not start with '" + this.scopePrefix
|
||||
+ "' since '" + this.scopePrefix
|
||||
+ "' is automatically prepended when using hasScope and hasAnyScope. Consider using AuthorizationManagerFactory#hasAuthority or #hasAnyAuthority instead.");
|
||||
}
|
||||
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2025-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.core.authorization;
|
||||
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* A factory for creating different kinds of {@link AuthorizationManager} instances.
|
||||
*
|
||||
* @param <T> the type of object that the authorization check is being done on
|
||||
* @author Ngoc Nhan
|
||||
* @since 7.1
|
||||
*/
|
||||
public interface OAuth2AuthorizationManagerFactory<T> {
|
||||
|
||||
/**
|
||||
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
|
||||
* have a {@code SCOPE_scope} authority.
|
||||
*
|
||||
* <p>
|
||||
* For example, if you call {@code hasScope("read")}, then this will require that each
|
||||
* authentication have a {@link org.springframework.security.core.GrantedAuthority}
|
||||
* whose value is {@code SCOPE_read}.
|
||||
*
|
||||
* <p>
|
||||
* This would equivalent to calling
|
||||
* {@code AuthorityAuthorizationManager#hasAuthority("SCOPE_read")}.
|
||||
* @param scope the scope value to require
|
||||
* @return an {@link AuthorizationManager} that requires a {@code "SCOPE_scope"}
|
||||
* authority
|
||||
*/
|
||||
default AuthorizationManager<T> hasScope(String scope) {
|
||||
return OAuth2AuthorizationManagers.hasScope(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
|
||||
* have at least one authority among {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
|
||||
* {@code SCOPE_scopeN}.
|
||||
*
|
||||
* <p>
|
||||
* For example, if you call {@code hasAnyScope("read", "write")}, then this will
|
||||
* require that each authentication have at least a
|
||||
* {@link org.springframework.security.core.GrantedAuthority} whose value is either
|
||||
* {@code SCOPE_read} or {@code SCOPE_write}.
|
||||
*
|
||||
* <p>
|
||||
* This would equivalent to calling
|
||||
* {@code AuthorityAuthorizationManager#hasAnyAuthority("SCOPE_read", "SCOPE_write")}.
|
||||
* @param scopes the scope values to allow
|
||||
* @return an {@link AuthorizationManager} that requires at least one authority among
|
||||
* {@code "SCOPE_scope1"}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
|
||||
*/
|
||||
default AuthorizationManager<T> hasAnyScope(String... scopes) {
|
||||
return OAuth2AuthorizationManagers.hasAnyScope(scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
|
||||
* have all authorities {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
|
||||
* {@code SCOPE_scopeN}.
|
||||
*
|
||||
* <p>
|
||||
* For example, if you call {@code hasAllScopes("read", "write")}, then each
|
||||
* {@link org.springframework.security.core.Authentication} must have all
|
||||
* {@link org.springframework.security.core.GrantedAuthority} values of
|
||||
* {@code SCOPE_read} and {@code SCOPE_write}.
|
||||
*
|
||||
* <p>
|
||||
* This would be equivalent to calling
|
||||
* {@code AllAuthoritiesAuthorizationManager#hasAllAuthorities("SCOPE_read", "SCOPE_write")}.
|
||||
* @param scopes the scope values to require
|
||||
* @return an {@link AuthorizationManager} that requires all authorities
|
||||
* {@code SCOPE_scope1}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
|
||||
*/
|
||||
default AuthorizationManager<T> hasAllScopes(String... scopes) {
|
||||
return OAuth2AuthorizationManagers.hasAllScopes(scopes);
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.oauth2.core.authorization;
|
||||
|
||||
import org.springframework.security.authorization.AllAuthoritiesAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.util.Assert;
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see AuthorityAuthorizationManager
|
||||
* @see AllAuthoritiesAuthorizationManager
|
||||
*/
|
||||
public final class OAuth2AuthorizationManagers {
|
||||
|
||||
@@ -85,6 +87,34 @@ public final class OAuth2AuthorizationManagers {
|
||||
return AuthorityAuthorizationManager.hasAnyAuthority(mappedScopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link AuthorizationManager} that requires an {@link Authentication} to
|
||||
* have all authorities {@code SCOPE_scope1}, {@code SCOPE_scope2}, ...
|
||||
* {@code SCOPE_scopeN}.
|
||||
*
|
||||
* <p>
|
||||
* For example, if you call {@code hasAllScopes("read", "write")}, then each
|
||||
* {@link org.springframework.security.core.Authentication} must have all
|
||||
* {@link org.springframework.security.core.GrantedAuthority} values of
|
||||
* {@code SCOPE_read} and {@code SCOPE_write}.
|
||||
*
|
||||
* <p>
|
||||
* This would be equivalent to calling
|
||||
* {@code AllAuthoritiesAuthorizationManager#hasAllAuthorities("SCOPE_read", "SCOPE_write")}.
|
||||
* @param scopes the scope values to require
|
||||
* @return an {@link AuthorizationManager} that requires all authorities
|
||||
* {@code SCOPE_scope1}, {@code SCOPE_scope2}, ... {@code SCOPE_scopeN}.
|
||||
* @since 7.1
|
||||
*/
|
||||
public static <T> AuthorizationManager<T> hasAllScopes(String... scopes) {
|
||||
String[] mappedScopes = new String[scopes.length];
|
||||
for (int i = 0; i < scopes.length; i++) {
|
||||
assertScope(scopes[i]);
|
||||
mappedScopes[i] = "SCOPE_" + scopes[i];
|
||||
}
|
||||
return AllAuthoritiesAuthorizationManager.hasAllAuthorities(mappedScopes);
|
||||
}
|
||||
|
||||
private static void assertScope(String scope) {
|
||||
Assert.isTrue(!scope.startsWith("SCOPE_"),
|
||||
() -> scope + " should not start with SCOPE_ since SCOPE_"
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2025-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.core.authorization;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationManagerFactories;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AuthorizationManagerFactory}.
|
||||
*
|
||||
* @author Ngoc Nhan
|
||||
*/
|
||||
public class OAuth2AuthorizationManagerFactoryTests {
|
||||
|
||||
private static final String MSG_READ = "message:read";
|
||||
|
||||
private static final String MSG_WRITE = "message:write";
|
||||
|
||||
private static final String SCOPE_MSG_READ = "SCOPE_message:read";
|
||||
|
||||
private static final String SCOPE_MSG_WRITE = "SCOPE_message:write";
|
||||
|
||||
@Test
|
||||
public void hasScopeReturnsAuthorityAuthorizationManagerByDefault() {
|
||||
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
|
||||
AuthorizationManager<String> authorizationManager = factory.hasScope(MSG_READ);
|
||||
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAnyScopeReturnsAuthorityAuthorizationManagerByDefault() {
|
||||
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
|
||||
AuthorizationManager<String> authorizationManager = factory.hasAnyScope(MSG_READ, MSG_WRITE);
|
||||
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAllScopesReturnsAuthorityAuthorizationManagerByDefault() {
|
||||
OAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>();
|
||||
AuthorizationManager<String> authorizationManager = factory.hasAnyScope(MSG_READ, MSG_WRITE);
|
||||
assertThat(authorizationManager).isInstanceOf(AuthorityAuthorizationManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasScopeWhenSetAuthorizationManagerFactories() {
|
||||
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
|
||||
AuthorizationManagerFactories.<String>multiFactor().requireFactors(SCOPE_MSG_READ).build());
|
||||
assertUserGranted(factory.hasScope(MSG_READ), SCOPE_MSG_READ);
|
||||
assertUserDenied(factory.hasScope(MSG_WRITE), SCOPE_MSG_READ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAnyScopeWhenSetAuthorizationManagerFactories() {
|
||||
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
|
||||
AuthorizationManagerFactories.<String>multiFactor().requireFactors(SCOPE_MSG_READ).build());
|
||||
assertUserGranted(factory.hasAnyScope(MSG_READ), SCOPE_MSG_READ);
|
||||
assertUserDenied(factory.hasAnyScope(MSG_WRITE), SCOPE_MSG_READ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasAllScopesWhenSetAuthorizationManagerFactories() {
|
||||
DefaultOAuth2AuthorizationManagerFactory<String> factory = new DefaultOAuth2AuthorizationManagerFactory<>(
|
||||
AuthorizationManagerFactories.<String>multiFactor()
|
||||
.requireFactors(SCOPE_MSG_READ, SCOPE_MSG_WRITE)
|
||||
.build());
|
||||
assertUserGranted(factory.hasAllScopes(MSG_READ, MSG_WRITE), SCOPE_MSG_READ, SCOPE_MSG_WRITE);
|
||||
assertUserDenied(factory.hasAllScopes(MSG_READ, MSG_WRITE), SCOPE_MSG_READ);
|
||||
}
|
||||
|
||||
private void assertUserGranted(AuthorizationManager<String> manager, String... authorities) {
|
||||
AuthorizationResult authorizationResult = createAuthorizationResult(manager, authorities);
|
||||
assertThat(authorizationResult).isNotNull();
|
||||
assertThat(authorizationResult.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
private void assertUserDenied(AuthorizationManager<String> manager, String... authorities) {
|
||||
AuthorizationResult authorizationResult = createAuthorizationResult(manager, authorities);
|
||||
assertThat(authorizationResult).isNotNull();
|
||||
assertThat(authorizationResult.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
private AuthorizationResult createAuthorizationResult(AuthorizationManager<String> manager, String... authorities) {
|
||||
TestingAuthenticationToken authenticatedUser = new TestingAuthenticationToken("user", "pass", authorities);
|
||||
return manager.authorize(() -> authenticatedUser, "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,10 +18,12 @@ dependencies {
|
||||
optional 'org.springframework:spring-jdbc'
|
||||
optional 'org.springframework:spring-tx'
|
||||
optional 'tools.jackson.core:jackson-databind'
|
||||
optional 'com.fasterxml.jackson.core:jackson-databind'
|
||||
|
||||
provided 'jakarta.servlet:jakarta.servlet-api'
|
||||
|
||||
testImplementation project(path: ':spring-security-core', configuration: 'tests')
|
||||
testImplementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor'
|
||||
testImplementation 'io.projectreactor:reactor-test'
|
||||
testImplementation 'jakarta.xml.bind:jakarta.xml.bind-api'
|
||||
testImplementation 'jakarta.websocket:jakarta.websocket-api'
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ public final class TestAuthenticatorAttestationResponses {
|
||||
public static AuthenticatorAttestationResponse.AuthenticatorAttestationResponseBuilder createAuthenticatorAttestationResponse() {
|
||||
return AuthenticatorAttestationResponse.builder()
|
||||
.attestationObject(Bytes.fromBase64(
|
||||
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUy9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNhdAAAAALraVWanqkAfvZZFYZpVEg0AEDWRLOHq0Wxw4cOkCemynKqlAQIDJiABIVgg4Hkrn2kbGmpZTdoDZUNrppo93OqgQV7ONzVvo5GLCFciWCCrf6yIQggq2BfZntawxRsBBbWG_FWkYAoU8yPipS-5hg-p1VREax-qKTGn1Bp92fRMjRMunH7XwSlf9mMWJdY3VvAkicTChlxVd256yk4jEiXiJ5BuwugdpT7fBOYtymjpQECAyYgASFYIJK-2epPEw0ujHN-gvVp2Hp3ef8CzU3zqwO5ylx8L2OsIlggK5x5OlTGEPxLS-85TAABum4aqVK4CSWJ7LYDdkjuBLk"))
|
||||
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUy9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNhdAAAAALraVWanqkAfvZZFYZpVEg0AEDWRLOHq0Wxw4cOkCemynKqlAQIDJiABIVgg4Hkrn2kbGmpZTdoDZUNrppo93OqgQV7ONzVvo5GLCFciWCCrf6yIQggq2BfZntawxRsBBbWG_FWkYAoU8yPipS-5hg=="))
|
||||
.clientDataJSON(Bytes.fromBase64(
|
||||
"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoicTdsQ2RkM1NWUXhkQy12OHBuUkFHRW4xQjJNLXQ3WkVDV1B3Q0FtaFd2YyIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0"))
|
||||
.transports(AuthenticatorTransport.HYBRID, AuthenticatorTransport.INTERNAL);
|
||||
|
||||
+8
-3
@@ -43,6 +43,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.dataformat.cbor.CBORMapper;
|
||||
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
@@ -78,6 +80,7 @@ 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.RETURNS_SELF;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
@@ -613,9 +616,11 @@ class Webauthn4jRelyingPartyOperationsTests {
|
||||
|
||||
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));
|
||||
JsonMapper json = new JsonMapper();
|
||||
CBORMapper cbor = mock(CBORMapper.class);
|
||||
CBORMapper.Builder builder = mock(CBORMapper.Builder.class, RETURNS_SELF);
|
||||
given(builder.build()).willReturn(cbor);
|
||||
given(cbor.rebuild()).willReturn(builder);
|
||||
AttestationObject attestationObject = mock(AttestationObject.class);
|
||||
AuthenticatorData wa4jAuthData = mock(AuthenticatorData.class);
|
||||
given(attestationObject.getAuthenticatorData()).willReturn(wa4jAuthData);
|
||||
|
||||
Reference in New Issue
Block a user