Spring Security6 教程

MethodExpressionAuthorizationManager 授权管理器

MethodExpressionAuthorizationManager 是 Spring Security 6.1 中用于基于表达式的方法授权管理器,它允许你使用 SpEL(Spring Expression Language)表达式定义复杂的访问控制规则。通过该管理器,你可以在方法调用前或调用后进行动态权限验证,支持访问用户信息、方法参数、返回值等上下文数据。

核心功能

  • 表达式支持,使用 SpEL 表达式定义授权规则,例如:

    • hasRole('admin'):验证用户角色

    • hasPermission(#user, 'delete'):基于对象实例的权限验证

    • @securityService.checkAccess(#user):调用自定义服务进行验证

  • 注解驱动,支持 Spring Security 的注解:

    • @PreAuthorize:方法调用前验证

    • @PostAuthorize:方法调用后验证

    • @PreFilter:过滤方法参数

    • @PostFilter:过滤方法返回值

  • 上下文访问,在表达式中访问:

    • authentication:当前用户认证信息

    • principal:用户主体对象

    • 方法参数(通过名称或索引)

    • 方法返回值(仅 @PostAuthorize)

核心 SpEL 表达式

下面是 Spring Security 中 SpEL 的核心表达式,如下表:

表达式描述
hasRole('ROLE')验证用户是否拥有指定角色(自动添加 ROLE_ 前缀)
hasAuthority('AUTH')验证用户是否拥有指定权限(不添加前缀)
principal访问当前用户主体对象
authentication访问完整的 Authentication 对象
permitAll允许所有访问
denyAll拒绝所有访问
isAnonymous()验证用户是否为匿名用户
isAuthenticated()验证用户是否已认证
isFullyAuthenticated()验证用户是否已完全认证(非记住我登录)
hasPermission(target, permission)使用 PermissionEvaluator 验证对象权限

源码分析

下面是 MethodExpressionAuthorizationManager 类的源码:

package org.springframework.security.authorization.method;

import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;

/**
 * 基于表达式的方法授权管理器
 * 用于通过评估 SpEL 表达式来决定是否允许对方法的访问
 * 支持在方法调用前进行权限检查
 */
public final class MethodExpressionAuthorizationManager implements AuthorizationManager<MethodInvocation> {
    // 安全表达式处理器,用于解析和计算安全表达式
    private SecurityExpressionHandler<MethodInvocation> expressionHandler = new DefaultMethodSecurityExpressionHandler();

    // 待评估的 SpEL 表达式
    private Expression expression;

    /**
     * 创建一个方法表达式授权管理器实例
     * @param expressionString 要解析的原始表达式字符串,例如 "hasRole('ADMIN')"
     */
    public MethodExpressionAuthorizationManager(String expressionString) {
        Assert.hasText(expressionString, "expressionString cannot be empty");
        // 使用表达式处理器解析表达式字符串
        this.expression = this.expressionHandler.getExpressionParser().parseExpression(expressionString);
    }

    /**
     * 设置用于解析和计算表达式的安全表达式处理器
     * 默认使用 DefaultMethodSecurityExpressionHandler
     * @param expressionHandler 要使用的表达式处理器
     */
    public void setExpressionHandler(SecurityExpressionHandler<MethodInvocation> expressionHandler) {
        Assert.notNull(expressionHandler, "expressionHandler cannot be null");
        this.expressionHandler = expressionHandler;
        // 使用新的表达式处理器重新解析表达式
        this.expression = expressionHandler.getExpressionParser()
            .parseExpression(this.expression.getExpressionString());
    }

    /**
     * 通过评估表达式来决定是否允许访问
     * @param authentication 当前用户的认证信息提供者
     * @param context 方法调用上下文,包含方法信息和参数
     * @return 基于表达式评估结果的授权决策
     */
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation context) {
        // 创建表达式评估上下文,将认证信息和方法调用上下文注入
        EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, context);
        // 评估表达式并获取布尔结果
        boolean granted = ExpressionUtils.evaluateAsBoolean(this.expression, ctx);
        // 创建包含表达式和评估结果的授权决策
        return new ExpressionAuthorizationDecision(granted, this.expression);
    }

    @Override
    public String toString() {
        return "WebExpressionAuthorizationManager[expression='" + this.expression + "']";
    }
}

