若依权限校验源码分析

若依的权限校验实现原理就是AOP+自定义注解,代码并不多,debug跟一遍之后很容易理解,详细流程整理如下

用到的类

  • 注解类RequiresLogin、RequiresPermissions、RequiresRoles,分别用于登录认证、权限认证和角色认证
  • 切面类PreAuthorizeAspect,基于 Spring Aop 的注解鉴权
  • 被代理类SysJobController,被代理类就是添加注解的方法所在的类,可以是任意一个类

链路跟踪

在需要鉴权的方法上添加对应的注解
@RequiresPermissions(“monitor:job:list”)表示需要在该方法执行前进行权限认证,参数即具体权限

/**
* 查询定时任务列表
*/
@RequiresPermissions("monitor:job:list")
@GetMapping("/list")
public TableDataInfo list(SysJob sysJob) {
	startPage();
	List<SysJob> list = jobService.selectJobList(sysJob);
	return getDataTable(list);
}

执行切面类

package com.zhy.common.security.aspect;

import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import com.zhy.common.security.annotation.RequiresLogin;
import com.zhy.common.security.annotation.RequiresPermissions;
import com.zhy.common.security.annotation.RequiresRoles;
import com.zhy.common.security.auth.AuthUtil;

/**
 * 基于 Spring Aop 的注解鉴权
 *
 * @author kong
 */
@Aspect
@Component
public class PreAuthorizeAspect
{
    /**
     * 构建
     */
    public PreAuthorizeAspect()
    {
    }

    /**
     * 定义AOP签名 (切入所有使用鉴权注解的方法)
     */
    public static final String POINTCUT_SIGN = " @annotation(com.zhy.common.security.annotation.RequiresLogin) || "
            + "@annotation(com.zhy.common.security.annotation.RequiresPermissions) || "
            + "@annotation(com.zhy.common.security.annotation.RequiresRoles)";

    /**
     * 声明AOP签名
     */
    @Pointcut(POINTCUT_SIGN)
    public void pointcut()
    {
    }

    /**
     * 环绕切入
     *
     * @param joinPoint 切面对象
     * @return 底层方法执行后的返回值
     * @throws Throwable 底层方法抛出的异常
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable
    {
        // 注解鉴权
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        checkMethodAnnotation(signature.getMethod());
        try
        {
            // 执行原有逻辑
            Object obj = joinPoint.proceed();
            return obj;
        }
        catch (Throwable e)
        {
            throw e;
        }
    }

    /**
     * 对一个Method对象进行注解检查
     */
    public void checkMethodAnnotation(Method method)
    {
        // 校验 @RequiresLogin 注解
        RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
        if (requiresLogin != null)
        {
            AuthUtil.checkLogin();
        }

        // 校验 @RequiresRoles 注解
        RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);
        if (requiresRoles != null)
        {
            AuthUtil.checkRole(requiresRoles);
        }

        // 校验 @RequiresPermissions 注解
        RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
        if (requiresPermissions != null)
        {
            AuthUtil.checkPermi(requiresPermissions);
        }
    }
}

debug进到around方法
signature.getMethod()获取到添加注解的方法

在这里插入图片描述

进到checkMethodAnnotation方法
拿到注解所在方法后首先判断该方法上添加了哪些注解,可以有一个或多个,此处只添加了@RequiresPermissions注解

在这里插入图片描述

进入校验权限认证的方法
权限验证工具类跟进到权限验证逻辑实现类

在这里插入图片描述

首先是调用SecurityContextHolder.setPermission
在这里插入图片描述

debug进入set方法
在这里插入图片描述

这里ROLE_PERMISSION是在SecurityConstants即权限相关通用常量类中定义的,该类中的其他属性如DETAILS_USER_ID、DETAILS_USERNAME、USER_KEY、LOGIN_USER等都是在用户登录时进行赋值

