Sa-Token源码简单阅读

一.权限登录模块包括几个基本子模块:

1.登录。

实现方式大致为:先检验用户名密码是否正确,如正确则在缓存中存入用户信息(一般必须要有用户标识和访问token,或再加一些附加信息如用户的角色权限),再返回访问token给客户端。

2.过滤器,主要通过客户端访问时带的token检验是否有访问url权限。

实现方式大致为:通过客户端访问的url匹配资源类型。一些资源可直接放行,一些资源需要校验登录,一些资源需要校验角色权限。

3.获取角色权限的方式。

因为取权限角色的方式不同,一般权限框架会提供一层抽象(接口),需要开发者实现。取角色权限未必需要在过滤器中调用,可以在任何需要的时候调用。

根据缓存方式不同可以做一层抽象(接口)。

Sa-Token框架的核心类是cn.dev33.satoken.stp.StpLogic,该类实现了大部分简单逻辑操做功能。

 二.登录。

1.Sa-token默认登录操作cn.dev33.satoken.stp.StpLogic#login(Object, SaLoginModel),第一个参数为自定义用户的凭证

cn.dev33.satoken.stp.StpLogic#login==>cn.dev33.satoken.stp.StpLogic#createLoginSession

/**
	 * 创建指定账号id的登录会话
	 * @param id 登录id,建议的类型:(long | int | String)
	 * @param loginModel 此次登录的参数Model 
	 * @return 返回会话令牌 
	 */
	public String createLoginSession(Object id, SaLoginModel loginModel) {
		
		// ------ 前置检查
		SaTokenException.throwByNull(id, "账号id不能为空");
		
		// ------ 1、初始化 loginModel 
		SaTokenConfig config = getConfig();
		loginModel.build(config);
		
		// ------ 2、分配一个可用的 Token  
		String tokenValue = distUsableToken(id, loginModel);
		
		// ------ 3. 获取 User-Session , 续期 
		SaSession session = getSessionByLoginId(id, true);
		session.updateMinTimeout(loginModel.getTimeout());
		
		// 在 User-Session 上记录token签名 
		session.addTokenSign(tokenValue, loginModel.getDeviceOrDefault());
		
		// ------ 4. 持久化其它数据 
		// token -> id 映射关系  
		saveTokenToIdMapping(tokenValue, id, loginModel.getTimeout());

		// 写入 [token-last-activity] 
		setLastActivityToNow(tokenValue); 

		// $$ 发布事件:账号xxx 登录成功 
		SaTokenEventCenter.doLogin(loginType, id, tokenValue, loginModel);

		// 检查此账号会话数量是否超出最大值 
		if(config.getMaxLoginCount() != -1) {
			logoutByMaxLoginCount(id, session, null, config.getMaxLoginCount());
		}
		
		// 返回Token 
		return tokenValue;
	}

 上面第2步生成token后,第3步底层在缓存中添加token和session对象的映射。

 上面第4步底层又存入了token和用户凭证的映射关系

 第三方框架snowy在登录时又在缓存中存了用户权限角色基本信息,方便单点登录时取权限角色信息(存在下面session的dataMap中)

 底层先在缓存创建一个新的session在缓存中,在更新了一次数据。调用了2次缓存操作

三.过滤器。

过滤器配置源码

package com.wisdomcity.laian.river.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.zy.genius.geomatics.basic.ResultCode;
import com.wisdomcity.laian.river.utils.GlobalExceptionUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import vip.xiaonuo.auth.core.enums.SaClientTypeEnum;
import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
import vip.xiaonuo.common.pojo.CommonResult;

import java.util.List;

