Browse Source

Document Method Security hasScope Support

Issue gh-18013

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
pull/18713/head
Josh Cummings 2 months ago
parent
commit
705fa60a01
  1. 15
      docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc
  2. 15
      docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc
  3. 16
      docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MessageService.java
  4. 18
      docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeConfiguration.java
  5. 59
      docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeConfigurationTests.java
  6. 21
      docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeMfaConfiguration.java
  7. 15
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MessageService.kt
  8. 18
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeConfiguration.kt
  9. 61
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeConfigurationTests.kt
  10. 20
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeMfaConfiguration.kt

15
docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc

@ -949,6 +949,21 @@ fun getMessages(): List<Message> { } @@ -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

15
docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc

@ -638,6 +638,21 @@ fun getMessages(): List<Message?> {} @@ -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
docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MessageService.java

@ -0,0 +1,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
docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeConfiguration.java

@ -0,0 +1,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
docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeConfigurationTests.java

@ -0,0 +1,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
docs/src/test/java/org/springframework/security/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeMfaConfiguration.java

@ -0,0 +1,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
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MessageService.kt

@ -0,0 +1,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
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeConfiguration.kt

@ -0,0 +1,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
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeConfigurationTests.kt

@ -0,0 +1,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
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/oauth2/resourceserver/methodsecurityhasscope/MethodSecurityHasScopeMfaConfiguration.kt

@ -0,0 +1,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[]
}
Loading…
Cancel
Save