RuoYi-Vue-Plus (SaToken 注解鉴权)

news2024/11/16 1:40:52

一、SaInterceptor  注解鉴权和路由拦截鉴权

拦截器:SaInterceptor

实现类位置: cn.dev33.satoken.interceptor.SaInterceptor

功能:Sa-Token 综合拦截器,提供注解鉴权和路由拦截鉴权能力

/**
 * 创建一个 Sa-Token 综合拦截器,默认带有注解鉴权能力 
 * @param auth 认证函数,每次请求执行 
 */
public SaInterceptor(SaParamFunction<Object> auth) {
   this.auth = auth;
}

SaInterceptor 有参构造的参数,SaParamFunction: 

单形参、无返回值的函数式接口,方便开发者进行 lambda 表达式风格调用,如下@FunctionalInterface 标注
@FunctionalInterface
public interface SaParamFunction<T> {
	
	/**
	 * 执行的方法 
	 * @param r 传入的参数 
	 */
	void run(T r);
	
}

/**
 * Sa-Token 综合拦截器,提供注解鉴权和路由拦截鉴权能力 
 * 
 * @author click33
 * @since 1.31.0
 */
public class SaInterceptor implements HandlerInterceptor {

	/**
	 * 是否打开注解鉴权,配置为 true 时注解鉴权才会生效,配置为 false 时,即使写了注解也不会进行鉴权
	 */
	public boolean isAnnotation = true;
	
	/**
	 * 认证函数:每次请求执行 
	 * <p> 参数:路由处理函数指针 
	 */
	public SaParamFunction<Object> auth = handler -> {};

	/**
	 * 创建一个 Sa-Token 综合拦截器,默认带有注解鉴权能力 
	 */
	public SaInterceptor() {
	}

	/**
	 * 创建一个 Sa-Token 综合拦截器,默认带有注解鉴权能力 
	 * @param auth 认证函数,每次请求执行 
	 */
	public SaInterceptor(SaParamFunction<Object> auth) {
		this.auth = auth;
	}

	/**
	 * 设置是否打开注解鉴权:配置为 true 时注解鉴权才会生效,配置为 false 时,即使写了注解也不会进行鉴权
	 * @param isAnnotation /
	 * @return 对象自身
	 */
	public SaInterceptor isAnnotation(boolean isAnnotation) {
		this.isAnnotation = isAnnotation;
		return this;
	}
	
	/**
	 * 写入 [ 认证函数 ]: 每次请求执行
	 * @param auth / 
	 * @return 对象自身 
	 */
	public SaInterceptor setAuth(SaParamFunction<Object> auth) {
		this.auth = auth;
		return this;
	}
	
	
	// ----------------- 验证方法 ----------------- 

	/**
	 * 每次请求之前触发的方法 
	 */
	@Override
	@SuppressWarnings("all")
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		try {
			// 这里必须确保 handler 是 HandlerMethod 类型时,才能进行注解鉴权
			if(isAnnotation && handler instanceof HandlerMethod) {
				// 获取此请求对应的 Method 处理函数 
				Method method = ((HandlerMethod) handler).getMethod();
				// 如果此 Method 或其所属 Class 标注了 @SaIgnore,则忽略掉鉴权
				if(SaStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) {
					// 注意这里直接就退出整个鉴权了,最底部的 auth.run() 路由拦截鉴权也被跳出了
					return true;
				}
				// 执行注解鉴权
				SaStrategy.instance.checkMethodAnnotation.accept(method);
			}
			
			// Auth 路由拦截鉴权校验
			auth.run(handler);
		} catch (StopMatchException e) {
			// StopMatchException 异常代表:停止匹配,进入Controller
		} catch (BackResultException e) {
			// BackResultException 异常代表:停止匹配,向前端输出结果
			// 		请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 back 前自行设置 Content-Type 为 application/json
			// 		例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
			if(response.getContentType() == null) {
				response.setContentType("text/plain; charset=utf-8"); 
			}
			response.getWriter().print(e.getMessage());
			return false;
		}
		
		// 通过验证 
		return true;
	}

}

一、@SaIgnore 

忽略认证:表示被修饰的方法或类无需进行注解认证和路由拦截认证
请注意:此注解的忽略效果只针对 SaInterceptor拦截器 和 AOP注解鉴权 生效,对自定义拦截器与过滤器不生效。

拦截器前置处理逻辑:(对上面SaInterceptor  拦截器解读)

  1. 1- preHandle 前置方法中对SaIgnore注解进行了判断,如果标注就忽略鉴权。
