mirror of
https://github.com/spring-projects/spring-security.git
synced 2026-02-24 22:25:18 +00:00
Document Method Security hasScope Support
Issue gh-18013 Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
This commit is contained in:
parent
f2b7cb2de5
commit
705fa60a01
@ -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
|
||||
|
||||
|
||||
@ -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[]
|
||||
}
|
||||
@ -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[]
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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[]
|
||||
}
|
||||
@ -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[]
|
||||
}
|
||||
@ -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[]
|
||||
}
|
||||
@ -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() })
|
||||
}
|
||||
}
|
||||
@ -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[]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user