进入set方法,这里的getLocalMap最终获取的是TransmittableThreadLocal类型的THREAD_LOCAL集合,该集合中也存放了上面说的SecurityConstants类中的其他属性。TransmittableThreadLocal相比ThreadLocal而言优点在于能够处理处理父子线程变量不能共用的情况,ThreadLocal是跟当前线程挂钩的,所以脱离当前线程它就起不了作用

在这里插入图片描述

回到checkPermi方法中,这里判断为true,因为@RequiresPermissions注解中的logical默认值就是Logical.AND枚举字段,Logical枚举类有两个属性,AND和OR,表示验证用户所有的权限还是部分权限,继续执行checkPermiAnd方法

在这里插入图片描述

checkPermiAnd方法进来后首先获取当前账号的权限列表,debug进去

在这里插入图片描述

可以看到这里获取到admin账号的权限是"* : * : *",这个值也是登录流程中进行赋值

在这里插入图片描述

现在账号权限有了,执行业务逻辑需要的权限也有了,接下来就是进行比对了,如果通过那么AOP的任务就完成了,可以继续执行业务逻辑,不通过的会会抛出异常

在这里插入图片描述

debug进入hasPermi方法,来看看是怎么进行比对的,这里传进来的两个参数authorities和permission就是需要比对的值,值分别为"* : * : *“和"monitor:job:list”,可以看到这里用到了java8Stream中提供的filter过滤器,StringUtils::hasText过滤条件使用了双冒号运算符,表示遍历authorities集合中的元素作为参数调用 StringUtils工具类中的 hasText方法。anyMatch()表示进行匹配,其中x表示authorities权限列表中的元素,ALL_PERMISSION.contains(x)表示在 ALL_PERMISSION字符串中是否存在字符串x,PatternMatchUtils.simpleMatch(x, permission)) 表示 permission 中是否存在与 x 相匹配的子字符串。这里用到了或运算,由于ALL_PERMISSION是个字符串,值就是"* : * : *",所以ALL_PERMISSION.contains(x)永远为true,此处会发生或运算短路,意味着PatternMatchUtils.simpleMatch(x, permission)) 不会执行,所以return true

在这里插入图片描述

到此整个权限验证逻辑执行完毕