// 如果此 Method 或其所属 Class 标注了 @SaIgnore,则忽略掉鉴权
if(SaStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) {
   // 注意这里直接就退出整个鉴权了,最底部的 auth.run() 路由拦截鉴权也被跳出了
   return true;
}
  1. 2-  执行注解鉴权,校验其他注解,
// 拦截器执行注解鉴权代码,主要是调用策略类SaStrategy (是Sa-Token 策略对象,checkMethodAnnotation 方法
SaStrategy.instance.checkMethodAnnotation.accept(method);

checkMethodAnnotation: 

	/**
	 * 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
	 */
	public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> {

		// 先校验 Method 所属 Class 上的注解
		instance.checkElementAnnotation.accept(method.getDeclaringClass());

		// 再校验 Method 上的注解
		instance.checkElementAnnotation.accept(method);
	};

 checkElementAnnotation:

·	/**
	 * 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
	 */
	public SaCheckElementAnnotationFunction checkElementAnnotation = (element) -> {

		// 校验 @SaCheckLogin 注解
		SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.instance.getAnnotation.apply(element, SaCheckLogin.class);
		if(checkLogin != null) {
			SaManager.getStpLogic(checkLogin.type(), false).checkByAnnotation(checkLogin);
		}

		// 校验 @SaCheckRole 注解

。。。。。省略其他

3- 执行SoToken拦截器

Auth 路由拦截鉴权校验: auth.run(handler);

实际执行是:SaTokenConfig 配置类中的 handler 处理,即如下
  public void addInterceptors(InterceptorRegistry registry) {
        // 注册路由拦截器,自定义验证规则
        registry.addInterceptor(new SaInterceptor(new SaParamFunction<Object>() {
                @Override
                public void run(Object handler) {
                    AllUrlHandler allUrlHandler = SpringUtils.getBean(AllUrlHandler.class);
                    // 登录验证 -- 排除多个路径
                    SaRouter
                        // 获取所有的
                        .match(allUrlHandler.getUrls())
                        // 对未排除的路径进行检查
                        .check(() -> {
                            // 检查是否登录 是否有token
                            StpUtil.checkLogin();
                        });
                }
            })).addPathPatterns("/**")
            // 排除不需要拦截的路径
            .excludePathPatterns(securityProperties.getExcludes());
    }

 拦截器的实现类:

二、@SaCheckLogin

上面 preHandle 方法中,

	// 执行注解鉴权
				SaStrategy.instance.checkMethodAnnotation.accept(method);

点击进去 checkMethodAnnotation:

	/**
	 * 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
	 */
	public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> {

		// 先校验 Method 所属 Class 上的注解
		instance.checkElementAnnotation.accept(method.getDeclaringClass());

		// 再校验 Method 上的注解
		instance.checkElementAnnotation.accept(method);
	};

调用 checkElementAnnotation  进行 SaCheckLogin注解的校验:

1-先从元素上获取注解

2- SaManager.getStpLogic(checkLogin.type(), false) 获取到StpLogic,进行 SaCheckLogin 注解的登录鉴权

Sa-Token :权限认证,逻辑实现类(所有SoToken注解认证都在这边实现)

	/**
	 * 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
	 */
	public SaCheckElementAnnotationFunction checkElementAnnotation = (element) -> {

		// 校验 @SaCheckLogin 注解
		SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.instance.getAnnotation.apply(element, SaCheckLogin.class);
		if(checkLogin != null) {
			SaManager.getStpLogic(checkLogin.type(), false).checkByAnnotation(checkLogin);
		}
。。。。。省略下文

三、@SaCheckRole  @SaCheckPermission

功能:

  • SaCheckRole  :校验角色注解
  • SaCheckPermission :权限校验注解

下面用 SaCheckRole  来进行解读:

策略类中 SaManager.getStpLogic(checkLogin.type(), false) 返回 StpLogic 对象

调用了checkByAnnotation 方法,进行匹配角色:如下

SaManager.getStpLogic(checkLogin.type(), false).checkByAnnotation(checkLogin);
	/**
	 * 根据注解 ( @SaCheckRole ) 鉴权
	 *
	 * @param at 注解对象 
	 */
	public void checkByAnnotation(SaCheckRole at) {
		String[] roleArray = at.value();
        //1-满足所有角色才能校验通过
		if(at.mode() == SaMode.AND) {
			this.checkRoleAnd(roleArray);	
		} else {
        //2- 满足任意一个角色就能通过
			this.checkRoleOr(roleArray);	
		}
	}

在 上面代码 checkRoleAnd  和  checkRoleOr 中都调用了 

getRoleList 方法,该方法是用于查询角色权限

其查询权限的接口有2个实现类

其中 SaPermissionImpl 实现类,是我这在SaToken配置类中配置


    /**
     * 权限接口实现(使用bean注入方便用户替换)
     */
    @Bean
    public StpInterface stpInterface() {
        return new SaPermissionImpl();
    }

 实现了:

菜单权限查询方法getPermissionList

角色权限查询方法getRoleList

public class SaPermissionImpl implements StpInterface {

    /**
     * 获取菜单权限列表
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        LoginUser loginUser = LoginHelper.getLoginUser();
        UserType userType = UserType.getUserType(loginUser.getUserType());
        if (userType == UserType.SYS_USER) {
            return new ArrayList<>(loginUser.getMenuPermission());
        } else if (userType == UserType.APP_USER) {
            // 其他端 自行根据业务编写
        }
        return new ArrayList<>();
    }

    /**
     * 获取角色权限列表
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        LoginUser loginUser = LoginHelper.getLoginUser();
        UserType userType = UserType.getUserType(loginUser.getUserType());
        if (userType == UserType.SYS_USER) {
            return new ArrayList<>(loginUser.getRolePermission());
        } else if (userType == UserType.APP_USER) {
            // 其他端 自行根据业务编写
        }
        return new ArrayList<>();
    }
}

四、@SaCheckSafe

作用:二级认证(基于redis实现)

使用场景:

比如代码托管平台的仓库删除操作,尽管我们已经登录了账号,当我们点击 [删除] 按钮时,还是需要再次输入一遍密码,这么做主要为了两点:

  1. 保证操作者是当前账号本人。
  2. 增加操作步骤,防止误删除重要数据。

 SaToken 关于二级认证的文档如下:(很详细)二级认证 (sa-token.cc)icon-default.png?t=N7T8https://sa-token.cc/doc.html#/up/safe-auth

 简单使用方法:

  • 先调用StpUtil.openSafe("add", 600),在redis 缓存中保存数据
  • 之后在方法上面标注@RequestMapping("add") 注解即可开启

其代码实现逻辑如下注释:

	/**
	 * 1-校验:检查当前会话是否已通过指定业务的二级认证,如未通过则抛出异常
	 *
	 * @param service 业务标识  
	 */
	public void checkSafe(String service) {
        //获取token
		String tokenValue = getTokenValue();
        //判断token是否有效,否则抛出异常
		if ( ! isSafe(tokenValue, service)) {
			throw new NotSafeException(loginType, tokenValue, service).setCode(SaErrorCode.CODE_11071);
		}
	}



	/**
	 * 2-判断:指定 token 是否处于二级认证时间内
	 *
	 * @param tokenValue Token 值  
	 * @param service 业务标识  
	 * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时 
	 */
	public boolean isSafe(String tokenValue, String service) {
		// 1、如果提供的 Token 为空,则直接视为未认证
		if(SaFoxUtil.isEmpty(tokenValue)) {
			return false;
		}
		
		// 2、如果缓存中可以查询出指定的键值,则代表已认证,否则视为未认证
        //splicingKeySafe(tokenValue, service) 返回如下:
        // 格式:<Token名称>:<账号类型>:<safe>:<业务标识>:<Token值>
		// 形如:satoken:login:safe:important:gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__
		String value = getSaTokenDao().get(splicingKeySafe(tokenValue, service));
		return !(SaFoxUtil.isEmpty(value));
	}

 五、@SaCheckSafe

功能: 账号封禁(基于redis实现)

场景:

  1.         账号封禁
  2.         分类封禁 (封禁部分功能)
  3.         阶梯封禁 (比如按照时间长短封禁、按照等级 1234封禁)
  4.         使用注解封禁,如下:
// 校验当前账号是否被封禁 comment 服务,如果已被封禁会抛出异常,无法进入方法 
@SaCheckDisable("comment")

// 校验当前账号是否被封禁 comment、place-order、open-shop 等服务,指定多个值,只要有一个已被封禁,就无法进入方法 
@SaCheckDisable({"comment", "place-order", "open-shop"})

// 阶梯封禁,校验当前账号封禁等级是否达到5级,如果达到则抛出异常 
@SaCheckDisable(level = 5)


// 分类封禁 + 阶梯封禁 校验:校验当前账号的 comment 服务,封禁等级是否达到5级,如果达到则抛出异常 
@SaCheckDisable(value = "comment", level = 5)

API:账号封禁 (sa-token.cc)icon-default.png?t=N7T8https://sa-token.cc/doc.html#/up/disable

代码逻辑:

	/**
	 * 校验:指定账号的指定服务,是否已被封禁到指定等级(如果已经达到,则抛出异常)
	 * 
	 * @param loginId 指定账号id 
	 * @param service 指定封禁服务 
	 * @param level 封禁等级 (只有 封禁等级 ≥ 此值 才会抛出异常)
	 */
	public void checkDisableLevel(Object loginId, String service, int level) {
		// 1、先前置检查一下这个账号是否被封禁了
		String value = getSaTokenDao().get(splicingKeyDisable(loginId, service));
		if(SaFoxUtil.isEmpty(value)) {
			return;
		}

		// 2、再判断被封禁的等级是否达到了指定级别
		Integer disableLevel = SaFoxUtil.getValueByType(value, int.class);
		if(disableLevel >= level) {
			throw new DisableServiceException(loginType, loginId, service, disableLevel, level, getDisableTime(loginId, service))
					.setCode(SaErrorCode.CODE_11061);
		}
	}

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

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

相关文章

一句话或一张图讲清楚系列之——IDELAYE2的用法

主要参考&#xff1a; Xilinx IDELAYE2应用笔记及仿真实操-CSDN博客 xilinx原语介绍及仿真——IDELAYE2 & IDELAYCTRL_idelayctrl原语使用说明-CSDN博客 1 原理 IDELAYE2一般用于对输入lvds高速信号进行延时微调&#xff0c;可以把时钟和数据都单独微调&#xff1b;如果数…

Spring Cloud学习笔记(Feigh):简介,实战简单样例

这是本人学习的总结&#xff0c;主要学习资料如下 - 马士兵教育 1、Netflix Feign简介2、Open Feign的简单样例2.1、dependency2.2、代码样例 1、Netflix Feign简介 Netfilx Feign是用来帮助发送远程服务的&#xff0c;它让开发者觉得调用远程服务就像是调用本地方法一样&…

【第4讲】XTuner 微调 LLM:1.8B、多模态、Agent

目录 1 简介2 基础知识2.1 finetune简介2.2 xtuner简介2.2.1 技术架构2.2.2 快速上手xtuner 2.3 8GB显存玩转LLM&#xff08;intern1.8b&#xff09;2.3.1 flash attention vs deepspeed zero2.3.2 相关版本更新和使用 2.4 多模态LLM2.4.1 多模态LLaVA基本原理简介2.4.2 快速上…

【xhs爬虫软件】把小红书博主发布笔记API接口user_posted接口封装成GUI采集工具!

用Python开发的爬虫采集软件&#xff0c;可自动抓取小红书博主的已发布笔记。 小红书的已发布笔记接口URL&#xff1a; # 请求地址 posted_url https://edith.xiaohongshu.com/api/sns/web/v1/user_posted开发者模式分析过程&#xff1a; 进而封装成GUI界面软件&#xff0c;…

Vue 3中的ref和toRefs:响应式状态管理利器

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

05_Flutter屏幕适配

05_Flutter屏幕适配 一.屏幕适配方案 通过指定基准屏宽度&#xff0c;进行适配&#xff0c;基准屏宽度取决于设计图的基准宽度&#xff0c;以iphone 14 pro max为例&#xff0c; devicePixelRatio 物理宽度 / 逻辑宽度(基准宽度) iphone 14 pro max的物理尺寸宽度为1290&…

opencv_5_图像像素的算术操作

方法1&#xff1a;调用库函数 void ColorInvert::mat_operator(Mat& image) { Mat dst; Mat m Mat::zeros(image.size(), image.type()); m Scalar(2, 2, 2); multiply(image, m, dst); m1 Scalar(50,50, 50); //divide(image, m, dst); //add(im…

【web开发网页制作】html+css家乡长沙旅游网页制作(4页面附源码)

家乡长沙网页制作 涉及知识写在前面一、网页主题二、网页效果Page1、主页Page2、历史长沙Page3、著名人物Page4、留言区 三、网页架构与技术3.1 脑海构思3.2 整体布局3.3 技术说明书 四、网页源码HtmlCSS 五、源码获取5.1 获取方式 作者寄语 涉及知识 家乡长沙网页制作&#x…

promise笔记

1.介绍 之前的异步编程都是回调函数&#xff08;数据库操作、ajax、定时器、fs读取文件 &#xff09; promise是es6异步编程新的解决方案&#xff0c;是一个构造函数 优点&#xff1a;支持链式调用&#xff0c;可以解决回调地狱&#xff0c;可以指定回调函数 2.使用 functio…

Dubbo 和 Spring Cloud 的区别

根据微服务架构在各方面的要素&#xff0c;看看 Spring Cloud 和 Dubbo 都提供了哪些支持。 使用 Dubbo 构建的微服务架构就像组装电脑&#xff0c;各环节我们的选择自由度很高&#xff0c;但是最终结果很有可能因为一条内存质量不行就点不亮了&#xff0c;总是让人不怎么放心…

Spring Kafka—— KafkaListenerEndpointRegistry 隐式注册分析

由于我想在项目中实现基于 Spring kafka 动态连接 Kafka 服务&#xff0c;指定监听 Topic 并控制消费程序的启动和停止这样一个功能&#xff0c;所以就大概的了解了一下 Spring Kafka 的几个重要的类的概念&#xff0c;内容如下&#xff1a; ConsumerFactory 作用&#xff1a;…

探索数学语言模型的前沿进展——人工智能在数学教育和研究中的应用

数学一直被认为是科学的基石&#xff0c;对于推动技术进步和解决现实世界问题具有重要意义。然而&#xff0c;传统的数学问题解决方式正面临着数字化转型的挑战。MLMs的出现&#xff0c;预示着数学学习和研究方式的一次革命。 MLMs&#xff0c;包括预训练语言模型&#xff08;…

STM32F1串口

文章目录 1 数据通信的基础概念1.11.21.31.41.5 2 串口(RS-232&#xff09;2.12.22.32.42.5 3 STM32的USART3.13.23.33.53.9 USART寄存器介绍 4 HAL库外设初始化MSP回调机制5 HAL库中断回调机制6 USART/UART异步通信配置步骤 &#xff08;包括HAL库相关函数&#xff09;6.16.26…

SDN基础知识

&#x1f308;个人主页&#xff1a;小新_- &#x1f388;个人座右铭&#xff1a;“成功者不是从不失败的人&#xff0c;而是从不放弃的人&#xff01;”&#x1f388; &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f3c6;所属专栏&#xff1…

SQLite的DBSTAT 虚拟表(三十六)

返回&#xff1a;SQLite—系列文章目录 上一篇:SQLite运行时可加载扩展(三十五&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 1. 概述 DBSTAT 虚拟表是一个只读的同名虚拟表&#xff0c;返回 有关用于存储内容的磁盘空间量的信息 的 SQLite 数据库。 示例用例…

【数据结构(邓俊辉)学习笔记】绪论03——递归分析

文章目录 意图目标1. 线性递归数组求和线性递归减而治之 2. 递归分析递归跟踪递推方程典型递推方程 3. 递归模式多递归基多向递归 4. 递归消除空间成本尾递归及其消除 5. 二分递归分而治之数组求和 6 . 效率7. 算法设计优化总结前n项计算算法 意图 数据结构中经常用到递归&…

VScode配置MySQL

1、进入官网&#xff0c;下载MySQL 地址&#xff1a;dev.mysql.com/downloads/mysql/ ZIP方式下载&#xff0c;选择本地的路径进行解压。 2、配置环境变量 形如下方的路径&#xff1a; D:\software\Mysql\mysql-8.3.0-winx64\bin 即是解压位置后文件夹下的bin文件路径 3、初…

在 VSCode 中运行 C#

文章目录 1.为何选择VSCode而不是VS2.操作步骤2.1 安装.NET2.2 安装扩展插件2.2.1 C#2.2.2 Code Runner 3.新建工程HelloCsharp 1.为何选择VSCode而不是VS VS实在是太“重”了&#xff0c;如果只是写一些简单控制台程序进行调试&#xff0c;则完全没必要 2.操作步骤 2.1 安装…

【前端】vue3树形组件使用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、树形组件简介二、树形组件使用三、总结 前言 随着开发语言及人工智能工具的普及&#xff0c;使得越来越多的人学习使用vue前端工具&#xff0c;本文主要是…

第十、十一章 折线图 + 地图 + 柱状图的绘制

第十章 折线图的绘制 官网&#xff1a;pyecharts - A Python Echarts Plotting Library built with love. 画廊官网&#xff1a;Document 懒人工具&#xff1a;懒人工具-手机APP工具下载-手机软件下载大全 - 173软件站 (ab173.com) 导学 json 定义 &#xff08;1&#xff…