阅读spring security基于方法的权限认证
This commit is contained in:
@@ -67,6 +67,18 @@
|
|||||||
- [使用path变量](#使用path变量)
|
- [使用path变量](#使用path变量)
|
||||||
- [使用数据库来进行权限认证](#使用数据库来进行权限认证)
|
- [使用数据库来进行权限认证](#使用数据库来进行权限认证)
|
||||||
- [SecurityMatcher](#securitymatcher)
|
- [SecurityMatcher](#securitymatcher)
|
||||||
|
- [基于方法的权限认证](#基于方法的权限认证)
|
||||||
|
- [Method Security的工作原理](#method-security的工作原理)
|
||||||
|
- [多个注解组合](#多个注解组合)
|
||||||
|
- [不支持重复注解](#不支持重复注解)
|
||||||
|
- [支持通过向角色授权来替换复杂的spel表达式](#支持通过向角色授权来替换复杂的spel表达式)
|
||||||
|
- [通过注解来执行权限校验](#通过注解来执行权限校验)
|
||||||
|
- [@PreAuthorize](#preauthorize)
|
||||||
|
- [@PostAuthorize](#postauthorize)
|
||||||
|
- [@PreFilter](#prefilter)
|
||||||
|
- [@PostFilter](#postfilter)
|
||||||
|
- [在接口或类级别声明注解](#在接口或类级别声明注解)
|
||||||
|
- [在方法spel中指定自定义的bean调用](#在方法spel中指定自定义的bean调用)
|
||||||
|
|
||||||
|
|
||||||
# Spring Security
|
# Spring Security
|
||||||
@@ -1151,3 +1163,201 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 基于方法的权限认证
|
||||||
|
除了基于http请求的权限认证外,spring security还支持基于方法的权限认证。可以通过在@Configuration类上指定`@EnableMethodSecurity`来启用基于方法的权限认证。
|
||||||
|
|
||||||
|
在通过注解开启基于方法的权限认证之后,可以在spring管理的类或方法上添加`@PreAuthorize, @PostAuthorize, @PreFilter, 和@PostFilter`等注解来对方法调用进行权限认证,包括对其参数和方法返回值。
|
||||||
|
|
||||||
|
> 默认情况下,spring security并不会开启基于方法的权限认证。
|
||||||
|
|
||||||
|
#### Method Security的工作原理
|
||||||
|
spring security基于方法的权限认证对于如下场景非常方便:
|
||||||
|
- 细粒度的授权逻辑,例如方法调用的参数或返回值对权限认证逻辑有影响
|
||||||
|
- 在service层执行权限认证逻辑
|
||||||
|
|
||||||
|
Spring Security基于方法的权限认证是通过spring aop实现的。
|
||||||
|
|
||||||
|
基于方法的授权,其可分为两部分:
|
||||||
|
- before:目标方法执行前的权限认证
|
||||||
|
- after: 目标方法执行后的权限认证
|
||||||
|
|
||||||
|
如果想要对service bean中的方法进行权限认证,可以参照如下示例:
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class MyCustomerService {
|
||||||
|
@PreAuthorize("hasAuthority('permission:read')")
|
||||||
|
@PostAuthorize("returnObject.owner == authentication.name")
|
||||||
|
public Customer readCustomer(String id) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
当method security被启用时,执行`Customer#readCustomer`将会进行如下逻辑:
|
||||||
|
1. spring aop将会调用`readCustomer`的proxy method,`AuthorizationManagerBeforeMethodInterceptor`继承了`PointcutAdvisor`,在调用proxy method时,会调用所有满足`@PreAuthorize pointcut`的advisor
|
||||||
|
2. 该interceptor将会调用`PreAuthorizeAuthorizationManager#check`方法
|
||||||
|
3. authorization manager将会使用`MethodSecurityExpressionHandler`来转化注解中的spel表达式,并且从`MethodSecurityExpressionRoot`中构建相应的`EvaluationContext`上下文
|
||||||
|
4. interceptor将使用上下文来计算spel表达式,其会从Authentication中获取权限,并且校验是否拥有`permission:read`的权限
|
||||||
|
5. 如果校验通过,spring aop会继续调用被代理的方法
|
||||||
|
6. 如果校验不通过,那么会抛出`AccessDeniedException`异常,并发布`AuthorizationDeniedEvent`事件,`ExceptionTranslationFilter`会将异常捕获,并返回403响应
|
||||||
|
7. 在方法执行返回后,spring aop将会调用满足`@PostAuthorize pointcut`的`AuthorizationManagerAfterMethodInterceptor`,并执行和方法执行前一样的校验,
|
||||||
|
8. 如果表达式校验通过,那么继续正常执行
|
||||||
|
9. 如果表达式校验失败,那么会抛出`AccessDeniedException`异常,并发布`AuthorizationDeniedEvent`事件,`ExceptionTranslationFilter`会将异常捕获,并返回403响应
|
||||||
|
|
||||||
|
#### 多个注解组合
|
||||||
|
如上述示例所示,如果向同一方法中指定多个`method security`注解,那么注解会一次执行,同一时刻只会针对一个注解的逻辑进行执行。
|
||||||
|
|
||||||
|
故而,在执行多个注解时,多个注解的校验逻辑为`and`操作。
|
||||||
|
|
||||||
|
#### 不支持重复注解
|
||||||
|
虽然method security支持在同一方法中指定多个`method security`注解,但是,不支持在同一方法中指定多个相同的`method security`注解。无法在同一方法上指定两个`@PreAuthorize`注解。
|
||||||
|
|
||||||
|
并且,每个注解都有其自己的pointcut,每个注解也有其自己的method interceptor。
|
||||||
|
|
||||||
|
#### 支持通过向角色授权来替换复杂的spel表达式
|
||||||
|
有时候,可能会引入复杂的spel表达式:
|
||||||
|
```java
|
||||||
|
@PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
|
||||||
|
```
|
||||||
|
这种场景下,可以将`permission:read`权限授予给`ROLE_ADMIN`,可以通过`RoleHierarchy`来实现:
|
||||||
|
```java
|
||||||
|
@Bean
|
||||||
|
static RoleHierarchy roleHierarchy() {
|
||||||
|
return new RoleHierarchyImpl("ROLE_ADMIN > permission:read");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
故而,上述spel表达式可以被简化为如下形式:
|
||||||
|
```java
|
||||||
|
@PreAuthorize("hasAuthority('permission:read')")
|
||||||
|
```
|
||||||
|
#### 通过注解来执行权限校验
|
||||||
|
在开启method security之后,支持向类、方法、接口上添加注解来执行权限校验操作。
|
||||||
|
|
||||||
|
##### @PreAuthorize
|
||||||
|
@PreAuthorize的使用示例如下所示:
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class BankService {
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public Account readAccount(Long id) {
|
||||||
|
// ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
只有当spel表达式`hasRole('ADMIN')`校验通过时,才会实际调用`readAccount`方法。
|
||||||
|
|
||||||
|
##### @PostAuthorize
|
||||||
|
@PostAuthorize使用示例如下所示:
|
||||||
|
```java
|
||||||
|
@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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
通过@PostAuthorize,可以针对方法调用的返回结果进行校验。
|
||||||
|
|
||||||
|
##### @PreFilter
|
||||||
|
通过@PreFilter注解,可以对参数进行过滤,使用示例如下所示:
|
||||||
|
```java
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
上述示例中,会过滤掉accounts中所有不满足spel表达式的对象。
|
||||||
|
|
||||||
|
`@PreFilter`注解支持array、collection、map、stream,如下示例中的四种方式,其过滤逻辑都一样:
|
||||||
|
```java
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
##### @PostFilter
|
||||||
|
@PostFilter的使用示例如下所示:
|
||||||
|
```java
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
上述注解会过滤accounts中所有spel表达式校验不通过的account。
|
||||||
|
|
||||||
|
@PostFilter同样支持array、collection、map、stream形式,其使用和@PreFilter类似:
|
||||||
|
```java
|
||||||
|
@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)
|
||||||
|
```
|
||||||
|
##### 在接口或类级别声明注解
|
||||||
|
对于method security,同样支持在方法级别或类级别使用注解。
|
||||||
|
|
||||||
|
在类级别使用注解的示例如下所示:
|
||||||
|
```java
|
||||||
|
@Controller
|
||||||
|
@PreAuthorize("hasAuthority('ROLE_USER')")
|
||||||
|
public class MyController {
|
||||||
|
@GetMapping("/endpoint")
|
||||||
|
public String endpoint() { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
在类级别指定注解后,类中所有的方法都支持该注解的行为:
|
||||||
|
```java
|
||||||
|
@Controller
|
||||||
|
@PreAuthorize("hasAuthority('ROLE_USER')")
|
||||||
|
public class MyController {
|
||||||
|
@GetMapping("/endpoint")
|
||||||
|
public String endpoint() { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
也可以同时在方法级别和类级别指定注解,此时方法级别的注解会覆盖类级别注解的行为:
|
||||||
|
```java
|
||||||
|
@Controller
|
||||||
|
@PreAuthorize("hasAuthority('ROLE_USER')")
|
||||||
|
public class MyController {
|
||||||
|
@GetMapping("/endpoint")
|
||||||
|
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
|
||||||
|
public String endpoint() { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### 在方法spel中指定自定义的bean调用
|
||||||
|
如下为自定义的bean service
|
||||||
|
```java
|
||||||
|
@Component("authz")
|
||||||
|
public class AuthorizationLogic {
|
||||||
|
public boolean decide(MethodSecurityExpressionOperations operations) {
|
||||||
|
// ... authorization logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
可以通过如下方式在表达式中调用自定义的bean service
|
||||||
|
```java
|
||||||
|
@Controller
|
||||||
|
public class MyController {
|
||||||
|
@PreAuthorize("@authz.decide(#root)")
|
||||||
|
@GetMapping("/endpoint")
|
||||||
|
public String endpoint() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user