<think>好的,我现在需要给用户讲解若依微服务Cloud框架中的@RequiresPermissions("system:config:list")这个注解。用户之前已经问过切面相关的知识,可能对AOP有一定了解。这个注解看起来和权限控制有关,结合之前的切面例子,我需要解释清楚它的作用、实现原理以及使用场景。 首先,我应该确认这个注解在若依框架中的具体用途。根据常见的权限控制设计,类似@RequiresPermissions的注解通常用于方法级别的权限验证,确保用户拥有指定权限才能访问某个接口或方法。用户提到的“system:config:list”可能对应后台管理系统中的配置列表权限。 接下来,需要解释这个注解的工作原理。可能涉及若依框架如何利用AOP切面在方法执行前进行权限校验。这里需要联系用户之前提到的切面例子,比如使用@Before通知,在方法调用前检查用户权限。 然后,要说明注解的参数结构,比如权限字符串的层级划分,例如system:config:list可能代表系统模块下的配置管理的列表权限。这部分需要详细说明权限的层级设计,帮助用户理解如何组织权限代码。 还需要提供使用示例,展示在Controller层的方法上添加该注解的效果,比如控制某个API的访问权限。同时,可以对比不使用注解的情况,说明权限校验如何集中管理,避免代码重复。 可能用户还想知道如果权限校验不通过会怎样,应该提到常见的处理方式,比如抛出异常、返回错误码或跳转到登录页面,但需要具体到若依框架的实现,比如返回JSON错误信息。 另外,需要确保回答结构清晰,分点说明注解的作用、原理、参数设计、使用场景和异常处理。结合之前的切面解释,保持一致性,帮助用户建立知识连接。 最后,检查是否有遗漏点,比如是否需要说明如何自定义权限校验逻辑,或者若依框架中相关的配置步骤,但根据用户当前的问题,可能不需要过于深入,保持回答简洁明了即可。</think>### **@RequiresPermissions("system:config:list") 注解详解** 在若依微服务Cloud框架中,`@RequiresPermissions` 是一个**权限控制注解**,用于标记某个接口或方法需要特定权限才能访问。其核心功能是:**通过权限字符串(如 `system:config:list`)动态校验当前用户是否拥有该权限**,若无权限则拦截请求并返回错误提示。 --- ### **1. 注解的作用** - **权限校验**:在接口被调用时,校验当前登录用户是否拥有 `system:config:list` 权限。 - **接口保护**:防止未授权用户访问敏感操作(如删除数据、查看配置列表等)。 - **与RBAC模型结合**:配合若依的“角色-权限”模型,实现细粒度的权限控制。 --- ### **2. 实现原理** 若依框架通过 **AOP切面 + 自定义注解** 实现权限校验逻辑: 1. **切面拦截**:定义一个切面类,拦截所有被 `@RequiresPermissions` 注解标记的方法。 2. **权限解析**:从注解中提取权限字符串(如 `system:config:list`)。 3. **权限校验**:通过若依的权限服务(如 `SysPermissionService`)查询当前用户是否拥有该权限。 4. **结果处理**: - 有权限 → 放行请求,执行目标方法。 - 无权限 → 抛出 `AccessDeniedException`,返回 `403 Forbidden` 错误。 **示例代码片段(简化版切面逻辑)**: ```java @Aspect @Component public class PermissionAspect { @Autowired private SysPermissionService permissionService; // 拦截所有带 @RequiresPermissions 注解的方法 @Around("@annotation(requiresPermissions)") public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermissions requiresPermissions) throws Throwable { String permission = requiresPermissions.value(); if (!permissionService.hasPermission(permission)) { throw new AccessDeniedException("无权限访问"); } return joinPoint.proceed(); } } ``` --- ### **3. 参数设计:权限字符串的层级** 若依框架的权限字符串通常采用 **模块:功能:操作** 的层级结构,例如: - `system:config:list` → 系统管理模块 / 参数配置功能 / 列表查看操作 - `system:user:add` → 系统管理模块 / 用户管理功能 / 新增用户操作 **优点**: - 清晰划分权限粒度,便于后台权限配置。 - 支持灵活组合权限(如通过角色绑定多个权限字符串)。 --- ### **4. 使用场景示例** 在Controller层标记需要权限控制的接口: ```java @RestController @RequestMapping("/system/config") public class SysConfigController { // 查看参数配置列表(需 system:config:list 权限) @RequiresPermissions("system:config:list") @GetMapping("/list") public TableDataInfo list(SysConfig config) { // 查询配置列表逻辑 } // 删除参数配置(需 system:config:delete 权限) @RequiresPermissions("system:config:delete") @DeleteMapping("/{configId}") public AjaxResult delete(@PathVariable Long configId) { // 删除配置逻辑 } } ``` --- ### **5. 权限不通过时的响应** 若用户无权限访问接口,若依框架会**统一返回JSON格式错误**,例如: ```json { "code": 403, "msg": "无权限访问,请联系管理员授权" } ``` --- ### **6. 对比传统权限校验的优势** | **传统方式** | **@RequiresPermissions 方式** | |----------------------------------|---------------------------------------| | 在每个接口方法内手动校验权限 | 通过注解声明权限,代码简洁 | | 权限逻辑与业务代码耦合 | 权限校验由切面统一处理,解耦清晰 | | 修改权限规则需逐个修改接口 | 只需调整切面逻辑或权限字符串定义 | --- ### **总结** `@RequiresPermissions("system:config:list")` 是若依微服务框架中实现**声明式权限控制**的核心注解,通过“权限字符串 + AOP切面”的机制,实现了: 1. **代码简洁**:业务方法无需嵌入权限校验逻辑。 2. **灵活配置**:权限粒度可通过字符串自由定义。 3. **安全可靠**:统一拦截未授权请求,避免越权访问。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

紫荆之后-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值