此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.5! |
方法安全性
除了在请求级别对授权进行建模外, Spring Security 还支持在方法级别进行建模。
您可以在应用程序中通过注释任何@Configuration
class 替换为@EnableMethodSecurity
或添加<method-security>
添加到任何 XML 配置文件中,如下所示:
-
Java
-
Kotlin
-
Xml
@EnableMethodSecurity
@EnableMethodSecurity
<sec:method-security/>
然后,您可以立即使用@PreAuthorize
,@PostAuthorize
,@PreFilter
和@PostFilter
授权方法调用,包括输入参数和返回值。
默认情况下,Spring Boot Starter Security 不激活方法级授权。 |
Method Security 还支持许多其他用例,包括 AspectJ 支持、自定义注释和几个配置点。 考虑了解以下使用案例:
-
了解方法安全性的工作原理以及使用它的原因
-
使用 授权方法
@PreAuthorize
和@PostAuthorize
-
使用 JSR-250 注解授权方法
-
使用 AspectJ 表达式授权方法
-
与 AspectJ 字节码编织集成
-
自定义 SpEL 表达式处理
方法安全性的工作原理
Spring Security 的方法授权支持对于以下方面非常方便:
-
提取细粒度的授权逻辑;例如,当 method parameters 和 return values 对授权决策有贡献时。
-
在服务层实施安全性
-
在风格上倾向于基于注释而不是
HttpSecurity
基于 的配置
由于 Method Security 是使用 Spring AOP 构建的,因此您可以访问其所有表达能力,以根据需要覆盖 Spring Security 的默认值。
如前所述,您首先要添加@EnableMethodSecurity
更改为@Configuration
class 或<sec:method-security/>
在 Spring XML 配置文件中。
此 annotation 和 XML 元素取代
如果您正在使用 |
方法授权是方法授权之前和方法之后授权的组合。 考虑以下列方式注释的服务 Bean:
-
Java
-
Kotlin
@Service
public class MyCustomerService {
@PreAuthorize("hasAuthority('permission:read')")
@PostAuthorize("returnObject.owner == authentication.name")
public Customer readCustomer(String id) { ... }
}
@Service
open class MyCustomerService {
@PreAuthorize("hasAuthority('permission:read')")
@PostAuthorize("returnObject.owner == authentication.name")
fun readCustomer(id: String): Customer { ... }
}
对MyCustomerService#readCustomer
激活方法安全性时,可能如下所示:

