回顾
前面我们通过《循序渐进学spring security 第七篇,如何基于用户表和权限表配置权限?越学越简单了》介绍了如何通过动态给用户授权和控制URL权限,后面又通过《循序渐进学spring security 第十一篇 如何动态权限控制URL?如何动态给用户添加权限?》介绍了如何动态分配URL权限和用户权限,今天,我们继续介绍权限的四种模式
- 表达式控制 URL 路径权限
- 表达式控制方法权限
- 使用过滤注解
- 动态权限
四种模式
表达式控制 URL 路径权限
其实就是通过表达式控制 URL 路径权限,这种方式在之前的文章中实际上和大家讲过,这里我们再来稍微复习一下。
Spring Security 支持在 URL 和方法权限控制时使用 SpEL 表达式,如果表达式返回值为 true 则表示需要对应的权限,否则表示不需要对应的权限。提供表达式的类是 SecurityExpressionRoot:
可以看到,SecurityExpressionRoot 有两个实现类,表示在应对 URL 权限控制和应对方法权限控制时,分别对 SpEL 所做的拓展,例如在基于 URL 路径做权限控制时,增加了 hasIpAddress 选项。
我们来看下 SecurityExpressionRoot 类中定义的最基本的 SpEL 有哪些:
可以看到,这些都是该类对应的表达式,这些表达式我列出来了表格,说明其功能
表达式 | 备注 |
---|---|
hasRole | 具备指定角色可访问资源权限 |
hasAnyRole | 具备多个角色中的任意一个即可访问资源 |
hasAuthority | 类似于 hasRole |
hasAnyAuthority | 类似于 hasAnyRole |
permitAll | 统统允许访问 |
denyAll | 统统拒绝访问 |
isAnonymous | 判断是否匿名用户 |
isAuthenticated | 判断是否认证成功 |
isRememberMe | 判断是否通过记住我登录的 |
isFullyAuthenticated | 判断是否用户名/密码登录的 |
principle | 当前用户 |
authentication | 从 SecurityContext 中提取出来的用户对象 |
这里列举的是最基本的比较常用的方法
如果是通过 URL 进行权限控制,那么我们只需要按照如下方式配置即可:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated()
;
}
这里表示访问
- /admin/** 格式的路径需要 admin 角色
- 访问 /user/** 格式的路径需要 user 角色
这就是静态配置,需要事先确定好哪些URL拥有什么权限才能访问时,可以通过这种方式配置,但是通过这种方式配置,有个缺点,就是每次新增接口权限时,都得到配置文件中去配置权限,即需要修改原来的代码,容易引起bug,因此,在开发中,一般不会通过这种方式配置
表达式控制方法权限
当然,我们也可以通过在方法上添加注解来控制权限。需要我们首先开启注解@EnableGlobalMethodSecurity的使用,在 Spring Security 配置类上添加如下内容:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
...
}
这个配置开启了三个注解,分别是:
注解 | 备注 |
---|---|
@PreAuthorize | 方法执行前进行权限检查 |
@PostAuthorize | 方法执行后进行权限检查 |
@Secured | 类似于 @PreAuthorize |
这三个结合 SpEL 之后,用法非常灵活,这里和大家稍微分享几个 Demo
/**
* @PreAuthorize("principal.username.equals('mike')") 注解的约束是,只有当前登录用户名为 mike 的用户才可以访问该方法。
* @return
*/
@PreAuthorize("principal.username.equals('mike')")
@RequestMapping("/hello")
public String hello(){
return "一望二三里----某某诗人,hello user: "+getLoginUser();
}
/**
* @PreAuthorize("hasRole('admin')") 表示访问该方法的用户必须具备 admin 角色。
* @return
*/
@RequestMapping("/admin/index")
@PreAuthorize("hasRole('admin')")
public String adminIndex(){
return "落霞与孤鹜齐飞,秋水共长天一色----某某诗人,hello user: "+getLoginUser();
}
/**
* @Secured({"ROLE_user"})表示方法该方法的用户必须具备 user 角色,但是注意 user 角色需要加上 ROLE_ 前缀。
* @return
*/
@RequestMapping("/user/index")
@Secured({
"ROLE_user"})
public String userIndex(){
return "采菊东篱下悠然见南山----陶渊明,hello user: "+getLoginUser();
}
/**
* 访问该接口的 score 参数必须大于 80,否则请求不予通过。
* @return
*/
@RequestMapping("/score")
@PreAuthorize("#score>80")
public String getScore(int score){
return "当前登录用户"+getLoginUser()+"查询成绩:"+score;
}
- 第一个 /hello 接口,注解的约束是,只有当前登录用户名为 mike 的用户才可以访问该方法。
- 第二个 /admin/index 接口,表示访问该方法的用户必须具备 admin 角色。
- 第三个 /user/index 接口,表示方法该方法的用户必须具备 user 角色,但是注意 user 角色需要加上 ROLE_ 前缀。
- 第四个 /score 接口,表示访问该方法的 score 参数必须大于 80,否则请求不予通过。
这里的表达式还是非常丰富,如果想引用方法的参数,前面加上一个 # 即可,既可以引用基本类型的参数,也可以引用对象参数。
缺省对象除了 principal ,还有 authentication
其实,这些注解表达式,不仅可以用在controller接口上,也可以用在service,或者其他spring管理的bean方法上。
很显然,利用表达式控制方法权限 才是适合我们开发中经常会用到的,为什么?比如我现在需要新增一个controller接口,需要对某些方法进行URL权限控制,我就不需要去修改配置,增加一个URL和指定权限了,只需要在我这个controller的方法里面,增加个注解,如 @PreAuthorize(“hasRole(‘admin’)”) 其中admin就是对应URL应该要控制的权限,这样就完成了。这样的好处是,不需要去修改已有的代码,从而更好的避免bug的发生
使用过滤注解
Spring Security 中还有两个过滤函数 @PreFilter 和 @PostFilter,可以根据给出的条件,自动移除集合中的元素。
@RequestMapping("/getAllStudentScore")
@PostFilter("filterObject>5")
public List<Integer> getAllStudentScore() {
List<Integer> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(i);
}
return users;
}
@RequestMapping("/getAllAge")
// @PreFilter("filterObject%2==0")
@PreFilter(filterTarget = "ages",value = "filterObject%2==0")
public List<Integer> getAllAge(@RequestBody List<Integer> ages) {
System.out.println("ages = " + ages);
return ages;
}
- 在 getAllStudentScore 接口中,对集合进行过滤,只返回分数大于5的元素,filterObject 表示要过滤的元素对象。
- 在 getAllAge 方法中,如果只有一个集合参数,可以不用指定filterTarget 指定过滤对象,如果有多个参数,就需要指定
当然,这些注解不仅可以用在接口这里,也可以用在其他spring管理的bean上定义的,过滤注解非常灵活了,开发起来就很顺手,对于在原有接口做拓展时,用起来就是屡试不爽
以前,我要在某个方法返回数据之前,对数据进行过滤之后,将满足条件的数据返回,不满足的过滤掉,怎么做?
因为我不想去修改原有代码,因为代码不一定是我写的,逻辑非常复杂,去改别人的代码甚至自己的代码都是容易引起bug的,然后我就会写一个AOP去拓展,将方法返回数据后,在遍历去过滤满足条件的数据,然后再返回,每次都要新建AOP去处理,也是蛮痛苦的,因为也需要写一堆的过滤逻辑
那现在怎么做?
可以去改原有代码,但是这次怎么改呢?只需轻轻的在方法上面加个注解 @PostFilter,然后将过滤条件写上,就可以了,是不是爽多了?
可能有人会说,这不是要去改原有代码吗?这也没错,但是仔细看,你会发现,我只是加了个注解,没有修改原有的业务逻辑,是不会引起其他bug的,这个代价还是非常小的
动态权限
其实前面我们通过《循序渐进学spring security 第十一篇 如何动态权限控制URL?如何动态给用户添加权限?》已经介绍过动态权限了,如果没看过我之前的文章的,可以先去看看
好了,通过这篇文章,对于spring security的权限管理是不是更加了解了?如果这篇文章的知识领悟到了,在开发过程中,将会帮助到你很多关于权限控制方面的,还可以让你的开发效率上一高层
本文涉及到的代码,我是在之前《循序渐进学spring security 第十一篇 如何动态权限控制URL?如何动态给用户添加权限?》项目的基础上写的,我后面会上传代码到git仓库,大家可以下载下来体验一下,下载地址可以去之前文章下载