Spring Security 之方法级的权限管控 @PreAuthorize 使用详解

默认情况下, Spring Security 并不启用方法级的安全管控. 启用方法级的管控后, 可以针对不同的方法通过注解设置不同的访问条件.

Spring Security 支持三种方法级注解, 分别是 JSR-205 注解 /@Secured 注解 / prePostEnabled 注解. 这些注解不仅可以直接加 controller 方法上, 也可以注解 Service 或 DAO 类中的方法.

启用方法级的管控代码是, 新建一个 WebSecurityConfigurerAdapter Configuration 类, 加上 @EnableGlobalMethodSecurity() 注解, 通过 @EnableGlobalMethodSecurity 参数开启相应的方法级的管控.

1. JSR-205 注解

通过 @EnableGlobalMethodSecurity(jsr250Enabled=true), 开启 JSR-205 注解.

@DenyAll 注解, 拒绝所有的访问
@PermitAll 注解, 运行所有访问
@RolesAllowed({"USER","ADMIN"}), 该方法只允许有 ROLE_USER 或 ROLE_ADMIN 角色的用户访问.

2. @Secured 注解

通过 @EnableGlobalMethodSecurity(securedEnabled=true), 开启 @Secured 注解.

只有满足角色的用户才能访问被注解的方法, 否则将会抛出 AccessDenied 异常.

例子:
@Secured("ROLE_TELLER","ROLE_ADMIN"), 该方法只允许 ROLE_TELLER 或 ROLE_ADMIN 角色的用户访问.
@Secured("IS_AUTHENTICATED_ANONYMOUSLY"), 该方法允许匿名用户访问.

3. @PreAuthorize 类型的注解 (支持 Spring 表达式)

3.1 SPEL 表达试(bean 引用)

如果解析上下文已经配置,那么 bean 解析器能够 从表达式使用(@)符号查找 bean 类。

复制代码
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@foo").getValue(context);

@EnableGlobalMethodSecurity(prePostEnabled=true), 开启 prePostEnabled 相关的注解.

JSR-205 和 @Secured 注解功能较弱, 不支持 Spring EL 表达式. 推荐使用 @PreAuthorize 类型的注解.

具体有 4 个注解.
@PreAuthorize 注解, 在方法调用之前, 基于表达式结果来限制方法的使用.
@PostAuthorize 注解, 允许方法调用, 但是如果表达式结果为 false, 将抛出一个安全性异常.
@PostFilter 注解, 允许方法调用, 但必要按照表达式来过滤方法的结果.
@PreFilter 注解, 允许方法调用, 但必须在进入方法之前过来输入值.

例子:

复制代码
@PreAuthorize("hasRole('ADMIN')") //必须有 ROLE_ADMIN 角色
public void addBook(Book book);

//必须同时具备 ROLE_ADMIN 和 ROLE_DBA 角色
@PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")
public void addBook(Book book);


@PreAuthorize ("#book.owner == authentication.name")
public void deleteBook(Book book);


@PostAuthorize ("returnObject.owner == authentication.name")
public Book getBook();

3.2 @PreAuthorize 表达式

3.2.1. returnObject 保留名

对于 @PostAuthorize 和 @PostFilter 注解, 可以在表达式中使用 returnObject 保留名, returnObject 代表着被注解方法的返回值, 我们可以使用 returnObject 保留名对注解方法的结果进行验证.

比如:

复制代码
@PostAuthorize ("returnObject.owner == authentication.name")
public Book getBook();
3.2.2. 表达式中的 # 号

在表达式中, 可以使用 #argument123 的形式来代表注解方法中的参数 argument123.

比如:

复制代码
@PreAuthorize ("#book.owner == authentication.name")
public void deleteBook(Book book);

还有一种 #argument123 的写法, 即使用 Spring Security @P 注解来为方法参数起别名, 然后在 @PreAuthorize 等注解表达式中使用该别名. 不推荐这种写法, 代码可读性较差.

复制代码
@PreAuthorize("#c.name == authentication.name")
public void doSomething(@P("c") Contact contact);

3.3. 内置表达式有:

