分类
Spring Security

Spring Security – @PreFilter 与 @PostFilter 注解

Spring Security – @PreFilter and @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以及同步视频。