- github code demo: https://github.com/codedemo-club/spring-security-prefilter-postfilter
- 同步视频:https://www.bilibili.com/video/BV1At4y1D7Rw/
- 参考原文:https://www.baeldung.com/spring-security-prefilter-postfilter
在继续阅读之前,可以点击此处以下链接获取一份与本文相同的初始化代码。
1. 概述
本文中我们将介绍如何在Spring项目中使用 @PreFilter @PostFilter 注解,从而实现一些特定的安全规则。
@PreFilter @PostFilter 可以结合当前登录用户信息,使用SpEL(Spring Expression Language)实现更多的权限控制策略。
2. 初识 @PreFilter @PostFilter
简单来说@PreFilter以及@PostFilter的作用是:按设定的规则过滤数据列表,将符合规则的留下,将不符合规则的剔除。
@PostFilter用于事后过滤,过滤的对象是方法的返回值。在过滤的过程中,依次对返回的数据列表的项进行校验。当某个数据项经校验返回true时,则保留;返回false时,则剔除。
@Prefilter的原理也是如此。不同的是@Prefilter的过滤对象是传入方法的参数。
@PreFilter、@PostFilter支持添加到方法及类型上(类或接口)。本文中仅讨论其添加到方法如何使用。
Spring Security默认关闭了@PreFilter、@PostFilter,所以若想使其生效,则需要在@EnableGlobalMethodSecurity中加入prePostEnabled = true:
/** * 全局安全配置 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration { }
3. 定义安全规则
@PreFilter、@PostFilter均支持SpEL;在表达式中可以使用filterObject来表示传入的参数中(@PreFilter)的遍历项或返回值(@PostFilter)的遍历项。
Spring提供了一系列的像filterObject一样的表达式,详情请参考官方文档。
比如可以使用@PostFilter遍历返回的列表,将遍历的值中的teacherName(班主任姓名)属性与当前登录用户的name值不相同的对象过滤掉:
@PostFilter("filterObject.teacherName == authentication.principal.username") List<Student> findAll() { List<Student> students = new ArrayList<>(); students.add(new Student("zhangsan")); students.add(new Student("lisi")); students.add(new Student("wangwu")); return students; }
上述代码将首先执行findAll方法并获取该方法的返回值,接下来按@PostFilter的过滤规则进行过滤,将符合条件的保留,不符合条件的移除。
所以,假设当前登录的用户名是zhangsan,findAll方法最终将只返回当前教师名称为zhangsan的学生(lisi、wangwu的任务将被过滤掉)。
接下来,让我们展示一个稍微复杂些的表达式:
@PostFilter("hasRole('TEACHER') or filterObject.teacherName == authentication.principal.username") List<Student> findAllWithRole() { List<Student> students = new ArrayList<>(); students.add(new Student("zhangsan")); students.add(new Student("lisi")); students.add(new Student("wangwu")); return students; }
上述方法实现了:如果当前登录的用户角色是TEACHER(教师),则将返回全部的学生;如果登录的用户角色非TEACHER,则只返回当前教师负责的学生。
接下来,让我们看看@PreFilter是如何对传入的参数进行过滤的:
@PreFilter("hasRole('TEACHER') or filterObject.teacherName == authentication.principal.username") List<Student> save(List<Student> students) { System.out.println(students.size()); return students; }
该方法中我们使用了与前面@PosFilter参数相同的参数。此时,如果当前登录用户是TEACHER(教师),则不对传入参数进行过滤;否则则仅保留当前教师负责的学生。
4. 大数据量下的表现
@PosFilter虽然简单易用,但如果某方法中返回的数据量过大,则由于其需要遍历其每一项的特点,将对程序的执行效率产生影响。
比如我们想获取某个班级的所有同学,在使用@PosFilter时可以先获取数据库中的所有学生,然后依次对学生进行遍历判断。虽然最终也能够实现,但并不是一种好的方法。所以在使用的过程中,还要依据实际的情况判断是否适用使用@PosFilter。
5. 总结
本文简单对 @PreFilter and @PostFilter注解的使用方法进行了介绍,希望能对你有所帮助。
若想获取更为详细的使用的方法,请参考在本文开头为大家准备的code demo以及同步视频。