-
Spring AOP 调用其代理方法
readCustomer
.在代理的其他顾问程序中,它会调用AuthorizationManagerBeforeMethodInterceptor
匹配这@PreAuthorize
切入点 -
授权管理器使用
MethodSecurityExpressionHandler
解析注释的 SPEL 表达式并构造相应的EvaluationContext
从MethodSecurityExpressionRoot
含一个Supplier<Authentication>
和MethodInvocation
. -
侦听器使用此上下文来评估表达式;具体来说,它写道这
Authentication
从Supplier
并检查它是否具有permission:read
在其权威集合中 -
如果评估通过,则 Spring AOP 将继续调用该方法。
-
否则,拦截器会发布一个
AuthorizationDeniedEvent
并抛出一个AccessDeniedException
哪这ExceptionTranslationFilter
捕获并向响应返回 403 状态代码 -
在方法返回后, Spring AOP 会调用
AuthorizationManagerAfterMethodInterceptor
匹配这@PostAuthorize
切入点,作与上述相同,但使用PostAuthorizeAuthorizationManager
-
如果评估通过(在本例中,返回值属于已登录用户),则处理将继续正常进行
-
否则,拦截器会发布一个
AuthorizationDeniedEvent
并抛出一个AccessDeniedException
哪这ExceptionTranslationFilter
捕获并向响应返回 403 状态代码
如果未在 HTTP 请求的上下文中调用该方法,则可能需要处理AccessDeniedException 你自己 |
多个注释是按顺序计算的
如上所述,如果方法调用涉及多个 Method Security 注释,则一次处理一个 Method Security 注释。 这意味着它们可以统称为“和”在一起。 换句话说,要使调用获得授权,所有 Comments 检查都需要通过授权。
每个注释都有自己的切入点
每个 Annotation 都有自己的 pointcut 实例,该实例从方法及其封闭类开始,在整个对象层次结构中查找该 Annotation 或其元 Annotation 对应项。
每个 Annotation 都有自己的 Method Interceptor
每个 Comments 都有其自己的专用方法拦截器。
这样做的原因是为了让事情更具可组合性。
例如,如果需要,您可以禁用 Spring Security 默认值和仅发布@PostAuthorize
method intercept器.
方法拦截器如下:
-
为
@PreAuthorize
,Spring Security 使用AuthorizationManagerBeforeMethodInterceptor#preAuthorize
,而PreAuthorizeAuthorizationManager
-
为
@PostAuthorize
,Spring Security 使用AuthorizationManagerAfterMethodInterceptor#postAuthorize
,而PostAuthorizeAuthorizationManager
-
为
@PreFilter
,Spring Security 使用PreFilterAuthorizationMethodInterceptor
-
为
@PostFilter
,Spring Security 使用PostFilterAuthorizationMethodInterceptor
-
为
@Secured
,Spring Security 使用AuthorizationManagerBeforeMethodInterceptor#secured
,而SecuredAuthorizationManager
-
对于 JSR-250 注释,Spring Security 使用
AuthorizationManagerBeforeMethodInterceptor#jsr250
,而Jsr250AuthorizationManager
一般来说,你可以认为下面的清单代表了 Spring Security 在你添加@EnableMethodSecurity
:
-
Java
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor preAuthorizeMethodInterceptor() {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor postAuthorizeMethodInterceptor() {
return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor preFilterMethodInterceptor() {
return AuthorizationManagerBeforeMethodInterceptor.preFilter();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor postFilterMethodInterceptor() {
return AuthorizationManagerAfterMethodInterceptor.postFilter();
}
优先授予对复杂 SPEL 表达式的权限
很多时候,引入复杂的 SPEL 表达式可能很诱人,如下所示:
-
Java
-
Kotlin
@PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
@PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
但是,您可以改为授予permission:read
对于那些ROLE_ADMIN
.
一种方法是使用RoleHierarchy
这样:
-
Java
-
Kotlin
-
Xml
@Bean
static RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > permission:read");
}
companion object {
@Bean
fun roleHierarchy(): RoleHierarchy {
return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > permission:read")
}
}
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl" factory-method="fromHierarchy">
<constructor-arg value="ROLE_ADMIN > permission:read"/>
</bean>
然后在MethodSecurityExpressionHandler
实例.
这样,您就可以拥有更简单的@PreAuthorize
表达式,如下所示:
-
Java
-
Kotlin
@PreAuthorize("hasAuthority('permission:read')")
@PreAuthorize("hasAuthority('permission:read')")
或者,在可能的情况下,在登录时将特定于应用程序的授权逻辑调整为授予的权限。
比较请求级和方法级授权
何时应优先使用方法级授权而不是请求级授权? 其中一些归结为品味;但是,请考虑以下每个优势列表来帮助您做出决定。
请求级别 |
方法级别 |
|
授权类型 |
粗粒度 |
细粒度 |
配置位置 |
在 Config 类中声明 |
local to 方法声明 |
配置样式 |
DSL (英语) |
附注 |
授权定义 |
编程 |
斯佩尔 |
主要的权衡似乎是您希望授权规则所在的位置。
请务必记住,当您使用基于注释的方法安全性时,未注释的方法将不安全。
为了防止这种情况,请在您的HttpSecurity 实例。 |
使用注释进行授权
Spring Security 启用方法级授权支持的主要方式是通过可以添加到方法、类和接口的 Comments。
授权方法调用@PreAuthorize
当 Method Security 处于活动状态时,您可以使用@PreAuthorize
注解,如下所示:
-
Java
-
Kotlin
@Component
public class BankService {
@PreAuthorize("hasRole('ADMIN')")
public Account readAccount(Long id) {
// ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
}
}
@Component
open class BankService {
@PreAuthorize("hasRole('ADMIN')")
fun readAccount(id: Long): Account {
// ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
}
}
这是为了表明只有在提供的表达式hasRole('ADMIN')
通过。
然后,您可以测试该类以确认它正在强制执行授权规则,如下所示:
-
Java
-
Kotlin
@Autowired
BankService bankService;
@WithMockUser(roles="ADMIN")
@Test
void readAccountWithAdminRoleThenInvokes() {
Account account = this.bankService.readAccount("12345678");
// ... assertions
}
@WithMockUser(roles="WRONG")
@Test
void readAccountWithWrongRoleThenAccessDenied() {
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(
() -> this.bankService.readAccount("12345678"));
}
@WithMockUser(roles="ADMIN")
@Test
fun readAccountWithAdminRoleThenInvokes() {
val account: Account = this.bankService.readAccount("12345678")
// ... assertions
}
@WithMockUser(roles="WRONG")
@Test
fun readAccountWithWrongRoleThenAccessDenied() {
assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy {
this.bankService.readAccount("12345678")
}
}
@PreAuthorize 也可以是元注释,在类或接口级别定义,并使用 SPEL 授权表达式。 |
而@PreAuthorize
对于声明所需的权限非常有帮助,它也可以用于评估涉及方法参数的更复杂的表达式。
授权方法结果@PostAuthorize
当 Method Security 处于活动状态时,您可以使用@PostAuthorize
注解,如下所示:
-
Java
-
Kotlin
@Component
public class BankService {
@PostAuthorize("returnObject.owner == authentication.name")
public Account readAccount(Long id) {
// ... is only returned if the `Account` belongs to the logged in user
}
}
@Component
open class BankService {
@PostAuthorize("returnObject.owner == authentication.name")
fun readAccount(id: Long): Account {
// ... is only returned if the `Account` belongs to the logged in user
}
}
这是为了表明该方法只有在提供的表达式returnObject.owner == authentication.name
通过。returnObject
表示Account
对象。
然后,您可以测试该类以确认它正在强制执行授权规则:
-
Java
-
Kotlin
@Autowired
BankService bankService;
@WithMockUser(username="owner")
@Test
void readAccountWhenOwnedThenReturns() {
Account account = this.bankService.readAccount("12345678");
// ... assertions
}
@WithMockUser(username="wrong")
@Test
void readAccountWhenNotOwnedThenAccessDenied() {
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(
() -> this.bankService.readAccount("12345678"));
}
@WithMockUser(username="owner")
@Test
fun readAccountWhenOwnedThenReturns() {
val account: Account = this.bankService.readAccount("12345678")
// ... assertions
}
@WithMockUser(username="wrong")
@Test
fun readAccountWhenNotOwnedThenAccessDenied() {
assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy {
this.bankService.readAccount("12345678")
}
}
@PostAuthorize 也可以是元注释,在类或接口级别定义,并使用 SPEL 授权表达式。 |
@PostAuthorize
在防御不安全的 Direct Object Reference 时特别有用。
事实上,它可以被定义为元注释,如下所示:
-
Java
-
Kotlin
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PostAuthorize("returnObject.owner == authentication.name")
public @interface RequireOwnership {}
@Target(ElementType.METHOD, ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@PostAuthorize("returnObject.owner == authentication.name")
annotation class RequireOwnership
允许您改为按以下方式对服务进行注释:
-
Java
-
Kotlin
@Component
public class BankService {
@RequireOwnership
public Account readAccount(Long id) {
// ... is only returned if the `Account` belongs to the logged in user
}
}
@Component
open class BankService {
@RequireOwnership
fun readAccount(id: Long): Account {
// ... is only returned if the `Account` belongs to the logged in user
}
}
结果是上述方法只会返回Account
如果其owner
属性与已登录用户的name
.
否则,Spring Security 将抛出一个AccessDeniedException
并返回 403 状态代码。
过滤方法参数@PreFilter
当 Method Security 处于活动状态时,您可以使用@PreFilter
注解,如下所示:
-
Java
-
Kotlin
@Component
public class BankService {
@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Account... accounts) {
// ... `accounts` will only contain the accounts owned by the logged-in user
return updated;
}
}
@Component
open class BankService {
@PreFilter("filterObject.owner == authentication.name")
fun updateAccounts(vararg accounts: Account): Collection<Account> {
// ... `accounts` will only contain the accounts owned by the logged-in user
return updated
}
}
这是为了从accounts
其中表达式filterObject.owner == authentication.name
失败。filterObject
表示每个account
在accounts
,用于测试每个account
.
然后,您可以通过以下方式测试该类,以确认它正在强制执行授权规则:
-
Java
-
Kotlin
@Autowired
BankService bankService;
@WithMockUser(username="owner")
@Test
void updateAccountsWhenOwnedThenReturns() {
Account ownedBy = ...
Account notOwnedBy = ...
Collection<Account> updated = this.bankService.updateAccounts(ownedBy, notOwnedBy);
assertThat(updated).containsOnly(ownedBy);
}
@Autowired
lateinit var bankService: BankService
@WithMockUser(username="owner")
@Test
fun updateAccountsWhenOwnedThenReturns() {
val ownedBy: Account = ...
val notOwnedBy: Account = ...
val updated: Collection<Account> = bankService.updateAccounts(ownedBy, notOwnedBy)
assertThat(updated).containsOnly(ownedBy)
}
@PreFilter 也可以是元注释,在类或接口级别定义,并使用 SPEL 授权表达式。 |
@PreFilter
支持数组、集合、映射和流(只要流仍处于打开状态)。
例如,上面的updateAccounts
declaration 的功能与其他四个相同:
-
Java
-
Kotlin
@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Account[] accounts)
@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Collection<Account> accounts)
@PreFilter("filterObject.value.owner == authentication.name")
public Collection<Account> updateAccounts(Map<String, Account> accounts)
@PreFilter("filterObject.owner == authentication.name")
public Collection<Account> updateAccounts(Stream<Account> accounts)
@PreFilter("filterObject.owner == authentication.name")
fun updateAccounts(accounts: Array<Account>): Collection<Account>
@PreFilter("filterObject.owner == authentication.name")
fun updateAccounts(accounts: Collection<Account>): Collection<Account>
@PreFilter("filterObject.value.owner == authentication.name")
fun updateAccounts(accounts: Map<String, Account>): Collection<Account>
@PreFilter("filterObject.owner == authentication.name")
fun updateAccounts(accounts: Stream<Account>): Collection<Account>
结果是上述方法将只有Account
实例中,其owner
属性与已登录用户的name
.
过滤方法结果与@PostFilter
当 Method Security 处于活动状态时,您可以使用@PostFilter
注解,如下所示:
-
Java
-
Kotlin
@Component
public class BankService {
@PostFilter("filterObject.owner == authentication.name")
public Collection<Account> readAccounts(String... ids) {
// ... the return value will be filtered to only contain the accounts owned by the logged-in user
return accounts;
}
}
@Component
open class BankService {
@PostFilter("filterObject.owner == authentication.name")
fun readAccounts(vararg ids: String): Collection<Account> {
// ... the return value will be filtered to only contain the accounts owned by the logged-in user
return accounts
}
}
这是为了从返回值中过滤掉表达式filterObject.owner == authentication.name
失败。filterObject
表示每个account
在accounts
,用于测试每个account
.
然后,您可以像这样测试该类,以确认它正在强制执行授权规则:
-
Java
-
Kotlin
@Autowired
BankService bankService;
@WithMockUser(username="owner")
@Test
void readAccountsWhenOwnedThenReturns() {
Collection<Account> accounts = this.bankService.updateAccounts("owner", "not-owner");
assertThat(accounts).hasSize(1);
assertThat(accounts.get(0).getOwner()).isEqualTo("owner");
}
@Autowired
lateinit var bankService: BankService
@WithMockUser(username="owner")
@Test
fun readAccountsWhenOwnedThenReturns() {
val accounts: Collection<Account> = bankService.updateAccounts("owner", "not-owner")
assertThat(accounts).hasSize(1)
assertThat(accounts[0].owner).isEqualTo("owner")
}
@PostFilter 也可以是元注释,在类或接口级别定义,并使用 SPEL 授权表达式。 |
@PostFilter
支持数组、集合、映射和流(只要流仍处于打开状态)。
例如,上面的readAccounts
声明的功能与其他三种方法相同:
-
Java
-
Kotlin
@PostFilter("filterObject.owner == authentication.name")
public Collection<Account> readAccounts(String... ids)
@PostFilter("filterObject.owner == authentication.name")
public Account[] readAccounts(String... ids)
@PostFilter("filterObject.value.owner == authentication.name")
public Map<String, Account> readAccounts(String... ids)
@PostFilter("filterObject.owner == authentication.name")
public Stream<Account> readAccounts(String... ids)
@PostFilter("filterObject.owner == authentication.name")
fun readAccounts(vararg ids: String): Collection<Account>
@PostFilter("filterObject.owner == authentication.name")
fun readAccounts(vararg ids: String): Array<Account>
@PostFilter("filterObject.owner == authentication.name")
fun readAccounts(vararg ids: String): Map<String, Account>
@PostFilter("filterObject.owner == authentication.name")
fun readAccounts(vararg ids: String): Stream<Account>
结果是上述方法将返回Account
实例中,其owner
属性与已登录用户的name
.
内存筛选显然可能很昂贵,因此要考虑是否更适合筛选数据层中的数据。 |
授权方法调用@Secured
@Secured
是用于授权调用的旧选项。@PreAuthorize
取代它,而是推荐使用。
要使用@Secured
注解,您应该首先更改 Method Security 声明以启用它,如下所示:
-
Java
-
Kotlin
-
Xml
@EnableMethodSecurity(securedEnabled = true)
@EnableMethodSecurity(securedEnabled = true)
<sec:method-security secured-enabled="true"/>
这将导致 Spring Security 发布相应的方法拦截器,该拦截器授权带有 Comments 的方法、类和接口@Secured
.
使用 JSR-250 注释授权方法调用
如果您想使用 JSR-250 注释,Spring Security 也支持它。@PreAuthorize
具有更强的表现力,因此推荐使用。
要使用 JSR-250 注释,您应该首先更改 Method Security 声明以启用它们,如下所示:
-
Java
-
Kotlin
-
Xml
@EnableMethodSecurity(jsr250Enabled = true)
@EnableMethodSecurity(jsr250Enabled = true)
<sec:method-security jsr250-enabled="true"/>
这将导致 Spring Security 发布相应的方法拦截器,该拦截器授权带有 Comments 的方法、类和接口@RolesAllowed
,@PermitAll
和@DenyAll
.
在类或接口级别声明注释
还支持在类和接口级别具有 Method Security 注释。
如果它是在类级别,如下所示:
-
Java
-
Kotlin
@Controller
@PreAuthorize("hasAuthority('ROLE_USER')")
public class MyController {
@GetMapping("/endpoint")
public String endpoint() { ... }
}
@Controller
@PreAuthorize("hasAuthority('ROLE_USER')")
open class MyController {
@GetMapping("/endpoint")
fun endpoint(): String { ... }
}
然后,所有方法都继承类级行为。
或者,如果它在类和方法级别都声明如下:
-
Java
-
Kotlin
@Controller
@PreAuthorize("hasAuthority('ROLE_USER')")
public class MyController {
@GetMapping("/endpoint")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public String endpoint() { ... }
}
@Controller
@PreAuthorize("hasAuthority('ROLE_USER')")
open class MyController {
@GetMapping("/endpoint")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
fun endpoint(): String { ... }
}
然后,声明 Comments 的方法将覆盖类级 Comments。
接口也是如此,不同之处在于,如果一个类从两个不同的接口继承 Comments,则启动将失败。 这是因为 Spring Security 无法判断你想使用哪一个。
在这种情况下,您可以通过将 Annotation 添加到具体方法来解决歧义。
使用 Meta 注释
Method Security 支持元注释。 这意味着您可以采用任何注释,并根据特定于应用程序的用例提高可读性。
例如,您可以简化@PreAuthorize("hasRole('ADMIN')")
自@IsAdmin
这样:
-
Java
-
Kotlin
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ADMIN')")
public @interface IsAdmin {}
@Target(ElementType.METHOD, ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ADMIN')")
annotation class IsAdmin
结果是,您现在可以在安全方法上执行以下作:
-
Java
-
Kotlin
@Component
public class BankService {
@IsAdmin
public Account readAccount(Long id) {
// ... is only returned if the `Account` belongs to the logged in user
}
}
@Component
open class BankService {
@IsAdmin
fun readAccount(id: Long): Account {
// ... is only returned if the `Account` belongs to the logged in user
}
}
这将产生更具可读性的方法定义。
模板化元注释表达式
您还可以选择使用元注释模板,它允许使用更强大的注释定义。
首先,发布以下 Bean:
-
Java
-
Kotlin
@Bean
static AnnotationTemplateExpressionDefaults templateExpressionDefaults() {
return new AnnotationTemplateExpressionDefaults();
}
companion object {
@Bean
fun templateExpressionDefaults(): AnnotationTemplateExpressionDefaults {
return AnnotationTemplateExpressionDefaults()
}
}
Now 而不是@IsAdmin
,您可以创建更强大的内容,例如@HasRole
这样:
-
Java
-
Kotlin
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('{value}')")
public @interface HasRole {
String value();
}
@Target(ElementType.METHOD, ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('{value}')")
annotation class HasRole(val value: String)
结果是,您现在可以在安全方法上执行以下作:
-
Java
-
Kotlin
@Component
public class BankService {
@HasRole("ADMIN")
public Account readAccount(Long id) {
// ... is only returned if the `Account` belongs to the logged in user
}
}
@Component
open class BankService {
@HasRole("ADMIN")
fun readAccount(id: Long): Account {
// ... is only returned if the `Account` belongs to the logged in user
}
}
请注意,这也适用于方法变量和所有 Comments 类型,尽管您需要小心地正确使用引号,以便生成的 SPEL 表达式是正确的。
例如,请考虑以下@HasAnyRole
注解:
-
Java
-
Kotlin
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole({roles})")
public @interface HasAnyRole {
String[] roles();
}
@Target(ElementType.METHOD, ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole({roles})")
annotation class HasAnyRole(val roles: Array<String>)
在这种情况下,您会注意到您不应该在表达式中使用引号,而应该在参数值中使用引号,如下所示:
-
Java
-
Kotlin
@Component
public class BankService {
@HasAnyRole(roles = { "'USER'", "'ADMIN'" })
public Account readAccount(Long id) {
// ... is only returned if the `Account` belongs to the logged in user
}
}
@Component
open class BankService {
@HasAnyRole(roles = arrayOf("'USER'", "'ADMIN'"))
fun readAccount(id: Long): Account {
// ... is only returned if the `Account` belongs to the logged in user
}
}
因此,一旦被替换,表达式就会变成@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
.
启用某些注释
您可以关闭@EnableMethodSecurity
的预配置,并将其替换为您自己的配置。
如果您愿意,您可以选择这样做自定义AuthorizationManager
或Pointcut
.
或者您可能只想启用特定的注释,例如@PostAuthorize
.
您可以通过以下方式执行此作:
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postAuthorize() {
return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
}
}
@Configuration
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun postAuthorize() : Advisor {
return AuthorizationManagerAfterMethodInterceptor.postAuthorize()
}
}
<sec:method-security pre-post-enabled="false"/>
<aop:config/>
<bean id="postAuthorize"
class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
factory-method="postAuthorize"/>
上面的代码段通过首先禁用 Method Security 的预配置,然后发布这@PostAuthorize
拦截 器本身。
授权方式<intercept-methods>
虽然使用 Spring Security 的基于 Comments 的支持是方法安全性的首选,但您也可以使用 XML 来声明 Bean 授权规则。
如果需要在 XML 配置中声明它,可以使用<intercept-methods>
这样:
-
Xml
<bean class="org.mycompany.MyController">
<intercept-methods>
<protect method="get*" access="hasAuthority('read')"/>
<protect method="*" access="hasAuthority('write')"/>
</intercept-methods>
</bean>
这仅支持按前缀或名称匹配方法。 如果您的需求比这更复杂,请改用 annotation support。 |
以编程方式授权方法
如您所见,有几种方法可以使用方法安全性 SPEL 表达式指定重要的授权规则。
有多种方法可以让你的逻辑基于 Java 而不是基于 SPEL。 这为整个 Java 语言提供了 use 访问权限,以提高可测试性和流控制。
在 SPEL 中使用自定义 Bean
以编程方式授权方法的第一种方法是一个两步过程。
首先,声明一个 bean,该 bean 具有一个方法,该方法采用MethodSecurityExpressionOperations
实例,如下所示:
-
Java
-
Kotlin
@Component("authz")
public class AuthorizationLogic {
public boolean decide(MethodSecurityExpressionOperations operations) {
// ... authorization logic
}
}
@Component("authz")
open class AuthorizationLogic {
fun decide(operations: MethodSecurityExpressionOperations): boolean {
// ... authorization logic
}
}
然后,按以下方式在 Comments 中引用该 bean:
-
Java
-
Kotlin
@Controller
public class MyController {
@PreAuthorize("@authz.decide(#root)")
@GetMapping("/endpoint")
public String endpoint() {
// ...
}
}
@Controller
open class MyController {
@PreAuthorize("@authz.decide(#root)")
@GetMapping("/endpoint")
fun String endpoint() {
// ...
}
}
Spring Security 将为每个方法调用在该 bean 上调用给定的方法。
这样做的好处是,您的所有授权逻辑都位于一个单独的类中,该类可以独立进行单元测试和正确性验证。 它还可以访问完整的 Java 语言。
除了返回Boolean ,您还可以返回null 以指示 Code 放弃做出决策。 |
如果要包含有关决策性质的更多信息,可以改为返回自定义AuthorizationDecision
喜欢这个:
-
Java
-
Kotlin
@Component("authz")
public class AuthorizationLogic {
public AuthorizationDecision decide(MethodSecurityExpressionOperations operations) {
// ... authorization logic
return new MyAuthorizationDecision(false, details);
}
}
@Component("authz")
open class AuthorizationLogic {
fun decide(operations: MethodSecurityExpressionOperations): AuthorizationDecision {
// ... authorization logic
return MyAuthorizationDecision(false, details)
}
}
或者抛出自定义AuthorizationDeniedException
实例。
但请注意,返回对象是首选,因为这不会产生生成堆栈跟踪的费用。
然后,您可以在自定义授权结果的处理方式时访问自定义详细信息。
使用自定义授权管理器
以编程方式授权方法的第二种方法是创建自定义AuthorizationManager
.
首先,声明一个授权 Management 器实例,可能像这样:
-
Java
-
Kotlin
@Component
public class MyAuthorizationManager implements AuthorizationManager<MethodInvocation>, AuthorizationManager<MethodInvocationResult> {
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
// ... authorization logic
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult invocation) {
// ... authorization logic
}
}
@Component
class MyAuthorizationManager : AuthorizationManager<MethodInvocation>, AuthorizationManager<MethodInvocationResult> {
override fun check(authentication: Supplier<Authentication>, invocation: MethodInvocation): AuthorizationDecision {
// ... authorization logic
}
override fun check(authentication: Supplier<Authentication>, invocation: MethodInvocationResult): AuthorizationDecision {
// ... authorization logic
}
}
然后,使用与所需时间相对应的切入点发布方法 interceptorAuthorizationManager
运行。
例如,您可以将@PreAuthorize
和@PostAuthorize
像这样工作:
-
Java
-
Kotlin
-
Xml
@Configuration
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preAuthorize(MyAuthorizationManager manager) {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postAuthorize(MyAuthorizationManager manager) {
return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
}
}
@Configuration
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun preAuthorize(manager: MyAuthorizationManager) : Advisor {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager)
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun postAuthorize(manager: MyAuthorizationManager) : Advisor {
return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager)
}
}
<sec:method-security pre-post-enabled="false"/>
<aop:config/>
<bean id="preAuthorize"
class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
factory-method="preAuthorize">
<constructor-arg ref="myAuthorizationManager"/>
</bean>
<bean id="postAuthorize"
class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor"
factory-method="postAuthorize">
<constructor-arg ref="myAuthorizationManager"/>
</bean>
你可以使用 Order 常量中指定的 order 常量将拦截器放置在 Spring Security 方法拦截器之间 |
自定义表达式处理
或者,第三种,您可以自定义每个 SPEL 表达式的处理方式。
为此,您可以公开自定义MethodSecurityExpressionHandler
这样:
-
Java
-
Kotlin
-
Xml
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setRoleHierarchy(roleHierarchy);
return handler;
}
companion object {
@Bean
fun methodSecurityExpressionHandler(roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler {
val handler = DefaultMethodSecurityExpressionHandler()
handler.setRoleHierarchy(roleHierarchy)
return handler
}
}
<sec:method-security>
<sec:expression-handler ref="myExpressionHandler"/>
</sec:method-security>
<bean id="myExpressionHandler"
class="org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler">
<property name="roleHierarchy" ref="roleHierarchy"/>
</bean>
我们揭露 |
您还可以亚纲DefaultMessageSecurityExpressionHandler
以添加您自己的自定义授权表达式,而不是默认值。
使用 AOT
Spring Security 将扫描应用程序上下文中的所有 bean 以查找使用@PreAuthorize
或@PostAuthorize
.
找到一个 bean 后,它将解析安全表达式中使用的任何 bean,并为该 bean 注册适当的运行时提示。
如果它找到使用@AuthorizeReturnObject
,它将在方法的返回类型中递归搜索@PreAuthorize
和@PostAuthorize
注解并相应地注册它们。
例如,考虑以下 Spring Boot 应用程序:
-
Java
-
Kotlin
@Service
public class AccountService { (1)
@PreAuthorize("@authz.decide()") (2)
@AuthorizeReturnObject (3)
public Account getAccountById(String accountId) {
// ...
}
}
public class Account {
private final String accountNumber;
// ...
@PreAuthorize("@accountAuthz.canViewAccountNumber()") (4)
public String getAccountNumber() {
return this.accountNumber;
}
@AuthorizeReturnObject (5)
public User getUser() {
return new User("John Doe");
}
}
public class User {
private final String fullName;
// ...
@PostAuthorize("@myOtherAuthz.decide()") (6)
public String getFullName() {
return this.fullName;
}
}
@Service
class AccountService { (1)
@PreAuthorize("@authz.decide()") (2)
@AuthorizeReturnObject (3)
fun getAccountById(accountId: String): Account {
// ...
}
}
class Account(private val accountNumber: String) {
@PreAuthorize("@accountAuthz.canViewAccountNumber()") (4)
fun getAccountNumber(): String {
return this.accountNumber
}
@AuthorizeReturnObject (5)
fun getUser(): User {
return User("John Doe")
}
}
class User(private val fullName: String) {
@PostAuthorize("@myOtherAuthz.decide()") (6)
fun getFullName(): String {
return this.fullName
}
}
1 | Spring Security 会找到AccountService 豆 |
2 | 查找使用@PreAuthorize ,它将解析表达式authz 在这种情况下,并为 Bean 类注册运行时提示 |
3 | 查找使用@AuthorizeReturnObject ,它将查看方法的返回类型是否有任何@PreAuthorize 或@PostAuthorize |
4 | 然后,它会找到一个@PreAuthorize 替换为另一个 bean 名称:accountAuthz ;运行时提示也为 Bean 类注册 |
5 | 寻找另一个@AuthorizeReturnObject 它将再次查看方法的 return 类型 |
6 | 现在,一个@PostAuthorize 与使用的另一个 bean 名称一起找到:myOtherAuthz ;运行时提示也为 Bean 类注册 |
很多时候 Spring Security 无法提前确定方法的实际返回类型,因为它可能隐藏在擦除的泛型类型中。
请考虑以下服务:
-
Java
-
Kotlin
@Service
public class AccountService {
@AuthorizeReturnObject
public List<Account> getAllAccounts() {
// ...
}
}
@Service
class AccountService {
@AuthorizeReturnObject
fun getAllAccounts(): List<Account> {
// ...
}
}
在这种情况下,泛型类型被擦除,因此 Spring Security 事先并不明显Account
需要访问以检查@PreAuthorize
和@PostAuthorize
.
要解决此问题,您可以发布PrePostAuthorizeExpressionBeanHintsRegistrar
这样:
-
Java
-
Kotlin
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static SecurityHintsRegistrar registerTheseToo() {
return new PrePostAuthorizeExpressionBeanHintsRegistrar(Account.class);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun registerTheseToo(): SecurityHintsRegistrar {
return PrePostAuthorizeExpressionBeanHintsRegistrar(Account::class.java)
}
使用 AspectJ 进行授权
使用自定义切入点匹配方法
由于 Spring AOP 构建,您可以声明与 Comments 无关的模式,类似于请求级授权。 这具有集中方法级授权规则的潜在优势。
例如,您可以使用 publish your ownAdvisor
或使用<protect-pointcut>
将 AOP 表达式与服务层的授权规则匹配,如下所示:
-
Java
-
Kotlin
-
Xml
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor protectServicePointcut() {
AspectJExpressionPointcut pattern = new AspectJExpressionPointcut()
pattern.setExpression("execution(* com.mycompany.*Service.*(..))")
return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"))
}
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole
companion object {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun protectServicePointcut(): Advisor {
val pattern = AspectJExpressionPointcut()
pattern.setExpression("execution(* com.mycompany.*Service.*(..))")
return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"))
}
}
<sec:method-security>
<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))" access="hasRole('USER')"/>
</sec:method-security>
Integrate with AspectJ Byte-weaving
Performance can at times be enhanced by using AspectJ to weave Spring Security advice into the byte code of your beans.
After setting up AspectJ, you can quite simply state in the @EnableMethodSecurity
annotation or <method-security>
element that you are using AspectJ:
-
Java
-
Kotlin
-
Xml
@EnableMethodSecurity(mode=AdviceMode.ASPECTJ)
@EnableMethodSecurity(mode=AdviceMode.ASPECTJ)
<sec:method-security mode="aspectj"/>
And the result will be that Spring Security will publish its advisors as AspectJ advice so that they can be woven in accordingly.
Specifying Order
As already noted, there is a Spring AOP method interceptor for each annotation, and each of these has a location in the Spring AOP advisor chain.
Namely, the @PreFilter
method interceptor’s order is 100, @PreAuthorize
's is 200, and so on.
The reason this is important to note is that there are other AOP-based annotations like @EnableTransactionManagement
that have an order of Integer.MAX_VALUE
.
In other words, they are located at the end of the advisor chain by default.
At times, it can be valuable to have other advice execute before Spring Security.
For example, if you have a method annotated with @Transactional
and @PostAuthorize
, you might want the transaction to still be open when @PostAuthorize
runs so that an AccessDeniedException
will cause a rollback.
To get @EnableTransactionManagement
to open a transaction before method authorization advice runs, you can set @EnableTransactionManagement
's order like so:
-
Java
-
Kotlin
-
Xml
@EnableTransactionManagement(order = 0)
@EnableTransactionManagement(order = 0)
<tx:annotation-driven ref="txManager" order="0"/>
Since the earliest method interceptor (@PreFilter
) is set to an order of 100, a setting of zero means that the transaction advice will run before all Spring Security advice.
Expressing Authorization with SpEL
You’ve already seen several examples using SpEL, so now let’s cover the API a bit more in depth.
Spring Security encapsulates all of its authorization fields and methods in a set of root objects.
The most generic root object is called SecurityExpressionRoot
and it forms the basis for MethodSecurityExpressionRoot
.
Spring Security supplies this root object to MethodSecurityEvaluationContext
when preparing to evaluate an authorization expression.
Using Authorization Expression Fields and Methods
The first thing this provides is an enhanced set of authorization fields and methods to your SpEL expressions.
What follows is a quick overview of the most common methods:
-
permitAll
- The method requires no authorization to be invoked; note that in this case, the Authentication
is never retrieved from the session
-
denyAll
- The method is not allowed under any circumstances; note that in this case, the Authentication
is never retrieved from the session
-
hasAuthority
- The method requires that the Authentication
have a GrantedAuthority
that matches the given value
-
hasRole
- A shortcut for hasAuthority
that prefixes ROLE_
or whatever is configured as the default prefix
-
hasAnyAuthority
- The method requires that the Authentication
have a GrantedAuthority
that matches any of the given values
-
hasAnyRole
- A shortcut for hasAnyAuthority
that prefixes ROLE_
or whatever is configured as the default prefix
-
hasPermission
- A hook into your PermissionEvaluator
instance for doing object-level authorization
And here is a brief look at the most common fields:
-
authentication
- The Authentication
instance associated with this method invocation
-
principal
- The Authentication#getPrincipal
associated with this method invocation
Having now learned the patterns, rules, and how they can be paired together, you should be able to understand what is going on in this more complex example:
Authorize Requests
-
Java
-
Kotlin
-
Xml
@Component
public class MyService {
@PreAuthorize("denyAll") (1)
MyResource myDeprecatedMethod(...);
@PreAuthorize("hasRole('ADMIN')") (2)
MyResource writeResource(...)
@PreAuthorize("hasAuthority('db') and hasRole('ADMIN')") (3)
MyResource deleteResource(...)
@PreAuthorize("principal.claims['aud'] == 'my-audience'") (4)
MyResource readResource(...);
@PreAuthorize("@authz.check(authentication, #root)")
MyResource shareResource(...);
}
@Component
open class MyService {
@PreAuthorize("denyAll") (1)
fun myDeprecatedMethod(...): MyResource
@PreAuthorize("hasRole('ADMIN')") (2)
fun writeResource(...): MyResource
@PreAuthorize("hasAuthority('db') and hasRole('ADMIN')") (3)
fun deleteResource(...): MyResource
@PreAuthorize("principal.claims['aud'] == 'my-audience'") (4)
fun readResource(...): MyResource
@PreAuthorize("@authz.check(#root)")
fun shareResource(...): MyResource
}
<sec:method-security>
<protect-pointcut expression="execution(* com.mycompany.*Service.myDeprecatedMethod(..))" access="denyAll"/> (1)
<protect-pointcut expression="execution(* com.mycompany.*Service.writeResource(..))" access="hasRole('ADMIN')"/> (2)
<protect-pointcut expression="execution(* com.mycompany.*Service.deleteResource(..))" access="hasAuthority('db') and hasRole('ADMIN')"/> (3)
<protect-pointcut expression="execution(* com.mycompany.*Service.readResource(..))" access="principal.claims['aud'] == 'my-audience'"/> (4)
<protect-pointcut expression="execution(* com.mycompany.*Service.shareResource(..))" access="@authz.check(#root)"/> (5)
</sec:method-security>
1
This method may not be invoked by anyone for any reason
2
This method may only be invoked by Authentication
s granted the ROLE_ADMIN
authority
3
This method may only be invoked by Authentication
s granted the db
and ROLE_ADMIN
authorities
4
This method may only be invoked by Princpal
s with an aud
claim equal to "my-audience"
5
This method may only be invoked if the bean authz
's check
method returns true
You can use a bean like authz
above to add programmatic authorization.
Using Method Parameters
Additionally, Spring Security provides a mechanism for discovering method parameters so they can also be accessed in the SpEL expression as well.
For a complete reference, Spring Security uses DefaultSecurityParameterNameDiscoverer
to discover the parameter names.
By default, the following options are tried for a method.
-
If Spring Security’s @P
annotation is present on a single argument to the method, the value is used.
The following example uses the @P
annotation:
-
Java
-
Kotlin
import org.springframework.security.access.method.P;
...
@PreAuthorize("hasPermission(#c, 'write')")
public void updateContact(@P("c") Contact contact);
import org.springframework.security.access.method.P
...
@PreAuthorize("hasPermission(#c, 'write')")
fun doSomething(@P("c") contact: Contact?)
The intention of this expression is to require that the current Authentication
have write
permission specifically for this Contact
instance.
Behind the scenes, this is implemented by using AnnotationParameterNameDiscoverer
, which you can customize to support the value attribute of any specified annotation.
-
If Spring Data’s @Param
annotation is present on at least one parameter for the method, the value is used.
The following example uses the @Param
annotation:
-
Java
-
Kotlin
import org.springframework.data.repository.query.Param;
...
@PreAuthorize("#n == authentication.name")
Contact findContactByName(@Param("n") String name);
import org.springframework.data.repository.query.Param
...
@PreAuthorize("#n == authentication.name")
fun findContactByName(@Param("n") name: String?): Contact?
The intention of this expression is to require that name
be equal to Authentication#getName
for the invocation to be authorized.
Behind the scenes, this is implemented by using AnnotationParameterNameDiscoverer
, which you can customize to support the value attribute of any specified annotation.
-
If you compile your code with the -parameters
argument, the standard JDK reflection API is used to discover the parameter names.
This works on both classes and interfaces.
-
Finally, if you compile your code with debug symbols, the parameter names are discovered by using the debug symbols.
This does not work for interfaces, since they do not have debug information about the parameter names.
For interfaces, either annotations or the -parameters
approach must be used.
Authorizing Arbitrary Objects
Spring Security also supports wrapping any object that is annotated its method security annotations.
The simplest way to achieve this is to mark any method that returns the object you wish to authorize with the @AuthorizeReturnObject
annotation.
For example, consider the following User
class:
-
Java
-
Kotlin
public class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return this.name;
}
@PreAuthorize("hasAuthority('user:read')")
public String getEmail() {
return this.email;
}
}
class User (val name:String, @get:PreAuthorize("hasAuthority('user:read')") val email:String)
Given an interface like this one:
-
Java
-
Kotlin
public class UserRepository {
@AuthorizeReturnObject
Optional<User> findByName(String name) {
// ...
}
}
class UserRepository {
@AuthorizeReturnObject
fun findByName(name:String?): Optional<User?>? {
// ...
}
}
Then any User
that is returned from findById
will be secured like other Spring Security-protected components:
-
Java
-
Kotlin
@Autowired
UserRepository users;
@Test
void getEmailWhenProxiedThenAuthorizes() {
Optional<User> securedUser = users.findByName("name");
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> securedUser.get().getEmail());
}
import jdk.incubator.vector.VectorOperators.Test
import java.nio.file.AccessDeniedException
import java.util.*
@Autowired
var users:UserRepository? = null
@Test
fun getEmailWhenProxiedThenAuthorizes() {
val securedUser: Optional<User> = users.findByName("name")
assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy{securedUser.get().getEmail()}
}
Using @AuthorizeReturnObject
at the class level
@AuthorizeReturnObject
can be placed at the class level. Note, though, that this means Spring Security will attempt to proxy any return object, including String
, Integer
and other types.
This is often not what you want to do.
If you want to use @AuthorizeReturnObject
on a class or interface whose methods return value types, like int
, String
, Double
or collections of those types, then you should also publish the appropriate AuthorizationAdvisorProxyFactory.TargetVisitor
as follows:
-
Java
-
Kotlin
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
// ...
@Bean
static TargetVisitor skipValueTypes() {
return TargetVisitor.defaultsSkipValueTypes();
}
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor
// ...
@Bean
open fun skipValueTypes() = TargetVisitor.defaultsSkipValueTypes()
You can set your own AuthorizationAdvisorProxyFactory.TargetVisitor
to customize the proxying for any set of types
Programmatically Proxying
You can also programmatically proxy a given object.
To achieve this, you can autowire the provided AuthorizationProxyFactory
instance, which is based on which method security interceptors you have configured.
If you are using @EnableMethodSecurity
, then this means that it will by default have the interceptors for @PreAuthorize
, @PostAuthorize
, @PreFilter
, and @PostFilter
.
You can proxy an instance of user in the following way:
-
Java
-
Kotlin
@Autowired
AuthorizationProxyFactory proxyFactory;
@Test
void getEmailWhenProxiedThenAuthorizes() {
User user = new User("name", "email");
assertThat(user.getEmail()).isNotNull();
User securedUser = proxyFactory.proxy(user);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail);
}
@Autowired
var proxyFactory:AuthorizationProxyFactory? = null
@Test
fun getEmailWhenProxiedThenAuthorizes() {
val user: User = User("name", "email")
assertThat(user.getEmail()).isNotNull()
val securedUser: User = proxyFactory.proxy(user)
assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy(securedUser::getEmail)
}
Manual Construction
You can also define your own instance if you need something different from the Spring Security default.
For example, if you define an AuthorizationProxyFactory
instance like so:
-
Java
-
Kotlin
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
import static org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize;
// ...
AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
// and if needing to skip value types
proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize
// ...
val proxyFactory: AuthorizationProxyFactory = AuthorizationProxyFactory(preAuthorize())
// and if needing to skip value types
proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes())
Then you can wrap any instance of User
as follows:
-
Java
-
Kotlin
@Test
void getEmailWhenProxiedThenAuthorizes() {
AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
User user = new User("name", "email");
assertThat(user.getEmail()).isNotNull();
User securedUser = proxyFactory.proxy(user);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail);
}
@Test
fun getEmailWhenProxiedThenAuthorizes() {
val proxyFactory: AuthorizationProxyFactory = AuthorizationAdvisorProxyFactory.withDefaults()
val user: User = User("name", "email")
assertThat(user.getEmail()).isNotNull()
val securedUser: User = proxyFactory.proxy(user)
assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy(securedUser::getEmail)
}
Proxying Collections
AuthorizationProxyFactory
supports Java collections, streams, arrays, optionals, and iterators by proxying the element type and maps by proxying the value type.
This means that when proxying a List
of objects, the following also works:
-
Java
@Test
void getEmailWhenProxiedThenAuthorizes() {
AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
List<User> users = List.of(ada, albert, marie);
List<User> securedUsers = proxyFactory.proxy(users);
securedUsers.forEach((securedUser) ->
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(securedUser::getEmail));
}
Proxying Classes
In limited circumstances, it may be valuable to proxy a Class
itself, and AuthorizationProxyFactory
also supports this.
This is roughly the equivalent of calling ProxyFactory#getProxyClass
in Spring Framework’s support for creating proxies.
One place where this is handy is when you need to construct the proxy class ahead-of-time, like with Spring AOT.
Support for All Method Security Annotations
AuthorizationProxyFactory
supports whichever method security annotations are enabled in your application.
It is based off of whatever AuthorizationAdvisor
classes are published as a bean.
Since @EnableMethodSecurity
publishes @PreAuthorize
, @PostAuthorize
, @PreFilter
, and @PostFilter
advisors by default, you will typically need to do nothing to activate the ability.
SpEL expressions that use returnObject
or filterObject
sit behind the proxy and so have full access to the object.
Custom Advice
If you have security advice that you also want applied, you can publish your own AuthorizationAdvisor
like so:
-
Java
-
Kotlin
@EnableMethodSecurity
class SecurityConfig {
@Bean
static AuthorizationAdvisor myAuthorizationAdvisor() {
return new AuthorizationAdvisor();
}
}
@EnableMethodSecurity
internal class SecurityConfig {
@Bean
fun myAuthorizationAdvisor(): AuthorizationAdvisor {
return AuthorizationAdvisor()
}
]
And Spring Security will add that advisor into the set of advice that AuthorizationProxyFactory
adds when proxying an object.
Working with Jackson
One powerful use of this feature is to return a secured value from a controller like so:
-
Java
-
Kotlin
@RestController
public class UserController {
@Autowired
AuthorizationProxyFactory proxyFactory;
@GetMapping
User currentUser(@AuthenticationPrincipal User user) {
return this.proxyFactory.proxy(user);
}
}
@RestController
class UserController {
@Autowired
var proxyFactory: AuthorizationProxyFactory? = null
@GetMapping
fun currentUser(@AuthenticationPrincipal user:User?): User {
return proxyFactory.proxy(user)
}
}
You will need to add a MethodAuthorizationDeniedHandler
like this one:
-
Java
-
Kotlin
@Component
public class Null implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return null;
}
}
// ...
@HandleAuthorizationDenied(handlerClass = Null.class)
public class User {
...
}
@Component
class Null : MethodAuthorizationDeniedHandler {
override fun handleDeniedInvocation(methodInvocation: MethodInvocation?, authorizationResult: AuthorizationResult?): Any? {
return null
}
}
// ...
@HandleAuthorizationDenied(handlerClass = Null.class)
open class User {
...
}
Then, you’ll see a different JSON serialization based on the authorization level of the user.
If they don’t have the user:read
authority, then they’ll see:
{
"name" : "name",
"email" : null
}
And if they do have that authority, they’ll see:
{
"name" : "name",
"email" : "email"
}
You can also add the Spring Boot property spring.jackson.default-property-inclusion=non_null
to exclude the null value from serialization, if you also don’t want to reveal the JSON key to an unauthorized user.
Working with AOT
Spring Security will scan all beans in the application context for methods that use @AuthorizeReturnObject
.
When it finds one, it will create and register the appropriate proxy class ahead of time.
It will also recursively search for other nested objects that also use @AuthorizeReturnObject
and register them accordingly.
For example, consider the following Spring Boot application:
-
Java
-
Kotlin
@SpringBootApplication
public class MyApplication {
@RestController
public static class MyController { (1)
@GetMapping
@AuthorizeReturnObject
Message getMessage() { (2)
return new Message(someUser, "hello!");
}
}
public static class Message { (3)
User to;
String text;
// ...
@AuthorizeReturnObject
public User getTo() { (4)
return this.to;
}
// ...
}
public static class User { (5)
// ...
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class);
}
}
@SpringBootApplication
open class MyApplication {
@RestController
open class MyController { (1)
@GetMapping
@AuthorizeReturnObject
fun getMessage():Message { (2)
return Message(someUser, "hello!")
}
}
open class Message { (3)
val to: User
val test: String
// ...
@AuthorizeReturnObject
fun getTo(): User { (4)
return this.to
}
// ...
}
open class User { (5)
// ...
}
fun main(args: Array<String>) {
SpringApplication.run(MyApplication.class)
}
}
1
- First, Spring Security finds the MyController
bean
2
- Finding a method that uses @AuthorizeReturnObject
, it proxies Message
, the return value, and registers that proxy class to RuntimeHints
3
- Then, it traverses Message
to see if it uses @AuthorizeReturnObject
4
- Finding a method that uses @AuthorizeReturnObject
, it proxies User
, the return value, and registers that proxy class to RuntimeHints
5
- Finally, it traverses User
to see if it uses @AuthorizeReturnObject
; finding nothing, the algorithm completes
There will be many times when Spring Security cannot determine the proxy class ahead of time since it may be hidden in an erased generic type.
Consider the following change to MyController
:
-
Java
-
Kotlin
@RestController
public static class MyController {
@GetMapping
@AuthorizeReturnObject
List<Message> getMessages() {
return List.of(new Message(someUser, "hello!"));
}
}
@RestController
static class MyController {
@AuthorizeReturnObject
@GetMapping
fun getMessages(): Array<Message> = arrayOf(Message(someUser, "hello!"))
}
In this case, the generic type is erased and so it isn’t apparent to Spring Security ahead-of-time that Message
will need to be proxied at runtime.
To address this, you can publish AuthorizeProxyFactoryHintsRegistrar
like so:
-
Java
-
Kotlin
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static SecurityHintsRegsitrar registerTheseToo(AuthorizationProxyFactory proxyFactory) {
return new AuthorizeReturnObjectHintsRegistrar(proxyFactory, Message.class);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun registerTheseToo(proxyFactory: AuthorizationProxyFactory?): SecurityHintsRegistrar {
return AuthorizeReturnObjectHintsRegistrar(proxyFactory, Message::class.java)
}
Spring Security will register that class and then traverse its type as before.
Providing Fallback Values When Authorization is Denied
There are some scenarios where you may not wish to throw an AuthorizationDeniedException
when a method is invoked without the required permissions.
Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases where authorization denied happened before invoking the method.
Spring Security provides support for handling authorization denied on method invocation by using the @HandleAuthorizationDenied
.
The handler works for denied authorizations that happened in the @PreAuthorize
and @PostAuthorize
annotations as well as AuthorizationDeniedException
thrown from the method invocation itself.
Let’s consider the example from the previous section, but instead of creating the AccessDeniedExceptionInterceptor
to transform an AccessDeniedException
to a null
return value, we will use the handlerClass
attribute from @HandleAuthorizationDenied
:
-
Java
-
Kotlin
public class NullMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { (1)
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return null;
}
}
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean (2)
public NullMethodAuthorizationDeniedHandler nullMethodAuthorizationDeniedHandler() {
return new NullMethodAuthorizationDeniedHandler();
}
}
public class User {
// ...
@PreAuthorize(value = "hasAuthority('user:read')")
@HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler.class)
public String getEmail() {
return this.email;
}
}
class NullMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler { (1)
override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
return null
}
}
@Configuration
@EnableMethodSecurity
class SecurityConfig {
@Bean (2)
fun nullMethodAuthorizationDeniedHandler(): NullMethodAuthorizationDeniedHandler {
return MaskMethodAuthorizationDeniedHandler()
}
}
class User (val name:String, @PreAuthorize(value = "hasAuthority('user:read')") @HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler::class) val email:String) (3)
1
Create an implementation of MethodAuthorizationDeniedHandler
that returns a null
value
2
Register the NullMethodAuthorizationDeniedHandler
as a bean
3
Annotate the method with @HandleAuthorizationDenied
and pass the NullMethodAuthorizationDeniedHandler
to the handlerClass
attribute
And then you can verify that a null
value is returned instead of the AccessDeniedException
:
You can also annotate your class with @Component
instead of creating a @Bean
method
-
Java
-
Kotlin
@Autowired
UserRepository users;
@Test
void getEmailWhenProxiedThenNullEmail() {
Optional<User> securedUser = users.findByName("name");
assertThat(securedUser.get().getEmail()).isNull();
}
@Autowired
var users:UserRepository? = null
@Test
fun getEmailWhenProxiedThenNullEmail() {
val securedUser: Optional<User> = users.findByName("name")
assertThat(securedUser.get().getEmail()).isNull()
}
Using the Denied Result From the Method Invocation
There are some scenarios where you might want to return a secure result derived from the denied result.
For example, if a user is not authorized to see email addresses, you might want to apply some masking on the original email address, i.e. [email protected] would become use******@example.com.
For those scenarios, you can override the handleDeniedInvocationResult
from the MethodAuthorizationDeniedHandler
, which has the MethodInvocationResult
as an argument.
Let’s continue with the previous example, but instead of returning null
, we will return a masked value of the email:
-
Java
-
Kotlin
public class EmailMaskingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { (1)
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return "***";
}
@Override
public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) {
String email = (String) methodInvocationResult.getResult();
return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*");
}
}
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean (2)
public EmailMaskingMethodAuthorizationDeniedHandler emailMaskingMethodAuthorizationDeniedHandler() {
return new EmailMaskingMethodAuthorizationDeniedHandler();
}
}
public class User {
// ...
@PostAuthorize(value = "hasAuthority('user:read')")
@HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler.class)
public String getEmail() {
return this.email;
}
}
class EmailMaskingMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler {
override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
return "***"
}
override fun handleDeniedInvocationResult(methodInvocationResult: MethodInvocationResult, authorizationResult: AuthorizationResult): Any {
val email = methodInvocationResult.result as String
return email.replace("(^[^@]{3}|(?!^)\\G)[^@]".toRegex(), "$1*")
}
}
@Configuration
@EnableMethodSecurity
class SecurityConfig {
@Bean
fun emailMaskingMethodAuthorizationDeniedHandler(): EmailMaskingMethodAuthorizationDeniedHandler {
return EmailMaskingMethodAuthorizationDeniedHandler()
}
}
class User (val name:String, @PostAuthorize(value = "hasAuthority('user:read')") @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler::class) val email:String) (3)
1
Create an implementation of MethodAuthorizationDeniedHandler
that returns a masked value of the unauthorized result value
2
Register the EmailMaskingMethodAuthorizationDeniedHandler
as a bean
3
Annotate the method with @HandleAuthorizationDenied
and pass the EmailMaskingMethodAuthorizationDeniedHandler
to the handlerClass
attribute
And then you can verify that a masked email is returned instead of an AccessDeniedException
:
Since you have access to the original denied value, make sure that you correctly handle it and do not return it to the caller.
-
Java
-
Kotlin
@Autowired
UserRepository users;
@Test
void getEmailWhenProxiedThenMaskedEmail() {
Optional<User> securedUser = users.findByName("name");
// email is [email protected]
assertThat(securedUser.get().getEmail()).isEqualTo("use******@example.com");
}
@Autowired
var users:UserRepository? = null
@Test
fun getEmailWhenProxiedThenMaskedEmail() {
val securedUser: Optional<User> = users.findByName("name")
// email is [email protected]
assertThat(securedUser.get().getEmail()).isEqualTo("use******@example.com")
}
When implementing the MethodAuthorizationDeniedHandler
you have a few options on what type you can return:
-
A null
value.
-
A non-null value, respecting the method’s return type.
-
Throw an exception, usually an instance of AuthorizationDeniedException
. This is the default behavior.
-
A Mono
type for reactive applications.
Note that since the handler must be registered as beans in your application context, you can inject dependencies into them if you need a more complex logic.
In addition to that, you have available the MethodInvocation
or the MethodInvocationResult
, as well as the AuthorizationResult
for more details related to the authorization decision.
Deciding What to Return Based on Available Parameters
Consider a scenario where there might be multiple mask values for different methods, it would be not so productive if we had to create a handler for each of those methods, although it is perfectly fine to do that.
In such cases, we can use the information passed via parameters to decide what to do.
For example, we can create a custom @Mask
annotation and a handler that detects that annotation to decide what mask value to return:
-
Java
-
Kotlin
import org.springframework.core.annotation.AnnotationUtils;
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Mask {
String value();
}
public class MaskAnnotationDeniedHandler implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class);
return mask.value();
}
}
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public MaskAnnotationDeniedHandler maskAnnotationDeniedHandler() {
return new MaskAnnotationDeniedHandler();
}
}
@Component
public class MyService {
@PreAuthorize(value = "hasAuthority('user:read')")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
@Mask("***")
public String foo() {
return "foo";
}
@PreAuthorize(value = "hasAuthority('user:read')")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
@Mask("???")
public String bar() {
return "bar";
}
}
import org.springframework.core.annotation.AnnotationUtils
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Mask(val value: String)
class MaskAnnotationDeniedHandler : MethodAuthorizationDeniedHandler {
override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any {
val mask = AnnotationUtils.getAnnotation(methodInvocation.method, Mask::class.java)
return mask.value
}
}
@Configuration
@EnableMethodSecurity
class SecurityConfig {
@Bean
fun maskAnnotationDeniedHandler(): MaskAnnotationDeniedHandler {
return MaskAnnotationDeniedHandler()
}
}
@Component
class MyService {
@PreAuthorize(value = "hasAuthority('user:read')")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
@Mask("***")
fun foo(): String {
return "foo"
}
@PreAuthorize(value = "hasAuthority('user:read')")
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
@Mask("???")
fun bar(): String {
return "bar"
}
}
Now the return values when access is denied will be decided based on the @Mask
annotation:
-
Java
-
Kotlin
@Autowired
MyService myService;
@Test
void fooWhenDeniedThenReturnStars() {
String value = this.myService.foo();
assertThat(value).isEqualTo("***");
}
@Test
void barWhenDeniedThenReturnQuestionMarks() {
String value = this.myService.foo();
assertThat(value).isEqualTo("???");
}
@Autowired
var myService: MyService
@Test
fun fooWhenDeniedThenReturnStars() {
val value: String = myService.foo()
assertThat(value).isEqualTo("***")
}
@Test
fun barWhenDeniedThenReturnQuestionMarks() {
val value: String = myService.foo()
assertThat(value).isEqualTo("???")
}
Combining with Meta Annotation Support
You can also combine the @HandleAuthorizationDenied
with other annotations in order to reduce and simplify the annotations in a method.
Let’s consider the example from the previous section and merge @HandleAuthorizationDenied
with @Mask
:
-
Java
-
Kotlin
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class)
public @interface Mask {
String value();
}
@Mask("***")
public String myMethod() {
// ...
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class)
annotation class Mask(val value: String)
@Mask("***")
fun myMethod(): String {
// ...
}
Now you do not have to remember to add both annotations when you need a mask behavior in your method.
Make sure to read the Meta Annotations Support section for more details on the usage.
Migrating from @EnableGlobalMethodSecurity
If you are using @EnableGlobalMethodSecurity
, you should migrate to @EnableMethodSecurity
.
Replace global method security with method security
@EnableGlobalMethodSecurity
and <global-method-security>
are deprecated in favor of @EnableMethodSecurity
and <method-security>
, respectively.
The new annotation and XML element activate Spring’s pre-post annotations by default and use AuthorizationManager
internally.
This means that the following two listings are functionally equivalent:
-
Java
-
Kotlin
-
Xml
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
<global-method-security pre-post-enabled="true"/>
and:
-
Java
-
Kotlin
-
Xml
@EnableMethodSecurity
@EnableMethodSecurity
<method-security/>
For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior.
For example, a listing like:
-
Java
-
Kotlin
-
Xml
@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableGlobalMethodSecurity(securedEnabled = true)
<global-method-security secured-enabled="true"/>
should change to:
-
Java
-
Kotlin
-
Xml
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
<method-security secured-enabled="true" pre-post-enabled="false"/>
Use a Custom @Bean
instead of subclassing DefaultMethodSecurityExpressionHandler
As a performance optimization, a new method was introduced to MethodSecurityExpressionHandler
that takes a Supplier<Authentication>
instead of an Authentication
.
This allows Spring Security to defer the lookup of the Authentication
, and is taken advantage of automatically when you use @EnableMethodSecurity
instead of @EnableGlobalMethodSecurity
.
However, let’s say that your code extends DefaultMethodSecurityExpressionHandler
and overrides createSecurityExpressionRoot(Authentication, MethodInvocation)
to return a custom SecurityExpressionRoot
instance.
This will no longer work because the arrangement that @EnableMethodSecurity
sets up calls createEvaluationContext(Supplier<Authentication>, MethodInvocation)
instead.
Happily, such a level of customization is often unnecessary.
Instead, you can create a custom bean with the authorization methods that you need.
For example, let’s say you are wanting a custom evaluation of @PostAuthorize("hasAuthority('ADMIN')")
.
You can create a custom @Bean
like this one:
-
Java
-
Kotlin
class MyAuthorizer {
boolean isAdmin(MethodSecurityExpressionOperations root) {
boolean decision = root.hasAuthority("ADMIN");
// custom work ...
return decision;
}
}
class MyAuthorizer {
fun isAdmin(root: MethodSecurityExpressionOperations): boolean {
val decision = root.hasAuthority("ADMIN");
// custom work ...
return decision;
}
}
and then refer to it in the annotation like so:
-
Java
-
Kotlin
@PreAuthorize("@authz.isAdmin(#root)")
@PreAuthorize("@authz.isAdmin(#root)")
I’d still prefer to subclass DefaultMethodSecurityExpressionHandler
If you must continue subclassing DefaultMethodSecurityExpressionHandler
, you can still do so.
Instead, override the createEvaluationContext(Supplier<Authentication>, MethodInvocation)
method like so:
-
Java
-
Kotlin
@Component
class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
@Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
MethodSecurityExpressionOperations delegate = (MethodSecurityExpressionOperations) context.getRootObject().getValue();
MySecurityExpressionRoot root = new MySecurityExpressionRoot(delegate);
context.setRootObject(root);
return context;
}
}
@Component
class MyExpressionHandler: DefaultMethodSecurityExpressionHandler {
override fun createEvaluationContext(authentication: Supplier<Authentication>,
val mi: MethodInvocation): EvaluationContext {
val context = super.createEvaluationContext(authentication, mi) as StandardEvaluationContext
val delegate = context.getRootObject().getValue() as MethodSecurityExpressionOperations
val root = MySecurityExpressionRoot(delegate)
context.setRootObject(root)
return context
}
}
Further Reading
Now that you have secured your application’s requests, please secure its requests if you haven’t already.
You can also read further on testing your application or on integrating Spring Security with other aspects of you application like the data layer or tracing and metrics.