Sa-Token源码简单阅读

news2024/12/23 22:39:00

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

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.coloradmin.cn/o/450641.html

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

相关文章

国内外4款主流ERP系统评测,哪款最好用?

一、ERP系统的概念 ERP系统&#xff0c;是针对通用各个企业特点研发的ERP软件。由于行业产品结构复杂&#xff0c;导致原料种类众多&#xff0c;制造工艺复杂&#xff0c;外加客户、供应商、物流等不确定因素&#xff0c;传统手工、表格、纸质作业模式难以应对复杂状况&#x…

设计模式之责任链模式(C++)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 一、责任链模式是什么&#xff1f; 责任链模式是一种行为型的软件设计模式&#xff0c;对象内存在对下家的引用&#xff0c;层层连…

【世界读书日】2023年通信好书推荐

今天是世界读书日&#xff08;4月23日&#xff09;。按照老规矩&#xff0c;小编给大家推荐一些通信类的优秀书籍。 过去一年&#xff0c;通信行业的关注热点&#xff0c;主要是&#xff1a;5G-Advanced&#xff08;5.5G&#xff09;、算力网络、东数西算、6G、卫星互联网、智…

历史上的今天大事件查询工具推荐 - 历史上的今天 API

引言 历史上的今天&#xff0c;总会有一些特别的事件发生&#xff0c;这些事件对人类的发展产生了深远的影响。想要了解这些事件&#xff0c;往往需要花费大量的时间和精力去查阅历史资料。但现在&#xff0c;有了历史上的今天 API&#xff0c;一切变得方便了许多。 如果你对…

交友项目【根据id查询单条动态发布评论查询评论列表】

目录 1&#xff1a;根据id查询单条动态 1.1&#xff1a;接口分析 1.2&#xff1a;流程分析 1.3&#xff1a;代码实现 2&#xff1a;发布评论 2.1&#xff1a;接口分析 2.2&#xff1a;流程分析 2.3&#xff1a;代码实现 3&#xff1a;查询评论列表 3.1&#xff1a;接…

Python导出含有中文名文件解决方案

使用Python开发过程中有用到需要导出文件的功能 异常代码 # 代码片段 def return_workbook(self, workbook, model_code, x_io):name “税单.xls”workbook.close()res HttpResponse()res["Content-Type"] "application/octet-stream"res["Conte…

Python 基础(九):列表

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 一、声明列表二、访问列表元素三、修改列表元素四、添加列表元素4.1、在列表末尾添加元…

京东商品列表数据采集(APP,H5端)

采集场景: 在京东搜索页 https://search.jd.com/Search 输入搜索&#xff0c;搜出后得到的多个商品列表数据。 征地: 商品名称、价格、评论数、店铺名称、店铺链接等字段。 采集结果: 采集结果可导出为Excel&#xff0c;CSV&#xff0c;HTML&#xff0c;数据库等格式。导出…

学习系统编程No.22【消息队列和信号量】

引言&#xff1a; 北京时间&#xff1a;2023/4/20/7:48&#xff0c;闹钟6点和6点30&#xff0c;全部错过&#xff0c;根本起不来&#xff0c;可能是因为感冒还没好&#xff0c;睡不够吧&#xff01;并且今天是星期四&#xff0c;这个星期这是第二篇博客&#xff0c;作为一个日…

Java核心技术 卷1-总结-12

Java核心技术 卷1-总结-12 具体的集合链表数组列表 具体的集合 下表中除了以 Map结尾的类之外&#xff0c; 其他类都实现了 Collection 接口&#xff0c;而以 Map结尾的类实现了 Map 接口。 集合类型描述ArrayList一种可以动态增长和缩减的索引序列LinkedList一种可以在任何位…

为视频直播网站开发选择最佳技术

在今天的数字时代&#xff0c;随着人们越来越多地倾向于观看在线视频&#xff0c;视频直播网站开发已经成为了一项非常有前途的技术。无论是为了提供娱乐、教育还是商业目的&#xff0c;视频直播网站开发都是一个非常重要的领域。 在视频直播网站开发中&#xff0c;你需要考虑…

使用Process Monitor探测日志文件是C++程序哪个模块生成的

目录 1、问题描述 2、使用Process Monitor监测目标文件是哪个模块生成的思路说明 3、操作Process Monitor监测日志文件是哪个模块生成的 4、通过screenctach.dll库的时间戳&#xff0c;找到其pdb文件&#xff0c;然后去查看详细的函数调用堆栈 5、最后 VC常用功能开发汇总…

春秋云境:CVE-2022-25099(文件上传造成RCE)

目录 一、题目 二、burp上传执行木马 一、题目 介绍&#xff1a; WBCE CMS v1.5.2 /language/install.php 文件存在漏洞&#xff0c;攻击者可精心构造文件上传造成RCE 进入题目&#xff1a; 网站正在建设中。。。 直接访问/admin吧&#xff1a; admin:123456 成功进入&…

css案例:小黄人案例

css案例&#xff1a;小黄人案例 先看效果图 眼睛和嘴巴有做动画的&#xff0c;但是我懒得上传gif了。 3. 源码 html <!DOCTYPE html> <html lang"en"> <head><meta charset"utf-8"><style>.contain {width: 400px;height:…

贪心-合并果子(经典Huffman树)

题意 在一个果园里&#xff0c;达达已经将所有的果子打了下来&#xff0c;而且按果子的不同种类分成了不同的堆。 达达决定把所有的果子合成一堆。 每一次合并&#xff0c;达达可以把两堆果子合并到一起&#xff0c;消耗的体力等于两堆果子的重量之和。 可以看出&#xff0c;所…

Linux驱动开发:uboot启动流程详解

前言&#xff1a;uboot作为Linux驱动开发的 “三巨头” 之一&#xff0c;绝对是一座绕不开的大山。当然&#xff0c;即使不去细致了解uboot启动流程依旧不影响开发者对uboot的简单移植。但秉持着知其然知其所以然的学习态度&#xff0c;作者将给读者朋友细致化的过一遍uboot启动…

如何用ChatGPT举办活动,人类与AI的一次深度对谈

刚刚&#xff0c;Mixlab今年首次线下联合举办的活动开启了&#xff0c;活动不仅分享了AIGC对体验设计的新要求、内容产业的发展研判、用于模拟仿真的生成式智能体&#xff0c;还演示了AI如何深度整合到一场活动之中。 1/ 数字人出场介绍Mixlab 是如何实现的呢&#xff1f;无限…

[Net]SSE消息推送简介

文章目录 SSE网络协议客户端服务端事件 SSE示例客户端服务端 SSE&#xff08;Server-Sent Events&#xff09;是一种服务端到客户端&#xff08;浏览器&#xff09;的单向消息推送方式。 SSE网络协议 SSE是基于HTTP协议的&#xff0c;客户端向服务端发起一个请求&#xff0c;建…

Android 9.0 系统设置显示主菜单添加屏幕旋转菜单实现旋转屏幕功能

1.前言 在android9.0的系统rom定制化开发中,在对系统设置进行定制开发中,有产品需求要求增加旋转屏幕功能的菜单,就是在点击旋转屏幕菜单后弹窗显示旋转0度,旋转 90度,旋转180度,旋转270度针对不同分辨率的无重力感应的大屏设备的屏幕旋转功能的实现,接下来就来分析实现…