表达式 备注
hasRole(role) 如果有当前角色, 则返回 true(会自动加上 ROLE_ 前缀)
hasAnyRole(role1, role2) 如果有任一角色即可通过校验, 返回 true,(会自动加上 ROLE_ 前缀)
hasAuthority(authority) 如果有指定权限, 则返回 true
hasAnyAuthority(authority1, authority2) 如果有任一指定权限, 则返回 true
principal 获取当前用户的 principal 主体对象
authentication 获取当前用户的 authentication 对象,
permitAll 总是返回 true, 表示全部允许
denyAll 总是返回 false, 代表全部拒绝
isAnonymous() 如果是匿名访问, 返回 true
isRememberMe() 如果是 remember-me 自动认证, 则返回 true
isAuthenticated() 如果不是匿名访问, 则返回 true
isFullAuthenticated() 如果不是匿名访问或 remember-me 认证登陆, 则返回 true
hasPermission(Object target, Object permission)
hasPermission(Object target, String targetType, Object permission)

例子

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

SecurityConfig 配置类

SecurityConfig 配置类开启方法级管控 (仅启用 prePostEnabled 类的注解), 并 hard coded 了一个内置的用户清单.

因为没有重载 configure(HttpSecurity http) 方法, 用的是 Spring security 的 basic Authentication 和内置的 login form.

复制代码
@EnableWebSecurity@Configuation
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Override
    public void configure(AuthenticationManagerBuilder builder)
            throws Exception {
        builder.inMemoryAuthentication()
               .withUser("123").password("123").roles("USER")
               .and()
               .withUser("ADMIN").password("ADMIN").roles("ADMIN")
               .and()
               .withUser("124").password("124").roles("USER2");
    }

    @SuppressWarnings("deprecation")
    @Bean
    public NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }
}

BookService 配置类

为 BookService Service 类的方法加上 @PreAuthorize 注解, 对权限进行控制.

复制代码
@Service
class BookService{
    @PreAuthorize("hasRole('ADMIN')")
    public void addBook(Book book) {
        System.out.println("you have added a book successfully");
    }

    @PreAuthorize("hasAnyRole('ADMIN','USER')")
    public Book getBook() {
        Book book=new Book("A");
        return book ;
    }

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteBook(Book book) {
        System.out.println("Book deleted");
    }
}
  • spring security 添加自定义动态权限

  • spring security 兼容新版本 5.7

  • security CorsFilter 添加 order 最新执行,避免其他filter提前返回

  • elasticsearch 添加 JsonpMapper 扩展

  • web-spring-boot-starter: 添加404全局错误

  • e6f0bc99(feat(mybatis-plus-boot-starter): 添加租户开关配置,默认关)

  • 2632aa7f(fix(oss): 修改私有访问的 bucket previewUrl 链接的拼接)

  • 29d34be9(fix(分页工厂类): 当前页数错误)

  • 622293bb(pagehelper 版本升级)

相关推荐
葫芦和十三21 小时前
图解 MongoDB 26|片键设计:决定集群命运的一个决定
后端·mongodb·agent
Avan_菜菜1 天前
使用 Docker + rclone 自建 WebDAV
后端·agent·claude
小bo波1 天前
Java Swing 图形用户界面实验 —— 从算术练习到游戏开发的完整实践
java·课程设计·gui·游戏开发·扫雷·swing
阳光是sunny1 天前
别再被 worktree 绕晕了!AI 编程时代你必须掌握的 Git 隔离神器
前端·人工智能·后端
万少1 天前
万少的博客 - 技术分享与解决方案
前端·javascript·后端
咖啡八杯1 天前
GoF设计模式——备忘录模式
java·后端·spring·设计模式
苍何1 天前
腾讯再放大招,企微 Agent 大圆开启内测
后端
ethantan1 天前
一篇讲解AI Agent 组成:像人一样思考的智能体
人工智能·后端·程序员
Cosolar1 天前
vLLM 生产级部署完全指南
人工智能·后端·架构
IT_陈寒1 天前
垃圾回收器选错了,我的Java服务内存炸了
前端·人工智能·后端