Spring Security 提供了三种类型的注解来实现方法级别的权限控制:
- JSR-250 注解:标准的 Java EE 规范注解
- @Secured 注解:Spring Security 原生注解
- 基于表达式(Spring EL)的注解:功能最强大的注解类型
通过 @EnableGlobalMethodSecurity 注解的参数来开启相应的注解支持。
1 2 3 4
| @Configuration @EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true, securedEnabled = true) public class MyGlobalMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration { }
|
配置参数说明
| 参数 |
说明 |
prePostEnabled |
启用基于表达式的注解:@PreAuthorize、@PostAuthorize、@PreFilter、@PostFilter |
securedEnabled |
启用 @Secured 注解 |
jsr250Enabled |
启用 @RolesAllowed 注解(JSR-250 标准) |
基于表达式(Spring EL)的注解
这类注解功能最为强大,支持使用 Spring EL 表达式进行复杂的权限判断。
内置表达式
表达式根对象的基类是 org.springframework.security.access.expression.SecurityExpressionRoot。
| 表达式 |
描述 |
hasRole([role]) |
判断是否有指定角色。如果参数 role 不是以”ROLE_”开头,则默认会被加上此前缀。可以在 DefaultWebSecurityExpressionHandler 中修改 defaultRolePrefix 属性实现自定义前缀。 |
hasAnyRole([role1,role2]) |
判断是否有指定的任一角色。默认前缀同上。 |
hasAuthority([authority]) |
判断是否有指定权限。 |
hasAnyAuthority([authority1,authority2]) |
判断是否有指定的任一权限。 |
principal |
代表当前用户的主体对象。 |
authentication |
从 SecurityContext 获得的当前 Authentication 对象。 |
permitAll |
返回值始终为 true。允许任何人访问。 |
denyAll |
返回值始终为 false。不允许任何人访问。 |
isAnonymous() |
判断当前主体是否匿名用户。 |
isRememberMe() |
判断当前主体是否”remember-me”用户 |
isAuthenticated() |
判断当前主体是否非匿名用户。 |
isFullyAuthenticated() |
如果当前主体不是匿名用户或”remember-me”用户,则返回 true。 |
hasPermission(Object target, Object permission) |
如果用户对于给定的目标有指定权限的访问,则返回 true。需要自定义实现。 |
hasPermission(Object targetId, String targetType, Object permission) |
如果用户对于给定的目标(唯一标识+类型)有指定权限的访问,则返回 true。需要自定义实现。 |
四个核心注解
| 注解 |
说明 |
@PreAuthorize |
方法执行之前判断当前人员是否有权限 |
@PostAuthorize |
方法执行之后判断当前人员是否有权限 |
@PreFilter |
对方法参数进行过滤 |
@PostFilter |
对返回值进行过滤 |
Spring-EL 表达式的使用
参数引用
直接引用参数名
1 2
| @PreAuthorize("#contact.username == authentication.name") public void doSomething(Contact contact);
|
使用 @P 注解引用(Spring Security)
1 2 3 4
| import org.springframework.security.access.method.P;
@PreAuthorize("#c.username == authentication.name") public void doSomething(@P("c") Contact contact);
|
使用 @Param 注解引用(Spring Data)
1 2 3 4
| import org.springframework.data.repository.query.Param;
@PreAuthorize("#c.username == authentication.name") Contact findContactByName(@Param("c") Contact contact);
|
集合过滤
在使用 @PreFilter、@PostFilter 注解时,可以使用内置名称 filterObject 表示方法参数、方法返回值集合中的单个对象。
@PreFilter - 参数过滤
对方法参数的过滤。如果存在多个集合类型参数,则需要通过 filterTarget 属性指定目标集合。
1 2
| @PreFilter(value = "filterObject!=authentication.principal.username", filterTarget = "usernames") public String joinUsernamesAndRoles(List<String> usernames, List<String> roles);
|
@PostFilter - 返回值过滤
对方法返回值进行过滤。
1 2
| @PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'write')") public List<Contact> getAll();
|
Spring Bean 方法引用
可以通过 Spring-EL 表达式直接引用 Spring Bean 的方法,以方法的返回值(true/false)作为权限判断的结果。
1. 定义权限检查服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Service("myService") public class MyServiceImpl implements MyService {
@Override public boolean isAdmin() { return "admin".equals( ((User) SecurityContextHolder.getContext().getAuthentication()).getUsername() ); } }
|
2. 在方法上使用
1 2 3 4 5 6 7 8 9 10
|
@GetMapping(path = "/{id}") @PreAuthorize("@myService.isAdmin()") public ResponseEntity<Object> queryById(@PathVariable("id") Long id) { }
|
hasPermission() 内置表达式
工作原理
hasPermission() 内置表达式委托给了 org.springframework.security.access.PermissionEvaluator 的实例进行处理。
核心类层级关系
1 2 3 4
| SecurityExpressionRoot (基类) └─ MethodSecurityExpressionRoot └─ DefaultMethodSecurityExpressionHandler └─ GlobalMethodSecurityConfiguration
|
PermissionEvaluator 接口
1 2 3 4
| public interface PermissionEvaluator { boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission); boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission); }
|
自定义 PermissionEvaluator
1. 实现 PermissionEvaluator 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class MyPermissionEvaluator implements PermissionEvaluator {
@Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { User user = (User) authentication.getPrincipal(); return true/false; }
@Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { User user = (User) authentication.getPrincipal(); return true/false; } }
|
2. 配置到 Spring Security
1 2 3 4 5 6 7 8 9
| @Configuration @EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true, securedEnabled = true) public class MyGlobalMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Bean public PermissionEvaluator permissionEvaluator() { return new MyPermissionEvaluator(); } }
|
3. 使用示例
1 2 3 4 5 6 7 8 9 10
|
@GetMapping(path = "/{id}") @PreAuthorize("hasPermission(#id, 'myType', '')") public ResponseEntity<Object> queryById(@PathVariable("id") Long id) { }
|
三种注解类型对比
| 特性 |
JSR-250 (@RolesAllowed) |
@Secured |
Spring EL (@PreAuthorize) |
| 标准化 |
是 Java EE 标准 |
Spring 专有 |
Spring 专有 |
| 表达能力 |
仅支持角色判断 |
仅支持角色判断 |
支持任意 Spring EL 表达式 |
| 权限判断 |
简单角色检查 |
简单角色检查 |
复杂业务逻辑 |
| 灵活性 |
低 |
中 |
高 |
| 推荐场景 |
简单角色控制 |
简单角色控制 |
复杂业务权限 |
使用建议
- 简单角色控制:使用
@RolesAllowed 或 @Secured
- 复杂权限控制:使用
@PreAuthorize 配合自定义 PermissionEvaluator
- 权限表达式:充分利用
hasRole()、hasAnyRole()、hasAuthority() 等内置表达式
- Bean 引用:对于复杂的业务权限判断,通过 Spring Bean 方法引用实现