简单示例

@PreAuthorize 注解

该注解方法调用前验证,如下:

// 仅 normal 角色可调用,且用户 ID 必须与当前登录用户 ID 相同或为 lisi
@PreAuthorize("hasRole('admin') || #username == authentication.principal.username")
@GetMapping("/hello2")
public String hello2(@RequestParam("username") String username) {
    System.out.println("hello2() username=" + username);
    return "Hello, Spring Security!";
}

运行效果如下图:

image.png

@PostAuthorize 注解

该注解在方法调用后验证,如下:

// 返回后验证:仅用户所有者或 ADMIN 可查看敏感内容
@PostAuthorize("returnObject.username == authentication.principal.username || hasRole('admin')")
@GetMapping("/postAuthorize")
public UserModel postAuthorize() {
    System.out.println("postAuthorize()");
    UserModel userModel = new UserModel();
    userModel.setId(2);
    userModel.setUsername("lisi");
    return userModel;
}

上述代码运行效果如下图:

image.png

@PreFilter 注解

该注解用来参数值过滤,如下:

// 过滤输入参数:仅允许用户修改自己的消息
@PreFilter("filterObject.username == authentication.principal.username")
@PostMapping("/updateUsers")
public String updateMessages(@RequestBody List<UserModel> users) {
    return JSONObject.toJSONString(users, JSONWriter.Feature.PrettyFormat);
}

对应的 JS 代码如下:

function updateUsers() {
    $.ajax({
        url: '/updateUsers',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify([
            { "id": 1, "username": "zhangsan", "authorities":["menu:user:add", "menu:user:delete"] },
            { "id": 2, "username": "lisi", "authorities":["menu:user:add", "menu:user:delete"] }
        ]),
        success: function(response) {
            console.log('请求成功:', response);
        },
        error: function(xhr, status, error) {
            console.error('请求失败:', error);
        }
    });
}

运行上述示例,输出如下图:

image.png

@PostFilter 注解

该注解用来返回值过滤,如下:

// 过滤返回值:仅返回当前用户可见的消息
@PostFilter("filterObject.username == authentication.principal.username")
@GetMapping("/getUsers")
public List<UserModel> getMessages() {
    return new ArrayList<>(){{
        UserModel userModel1 = new UserModel();
        userModel1.setId(1);
        userModel1.setUsername("zhangsan");
        userModel1.setAuthorities(Arrays.asList("menu:user:add", "menu:user:delete"));
        add(userModel1);

        UserModel userModel2 = new UserModel();
        userModel2.setId(2);
        userModel2.setUsername("lisi");
        userModel2.setAuthorities(Arrays.asList("menu:user:add", "menu:user:delete"));
        add(userModel2);
    }};
}

运行上述代码,效果如下图:

image.png

UserModel 类到定义如下:

package com.hxstrive.spring_security.model;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
 * 用户实体
 * @author hxstrive.com
 */
public class UserModel implements UserDetails {
    // 用户ID
    private int id;
    // 用户名
    private String username;
    // 密码
    private String password;
    // 权限列表
    private Collection<? extends GrantedAuthority> authorities; // 主要是这个

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public void setAuthorities(List<String> authorities) {
        this.authorities = AuthorityUtils.createAuthorityList(authorities);
    }

    //...
}

注意,MethodExpressionAuthorizationManager 提供了强大的基于表达式的方法级授权能力,通过 SpEL 表达式可以实现复杂的动态访问控制。它是 Spring Security 中最灵活的授权方式,适合需要细粒度权限控制的企业级应用。

 

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号