@Slf4j
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
    /**
     * 注册Sa-Token的注解拦截器,打开注解式鉴权功能
     * <p>
     * 注解的方式有以下几中,注解既可以加在接口方法上,也可加在Controller类上:
     * 1.@SaCheckLogin: 登录认证 —— 只有登录之后才能进入该方法(常用)
     * 2.@SaCheckRole("admin"): 角色认证 —— 必须具有指定角色标识才能进入该方法(常用)
     * 3.@SaCheckPermission("user:add"): 权限认证 —— 必须具有指定权限才能进入该方法(常用)
     * 4.@SaCheckSafe: 二级认证校验 —— 必须二级认证之后才能进入该方法
     * 5.@SaCheckBasic: HttpBasic认证 —— 只有通过 Basic 认证后才能进入该方法
     * <p>
     * 在Controller中创建一个接口,默认不需要登录也不需要任何权限都可以访问的,只有加了上述注解才会校验
     **/
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关,只是说明哪些接口不需要被拦截器拦截,此处都拦截)
        registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
    }

    @Bean("stpLogic")
    public StpLogic getStpLogic() {
        // 重写Sa-Token的StpLogic,默认客户端类型为B
        return new StpLogic(SaClientTypeEnum.B.getValue());
    }

    @Bean("stpClientLogic")
    public StpLogic getStpClientLogic() {
        // 重写Sa-Token的StpLogic,默认客户端类型为C
        return new StpLogic(SaClientTypeEnum.C.getValue());
    }

    @Bean
    public void rewriteSaStrategy() {
        // 重写Sa-Token的注解处理器,增加注解合并功能
        SaStrategy.me.getAnnotation = AnnotatedElementUtils::getMergedAnnotation;
    }

    /**
     * 权限认证接口实现类,集成权限认证功能
     **/
    @Component
    public static class StpInterfaceImpl implements StpInterface {

        /**
         * 返回一个账号所拥有的权限码集合
         */
        @Override
        public List<String> getPermissionList(Object loginId, String loginType) {
            return StpLoginUserUtil.getLoginUser().getPermissionCodeList();
        }

        /**
         * 返回一个账号所拥有的角色标识集合
         */
        @Override
        public List<String> getRoleList(Object loginId, String loginType) {
            return StpLoginUserUtil.getLoginUser().getRoleCodeList();
        }
    }

    /**
     * 无需登录的接口地址集合
     */
    private static final String[] NO_LOGIN_PATH_ARR = {
            /* 主入口 */
            "/",
            /* 静态资源 */
            "/static/fonts/**",
            "/static/icons/**",
            "/favicon.ico",
            "/doc.html",
            "/webjars/**",
            "/swagger-resources/**",
            "/swagger-ui/**",
            "/v2/api-docs",
            "/v2/api-docs-ext",
            "/v3/api-docs",
            "/v3/api-docs-ext",
            "/configuration/ui",
            "/configuration/security",
            "/ureport/**",
            "/druid/**",
            /* 文件预览 */
            "/file/online/view",
    };

    /**
     * 仅超管使用的接口地址集合
     */
    private static final String[] SUPER_PERMISSION_PATH_ARR = {
            "/auth/session/**",
            "/auth/third/page",
            "/client/user/**",
    };

    /**
     * 注册 [Sa-Token 全局过滤器]
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
                // 指定拦截路由
                .addInclude("/**")

                // 设置鉴权的接口
                .setAuth(r -> {
                    SaRouter.match("/**")
                            .notMatch(CollectionUtil.newArrayList(NO_LOGIN_PATH_ARR))
                            .check(r1 -> StpUtil.checkLogin());
                    SaRouter.match(CollectionUtil.newArrayList(SUPER_PERMISSION_PATH_ARR))
                            .notMatch(CollectionUtil.newArrayList(NO_LOGIN_PATH_ARR))
                            .check(r1 -> StpUtil.checkRole("superAdmin"));
                })

                // 前置函数:在每次认证函数之前执行
                .setBeforeAuth(obj -> {
                    // ---------- 设置跨域响应头 ----------
                    SaHolder.getResponse()

                            // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                            // .setHeader("X-Frame-Options", "SAMEORIGIN")

                            // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
                            .setHeader("X-XSS-Protection", "1; mode=block")
                            // 禁用浏览器内容嗅探
                            .setHeader("X-Content-Type-Options", "nosniff")
                            // 允许指定域访问跨域资源
                            .setHeader("Access-Control-Allow-Origin", "*")
                            // 允许所有请求方式
                            .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
                            // 有效时间
                            .setHeader("Access-Control-Max-Age", "3600")
                            // 允许的header参数
                            .setHeader("Access-Control-Allow-Headers", "*");

                    // 如果是预检请求,则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            // OPTIONS预检请求,不做处理
                            .free(r -> {
                            })
                            .back();
                })

                // 异常处理
                .setError(e -> {
                    // 由于过滤器中抛出的异常不进入全局异常处理,所以必须提供[异常处理函数]来处理[认证函数]里抛出的异常
                    // 在[异常处理函数]里的返回值,将作为字符串输出到前端,此处统一转为JSON输出前端
                    SaResponse saResponse = SaHolder.getResponse();
                    saResponse.setHeader(Header.CONTENT_TYPE.getValue(), ContentType.JSON + ";charset=" + CharsetUtil.UTF_8);
                    CommonResult<String> commonResult = GlobalExceptionUtil.getCommonResult((Exception) e);
                    saResponse.setStatus(commonResult.getCode() < ResultCode.HttpStatusCodeMax
                            ? commonResult.getCode()
                            : HttpStatus.FORBIDDEN.value());
                    return commonResult;
                });
    }
}

cn.dev33.satoken.filter.SaServletFilter#doFilter

在过滤中调用上面beforeAuth,Auth两个策略

@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
		try {
			// 执行全局过滤器 
			SaRouter.match(includeList).notMatch(excludeList).check(r -> {
				beforeAuth.run(null);
				auth.run(null);
			});
			
		} catch (StopMatchException e) {
			
		} catch (Throwable e) {
			// 1. 获取异常处理策略结果 
			String result = (e instanceof BackResultException) ? e.getMessage() : String.valueOf(error.run(e));
			
			// 2. 写入输出流 
			if(response.getContentType() == null) {
				response.setContentType("text/plain; charset=utf-8"); 
			}
			response.getWriter().print(result);
			return;
		}
		
		// 执行 
		chain.doFilter(request, response);
	}

在auth策略中调用StpUtil.checkLogin()检验账号是否登录,底层就是拿前端的token去缓存查询登录凭证。

四.获取权限

1.获取权限在第三方框架snowy中比较简单,因为缓存中已经存有token和session的映射(session中存有用户信息),直接通过token就能在缓存中取到了。

vip.xiaonuo.auth.core.util.StpLoginUserUtil#getLoginUser中调用StpUtil.getTokenSession()会在缓存中取session信息

 

2.开启权限注解后(配置了注解拦截器),调用com.wisdomcity.laian.river.config.SaTokenConfig.StpInterfaceImpl#getPermissionList的时机会在方法或类上添加SaCheckPermission.class注解后调用。

拦截器cn.dev33.satoken.interceptor.SaAnnotationInterceptor#preHandle 

cn.dev33.satoken.strategy.SaStrategy#checkElementAnnotation

 

调用链

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【unity细节】—(Can‘t add script)脚本文件无法拖拽到对象的问题

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity细节和bug ⭐关于脚本文件无法拖拽到对象的问题⭐ 文章目录 ⭐关于脚本文件无法拖拽到对象的…

不得不说的结构型模式-装饰器模式

目录 装饰器模式是什么 下面是装饰器模式的一个通用的类图&#xff1a; 以下是使用C实现装饰器模式的示例代码&#xff1a; 下面是面试中关于桥接器模式的常见的问题&#xff1a; 下面是问题的答案&#xff1a; 装饰器模式是什么 装饰器模式是一种结构型设计模式&#xff…

苹果手机怎么看生产日期?参考方法在这!

案例&#xff1a;怎么查苹果手机买了几年&#xff1f; 【求助&#xff01;我从别人那里买了一部苹果手机&#xff08;非官方&#xff09;&#xff0c;怎么看这个手机用了几年&#xff1f;】 苹果手机作为一款高端手机&#xff0c;备受用户的喜爱。然而&#xff0c;许多用户不知…

Baumer工业相机堡盟工业相机如何通过BGAPI SDK获取每张图像的微秒时间和FrameID(C#)

BGAPI SDK获取图像微秒级时间和FrameID Baumer工业相机Baumer工业相机FrameID技术背景一、FrameID是什么&#xff1f;二、使用BGAPI SDK获取图像微秒时间和FrameID步骤 1.获取SDK图像微秒级时间2.获取SDK图像FrameIDBaumer工业相机使用微秒级时间和FrameID保存的用处Baumer工业…

全网唯一!Matlab世界顶尖艺术品配色包Rmetbrewer

想要绘制一幅颜色搭配合理、好看又不花哨的论文插图&#xff0c;该如何操作呢&#xff1f; 正所谓求其上者得其中&#xff0c;求其中者得其下。 那么&#xff0c;向高手借鉴思路&#xff0c;无疑是一种不落下乘的好策略。 而在色彩搭配领域&#xff0c;像莫奈、梵高这些世界…

操作系统原理 —— 进程有哪几种状态?状态之间如何切换?(七)

进程的五种状态 首先我们一起来看一下进程在哪些情况下&#xff0c;会有不同的状态表示。 创建态、就绪态 当我们刚开始运行程序的时候&#xff0c;操作系统把可执行文件加载到内存的时候&#xff0c;进程正在被创建的时候&#xff0c;它的状态是创建态&#xff0c;在这个阶…

三菱GX Works2梯形图程序分段显示设置的具体方法示例

三菱GX Works2梯形图程序分段显示设置的具体方法示例 大家平时在使用GX Works2进行梯形图程序编辑时,默认是一整段在一起,程序步数较多时查看起来不是那么方便,下面就和大家分享如何通过声明编辑来实现程序分段显示。 具体方法可参考以下内容: 如下图所示,打开GX Works2编…

DATAFAKER 使用方法记录

DATAFAKER 使用方法记录 win10 64位 Python 3.10.11 参考网址 datafaker的使用–详细教程 https://blog.csdn.net/A15517340610/article/details/105623103 https://github.com/gangly/datafaker python 版本 It is compatible with python2.7 and python3.4 也就是说 他…

案例2:Java图书商城系统设计与实现开题报告

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

使用nginx做WSS转为WS

HTTPS 是一种加密文本的超链接&#xff0c;所以需要证书&#xff0c;证书可以 使用域名&#xff0c;在腾讯云等服务商申请 https 证书&#xff0c;证书有 收费的和免费的&#xff0c;免费的有使用期限。 利用域名申请证书后&#xff0c;一般会有4中证书文件&#xff0c; .csr…

【Vue 基础】尚品汇项目-02-路由组件的搭建

项目路由说明&#xff1a; 前端的路由&#xff1a;Key-Value键值对 Key&#xff1a;URL&#xff08;地址栏中的路径&#xff09; Value&#xff1a;相应的路由组件 作用&#xff1a;设定访问路径&#xff0c;并将路径和组件映射起来&#xff08;就是用于局部刷新页面&#xff0…

前有谷歌的“生存指南”,后有金山系的“表格编程”,均登热榜

谷歌的“生存指南” 一位曾经在谷歌工作的工程师&#xff0c;干了一件了不起的事&#xff0c;花费了两年的时间&#xff0c;整理了一份“xg2xg”的清单。 原来这位离职的谷歌工程师为程序员编写了一份“厂外生存指南”&#xff0c;即使你从谷歌离职后&#xff0c;在这套“生存…

AlgoC++第六课:BP反向传播算法

目录 BP反向传播算法前言1. MNIST2. 感知机2.1 前言2.2 感知机-矩阵表示2.3 感知机-矩阵表示-多个样本2.4 感知机-增加偏置2.5 感知机-多个输出2.6 总结2.7 关于广播 3. BP4. 动量SGD5. BP示例代码总结 BP反向传播算法 前言 手写AI推出的全新面向AI算法的C课程 Algo C&#xf…

【三十天精通Vue 3】第十六天 Vue 3 的虚拟 DOM 原理详解

引言 Vue 3 的虚拟 DOM 是一种用于优化 Vue 应用程序性能的技术。它通过将组件实例转换为虚拟 DOM&#xff0c;并在组件更新时递归地更新虚拟 DOM&#xff0c;以达到高效的渲染性能。在 Vue 3 中&#xff0c;虚拟 DOM 树由 VNode 组成&#xff0c;VNode 是虚拟 DOM 的基本单元…

新黑马头条项目经验(黑马)

swagger (1)简介 Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(API Documentation & Design Tools for Teams | Swagger)。 它的主要作用是&#xff1a; 使得前后端分离开发更加方便&#xff0c;有利于团队协作 接…

HCIP之RSTP、MSTP

目录 RSTP 相较于802.1D改进 改进1&#xff1a;变更了端口角色 改进点2&#xff1a;修改了端口的状态类型 改进3&#xff1a;对配置BPDU的报文内容进行修改 改进点4&#xff1a;对配置BPDU的处理 改进点5&#xff1a;快速收敛机制 改进点6&#xff1a;拓扑变更机制的改进…

学电路设计时,你遇到过什么有趣的事?

说几个学生时代的傻x事&#xff1a; 1、以前对DC-DC懂得少&#xff0c;而且一般开关电源芯片小&#xff0c;还有一堆外围&#xff0c;手焊很麻烦&#xff0c;就觉得三端稳压器碉堡了啊&#xff0c;一个就能得到想要的电压啊&#xff0c;有木有。然后就各种用三端稳压器。那玩意…

无源滤波器为什么能滤波?

滤波器能够滤波的本质是利用构造特定的阻抗特性引起反射和损耗来实现对频率的选择。 如果从能量守恒的角度来讲&#xff0c;被抑制掉的信号去哪里了&#xff1f;​ 我们先看一下基本电路原理&#xff0c;上图中&#xff0c;负载接收的功率为 我们知道&#xff0c;最大功率传输…

【大数据之Hadoop】十八、MapReduce之压缩

1 概述 优点&#xff1a;减少磁盘IO、减少磁盘存储空间。 缺点&#xff1a;因为压缩解压缩都需要cpu处理&#xff0c;所以增加CPU开销。 原则&#xff1a;运算密集型的Job&#xff0c;少用压缩&#xff1b;IO密集型的Job&#xff0c;多用压缩。 2 压缩算法对比 压缩方式选择时…

广州蓝景分享—快速了解Typescript 5.0 中重要的新功能

作为一种在开发人员中越来越受欢迎的编程语言&#xff0c;TypeScript 不断发展&#xff0c;带来了大量的改进和新功能。在本文中&#xff0c;我们将深入研究 TypeScript 的最新迭代版本 5.0&#xff0c;并探索其最值得注意的更新。 1.装饰器 TypeScript 5.0 引入了改进的装饰…