Spring Security 中自定义权限表达式

news2024/11/19 3:23:39

Spring Security 中自定义权限表达式

    • 一. SpEL中使用自定义Bean
    • 二. 通过类继承自定义权限表达式
      • 2.1 自定义 ExpressionRoot
    • 三. 参考文章

前言

这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱

一. SpEL中使用自定义Bean

通过编程授权方法

首先,声明一个 Bean,如下所示:

@Service("authz")
public class PermissionService {
      /**
     * 验证用户是否具备某权限
     *
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    public boolean hasPerm(String permission) {
        // 逻辑处理
    }
    
}

然后,在注解中以如下方式引用该 Bean:

@Controller
public class MyController {
    @PreAuthorize("@authz.hasPerm('com:user:ip')")
    @GetMapping("/endpoint")
    public String endpoint() {
        // ...
    }
}

Spring Security 将在每次方法调用时调用该Bean上的给定方法。

这样做的好处是所有的授权逻辑都在一个单独的类中,可以独立进行单元测试并验证其正确性。

二. 通过类继承自定义权限表达式

  1. 第一种的缺点:

    (1) 使用第一种方式idea会有告警,使得项目很难看

    请添加图片描述

    (2) 这种自定义方式太自由了,没有在 Spring Security 架构内完成这件事。所以,我们要在不使用第三方对象的情况下,来自定义一个权限判断的表达式。

我们在 @PreAuthorize 注解中使用的不用加对象名就能调用的授权字段和方法,如 hasAuthority、hasPermission、hasRole、hasAnyRole 等, Spring Security 将所有授权字段和方法封装在一组根(root)对象中。最通用的根对象称为 SecurityExpressionRoot,它构成了 MethodSecurityExpressionRoot 的基础。当准备评估授权表达式时,Spring Security 会将该根对象提供给 MethodSecurityEvaluationContext

2.1 自定义 ExpressionRoot

首先我们自定义一个类继承自 SecurityExpressionRoot 并实现 MethodSecurityExpressionOperations 接口(本来直接继承自 MethodSecurityExpressionRoot 即可,但是因为这个类不是 public 的,没法继承,所以我们就实现 MethodSecurityExpressionOperations 接口即可):

/**
 * 自定义权限实现
 *
 * @author chenyunzhi
 */
public class PermissionSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    private Object filterObject;

    private Object returnObject;

    private Object target;


    /**
     * 所有权限标识
     */
    private static final String ALL_PERMISSION = "*:*:*";

    private static final String PERMISSION_DELIMETER = ",";

    /**
     * Creates a new instance
     *
     * @param authentication the {@link Authentication} to use. Cannot be null.
     */
    public PermissionSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    /**
     * 验证用户是否具备某权限
     *
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    public boolean hasPerm(String permission) {
        if (StringUtils.isEmpty(permission)) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
            return false;
        }
        return hasPermissions(loginUser.getPermissions(), permission);
    }

    /**
     * 验证用户是否具有以下任意一个权限
     *
     * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
     * @return 用户是否具有以下任意一个权限
     */
    public boolean hasAnyPerm(String permissions) {
        if (StringUtils.isEmpty(permissions)) {
            return false;
        }
//        LoginUser loginUser = SecurityUtils.getLoginUser();
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
            return false;
        }
        Set<String> authorities = loginUser.getPermissions();
        for (String permission : permissions.split(PERMISSION_DELIMETER))
        {
            if (permission != null && hasPermissions(authorities, permission))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断是否包含权限
     *
     * @param permissions 权限列表
     * @param permission  权限字符串
     * @return 用户是否具备某权限
     */
    private boolean hasPermissions(Set<String> permissions, String permission) {
        return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
    }

    @Override
    public void setFilterObject(Object filterObject) {
        this.filterObject = filterObject;
    }

    @Override
    public Object getFilterObject() {
        return this.filterObject;
    }

    @Override
    public void setReturnObject(Object returnObject) {
        this.returnObject = returnObject;
    }

    @Override
    public Object getReturnObject() {
        return this.returnObject;
    }

    @Override
    public Object getThis() {
        return this.target;
    }
}

Spring Security 中,MethodSecurityExpressionRoot 的配置是通过 DefaultMethodSecurityExpressionHandler 来完成的,现在我们自定义了 PermissionSecurityExpressionRoot, 所以,再来一个PermissionSecurityExpressionHandler类继承DefaultMethodSecurityExpressionHandler,如下:

/**
 * @author chenyunzhi
 * @description: 定义handler配置 PermissionSecurityExpressionRoot
 */
@Component
public class PermissionSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
        PermissionSecurityExpressionRoot root = new PermissionSecurityExpressionRoot(authentication);
        root.setTrustResolver(getTrustResolver());
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setRoleHierarchy(getRoleHierarchy());
        return root;
    }
}

