@EnableGlobalMethodSecurity

SpringSecurity 支持多种权限注解,但是需要通过全局的【@EnableGlobalMethodSecurity】注解开启权限注解的使用

  1. @Configuration
  2. @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
  3. public class SecurityConfig{}

其中该注解配置了三个属性:

  • prePostEnabled:表示开启Spring Security默认提供的四个注解【@PostAuthorize】【@PostFilter】【@PreAuthorize】【@PreFilter】,支持权限表达式,支持SpEL表达式
  • securedEnabled:开启SpringSecurity提供的【@Secured】 注解,不支持权限表达式
  • jsr250Enabled:开启JSR250提供的注解,主要包括【@DenyAll】【@PermitAll】【@RoleAllowed】


测试

导入基础依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-security</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-test</artifactId>
  13. <scope>test</scope>
  14. </dependency>
  15. <dependency>
  16. <groupId>mysql</groupId>
  17. <artifactId>mysql-connector-java</artifactId>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.projectlombok</groupId>
  21. <artifactId>lombok</artifactId>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.security</groupId>
  25. <artifactId>spring-security-test</artifactId>
  26. <scope>test</scope>
  27. </dependency>
  28. </dependencies>

接口&配置

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {

}

测试类

@PreAuthorize

通过 Security测试类的注解【@WithMockUser(roles = “ADMIN”) 】
注解设定当前执行的用户角色是 ADMIN

@Service
@Slf4j
public class HelloService {

    /**
     * 只有ADMIN角色才能访问
     */
    @PreAuthorize("hasRole('ADMIN')")
    public String hello() {
        log.info("Hello");
        return "Hello";
    }
}
@SpringBootTest
public class TestSecurity {

    @Autowired
    private HelloService helloService;

    @Test
    @WithMockUser(roles = "ADMIN")
    void testHasRole() {
        String hello = helloService.hello();
        assertNotNull(hello);
    }

}

测试通过
image.png
切换为其他的角色
@WithMockUser(roles = “user”)
image.png

多个表达式

这里除了 hasRole 表达式之外,也可以使用其他权限表达式,甚至也可以同时使用多个权限表达式

/**
     * 访问者名称必须是 zukxu,而且还需要同时具备 ADMIN 角色,才可以访问该方法
     */
    @PreAuthorize("hasRole('ADMIN') and authentication.name=='zukxu'")
    public String hello2() {
        log.info("Hello zukxu");
        return "Hello zukxu";
    }
    @Test
    @WithMockUser(roles = "ADMIN", username = "zukxu")
    void testHasRole2() {
        String hello = helloService.hello2();
        assertNotNull(hello);
    }

成功
image.png

参数校验

在 @PreAuthorize 注解中,还可以通过 # 引用方法的参数,并对其进行校验

 /**
     * 表示请求者的用户名必须等于方法参数 name 的值,方法才可以被执行
     */
    @PreAuthorize("authentication.name==#name")
    public String hello3(String name) {
        log.info("Hello {}",name);
        return "hello:" + name;
    }
   @Test
    @WithMockUser(username = "zukxu")
    void testPreAuthorize3() {
        String hello = helloService.hello3("aaa");
        assertNotNull(hello);
    }

结果
image.png

@PreFilter

@PreFilter 主要是对方法的请求参数进行过滤,
它里边包含了一个内置对象 filterObject 表示要过滤的参数,
如果方法只有一个参数,则内置的 filterObject 对象就代表该参数;如果方法有多个参数,则需要通过 filterTarget 来指定 filterObject 到底代表哪个对象

  /**
     * 对参数进行过滤,排除不满足条件的
     */
    @PreFilter(value = "filterObject.id%2!=0", filterTarget = "users")
    public void addUsers(List<SysUser> users, Integer other) {
        log.info("users {}", users);
        log.info("other {}", other);
    }
  @Test
    @WithMockUser(username = "zukxu")
    void testPreFilter() {
        List<SysUser> users = new ArrayList<>();
        for(int i = 0; i < 10; i++) {
            users.add(new SysUser(i, "zukxu:" + i));
        }
        helloService.addUsers(users, 99);
    }

结果
image.png

@PostAuthorize

@PostAuthorize 是在目标方法执行之后进行权限校验。
目标方法都执行完了才去做权限校验意义何在?
其实这个主要是在 ACL 权限模型中会用到,目标方法执行完毕后,通过 @PostAuthorize 注解去校验目标方法的返回值是否满足相应的权限要求。

方法执行完成后,根据用户的权限信息过滤出需要返回给用户的数据。

从技术角度来讲,@PostAuthorize 注解中也可以使用权限表达式,但是在实际开发中权限表达式一般都是结合 @PreAuthorize 注解一起使用的。
@PostAuthorize 包含一个内置对象 returnObject,表示方法的返回值,开发者可以对返回值进行校验

 /**
     * 对返回结果进行校验
     */
    @PostAuthorize("returnObject.id==1")
    public SysUser getUserById(Integer id) {
        log.info("id {}", id);
        return new SysUser(id, "zukxu");
    }
 @Test
    @WithMockUser(username = "zukxu")
    void testPostAuthorize() {
        SysUser user = helloService.getUserById(2);
        assertNotNull(user);
    }

结果
image.png

@PostFilter

@PostFilter 注解是在目标方法执行之后,对目标方法的返回结果进行过滤,
该注解中包含了一个内置对象 filterObject,表示目标方法返回的集合/数组中的具体元素

  /**
     * 对返回结果及逆行过滤
     */
    @PostFilter("filterObject.id%2==0")
    public List<SysUser> listUser() {
        List<SysUser> users = new ArrayList<>();
        for(int i = 0; i < 10; i++) {
            users.add(new SysUser(i, "zukxu:" + i));
        }
        return users;
    }
 @Test
    @WithMockUser(username = "zukxu")
    void testPostFilter() {
        List<SysUser> user = helloService.listUser();
        user.forEach(System.out::println);
        assertNotNull(user);
    }

结果
image.png

@Secured

该注解不支持权限表达式,只能做一些简单的权限描述

 /**
     * 满足ADMIN 和 USER 角色的才能访问
     */
    @Secured({ "ROLE_ADMIN", "ROLE_USER" })
    public SysUser getUserByUsername(String username) {
        log.info("username {}", username);
        return new SysUser(99, username);
    }

注意,这里不需要给角色添加 ROLE_ 前缀,系统会自动添加

  @Test
    @WithMockUser(roles = "ADMIN", username = "zukxu")
    void testSecured() {
        SysUser user = helloService.getUserByUsername("zukxu");
        System.out.println(user);
        assertNotNull(user);
    }

结果
image.png

@DenyAll

拒绝所有访问:访问会抛出异常

@PermitAll

允许所有访问

@RolesAllowed

可以添加在方法上或者类上,当添加在类上时,表示该注解对类中的所有方法生效;如果类上和方法上都有该注解,并且起冲突,则以方法上的注解为准

    /**
     * 具备任一角色即可访问
     */
    @RolesAllowed({ "ADMIN", "USER"})
    public String rolesAllowed() {
        return "Roles Allowed";
    }
    @Test
    @WithMockUser(roles = "ADMIN", username = "zukxu")
    void testRolesAllowed() {
        String s = helloService.rolesAllowed();
        System.out.println(s);
        assertNotNull(s);
    }

结果
image.png