然后就可以通过以下注解使用了

@PreAuthorize("hasPerm('com:user:add')")

三. 参考文章

如何在 Spring Security 中自定义权限表达式

security中文文档

作者:神的孩子都在歌唱
本人博客:https://blog.csdn.net/weixin_46654114
转载说明:务必注明来源,附带本人博客连接。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1155348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Thread

Thread 线程启动线程第一种创建线程线程的第二种创建方式使用匿名内部类完成线程的两种创建 Thread API线程的优先级线程提供的静态方法守护线程用户线程和守护线程的区别体现在进程结束时 多线并发安全问题同步块 线程 启动线程 启动线程:调用线程的start方法,而不是直接调用…

新版Idea显示Git提交人信息

新版Idea的类和方法上会展示开发者信息 不想展示的话可以做以下配置&#xff1a;

数据查找(search)-----散列表(哈希表)

目录 前言 一.散列表&#xff08;哈希表&#xff09;基本概念 二.哈希函数的构造 构造原则 构造方法 1.直接定址法 2.除留余数法 3.数字分析法 三.地址冲突 四.处理冲突的方法 开放定址法 1.线性探测法 2.二次探测法 3.伪随机探测法 链地址法 五.散列表的查找 前…

路由器如何设置IP地址

IP地址是计算机网络中的关键元素&#xff0c;用于标识和定位设备和主机。在家庭或办公室网络中&#xff0c;路由器起到了连接内部设备和外部互联网的关键作用。为了使网络正常运行&#xff0c;需要正确设置路由器的IP地址。本文将介绍如何设置路由器的IP地址&#xff0c;以确保…

P3983 赛斯石(赛后强化版),背包

题目背景 白露横江&#xff0c;水光接天&#xff0c;纵一苇之所如&#xff0c;凌万顷之茫然。——苏轼真程海洋近来需要进购大批赛斯石&#xff0c;你或许会问&#xff0c;什么是赛斯石&#xff1f; 首先我们来了解一下赛斯&#xff0c;赛斯是一个重量单位&#xff0c;我们用…

谷歌财报解读:基本盘守成有余,云业务进取不足?

科技巨头的AI之战持续上演&#xff0c;而财报季是一窥AI成色的重要窗口。 谷歌和微软这对在多个领域均正面对决的科技巨头&#xff0c;又在同一日发布了财报&#xff0c;而这次相比上季度&#xff0c;战局似乎迎来了反转。 上季度&#xff0c;谷歌不仅成功抵御了Bing联手ChatG…

从歌尔股份三季报中,读懂消费电子的“增程式”复苏

第三季度财报季前夕&#xff0c;消费电子板块可谓利好不断。 9月&#xff0c;苹果、华为纷纷发布新品&#xff0c;大厂高端机型带动购机热潮重现。同时&#xff0c;Meta推出的MR头显Quest3、智能眼镜Ray-Ban等XR新产品也备受消费者期待&#xff0c;大摩预测Quest 3今年出货量将…

面试150题做题记录

面试150题做题记录 题目1: 合并两个有序数组 题目1: 合并两个有序数组 题目&#xff1a;https://leetcode.cn/problems/merge-sorted-array/?envTypestudy-plan-v2&envIdtop-interview-150 最优思路&#xff1a;利用原有数列的单调性质&#xff0c;从右往左遍历&#xff…

驱动day10作业

基于platform驱动模型完成LED驱动的编写 驱动程序 #include <linux/init.h> #include <linux/module.h> #include<linux/platform_device.h> #include<linux/mod_devicetable.h> #include<linux/of.h> #include<linux/of_gpio.h> #inclu…

本地部署 ChatGLM3

本地部署 ChatGLM3 ChatGLM3 介绍ChatGLM3 Github 地址部署 ChatGLM3运行综合 Demo对话模式工具模式代码解释器模式 API 部署 ChatGLM3 介绍 ChatGLM3 是智谱AI和清华大学 KEG 实验室联合发布的新一代对话预训练模型。ChatGLM3-6B 是 ChatGLM3 系列中的开源模型&#xff0c;在…

(四)docker:为mysql和java jar运行环境创建同一网络,容器互联

看了很多资料&#xff0c;说做互联的一个原因是容器内ip不固定&#xff0c;关掉重启后如果有别的容器启动&#xff0c;之前的ip会被占用&#xff0c;所以做互联创建一个网络&#xff0c;让几个容器处于同一个网络&#xff0c;就可以互联还不受关闭再启动ip会改变的影响&#xf…

C++的拷贝构造函数

目录 拷贝构造函数一、为什么用拷贝构造二、拷贝构造函数1、概念2、特征1. 拷贝构造函数是构造函数的一个重载形式。2. 拷贝构造函数的参数3. 若未显式定义&#xff0c;编译器会生成默认的拷贝构造函数。4. 拷贝构造函数典型调用场景 拷贝构造函数 一、为什么用拷贝构造 日期…

FedGNN: Federated Graph Neural Network for Privacy-Preserving Recommendation

FedGNN&#xff1a;用于隐私保护推荐的联邦图神经网络 参考笔记 ICML-21-workshop 本文的主要创新工作 在具有局部差分隐私的模型训练中保护模型梯度&#xff0c;并提出一种伪交互项目采样技术来保护用户与之交互的项目。提出了一种保护隐私的用户-项目图扩展方法&#xff0…

函数总结

一、main函数 //argc 统计命令行传参的个数 //argv 保存命令行传的具体参数,每个参数当做字符串来存储&#xff0c;const是为了不让main函数修改argv数组里的内容 1.1值传递 此为值传递;形参的值改变不影响实参的值 1.2 地址传递 形参拿到的是实参的地址&#xff0c;实际操…

2023云曦秋季期中考

web 1z_php 看到有点晕&#xff0c;根本分不清0和o <?php // Yeedo told you to study hard! echo !(!(!(include "flag.php"|| (!error_reporting(0))|| !isset($_GET[OoO])|| !isset($_GET[0o0])|| ($_GET[OoO] 2023) //检查 "OoO" 参数是否等于…

使用simple_3dviz进行三维模型投影

【版权声明】 本文为博主原创文章&#xff0c;未经博主允许严禁转载&#xff0c;我们会定期进行侵权检索。 更多算法总结请关注我的博客&#xff1a;https://blog.csdn.net/suiyingy&#xff0c;或”乐乐感知学堂“公众号。 本文章来自于专栏《Python三维模型处理基础》的系列文…

Linux———— 运算命令

Shell与其他编程语言一样&#xff0c;支持多种类型的运算符&#xff0c;包括&#xff1a; 算术运算符&#xff1a;用于执行数学运算&#xff0c;例如加法、减法、乘法和除法。 关系运算符&#xff1a;用于比较两个值之间的关系&#xff0c;例如相等、大于、小于等。 布尔运算…

Vue路由(router)的安装和使用

Vue路由&#xff08;router&#xff09;的安装和使用 安装vue-router插件 第一步&#xff1a;在CMD窗口中&#xff0c;使用命令跳转到vue的安装路径下第二步&#xff1a;输入命令&#xff1a;npm i vue-router3 vue2 要安装 vue-router3 npm i vue-router3 vu3 要安装 vue-ro…

AI绘画|midjourney入门保姆教程,30秒出专业大片,国内直接使用

同学们&#xff0c;之前大家想用midjourney还需要魔法上网和很复杂的注册配置&#xff0c;现在微信里就能使用midjourney了&#xff0c; 还支持中文&#xff0c;大家赶紧来试试吧。 AI写稿专家 www.promptspower.comhttp://www.promptspower.com 我们还给大家提供了各个行业的…

【C++】多态 ⑧ ( 验证指向 虚函数表 的 vptr 指针 | 对比定义了虚函数的类和没有定义虚函数类的大小 )

文章目录 一、验证指向 虚函数表 的 vptr 指针 是否存在1、虚函数表与 vptr 指针由来2、虚函数类与普通函数类对比 - 多出了 vptr 指针的大小 对比 定义了 虚函数 的类 与 没有定义虚函数的类 的大小 , 其它成员都相同 , 定义了虚函数的类多出了 4 字节 , 多出的 4 字节就是 vp…