sa-token使用及与spring-security的对比

sa-token相关资料地址

官网: https://sa-token.cc/
gitee: https://gitee.com/dromara/sa-token
github: https://github.com/dromara/sa-token
快速开始: https://sa-token.cc/doc.html#/

sa-token典型应用

这里我直接拿SpringBoot_v2(springboot的开源后台脚手架)来作为应用案例进行演示了。

SpringBoot_v2介绍:SpringBoot_v2项目是努力打造springboot框架的极致细腻的脚手架。包括一套漂亮的前台。无其他杂七杂八的功能,原生纯净。

可以作为练手项目的脚手架,或快速开发小型系统的后台脚手架。

需要稍微注意点是,他自带一套前端简单界面,不过他是用thymeleaf方式的,不是nodejs生态,不太建议同时当作前端脚手架来使用。

相关资料:

git地址: https://gitee.com/bdj/SpringBoot_v2
github地址: https://github.com/fuce1314/Springboot_v2

以下简称SpringBoot_v2为应用。

a.应用初始化

建库导库。create database `springbootv2`;

将doc下的springbootv2.sql导入你的数据库。

b.修改数据库地址、用户名密码

c.启动服务

登录地址:

http://localhost:8080/admin/login

具体的功能就不介绍了,可以自己尝试。

下面对sa-token相关依赖、配置进行说明:
引入依赖:

        <dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.26.0</version></dependency><!-- Sa-Token 整合 Redis (使用jackson序列化方式) --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-dao-redis-jackson</artifactId><version>1.26.0</version></dependency>

进行配置:

定义用户、角色、权限信息获取

这个类里就直接访问系统中的dao了,也就是与系统原角色权限体系打通了。

配置权限控制注解方法拦截处理


SaTokenConfigure

    /*** 注册 Sa-Token 的注解拦截器,打开注解式鉴权功能 */@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");    }

上面这个SaAnnotationInterceptor是控制方法权限的,类似这种:

主要就是配合这几个注解来实现方法级的权限控制的。@SaCheckLogin @SaCheckPermission @SaCheckRole。
简单说明下:
检查是否登录:一般除了登录接口,其他接口都需要登录后访问
检查角色:用户可以关联角色,通过检查有没有指定的角色决定是否允许
检查权限:每个角色可以关联多个权限,权限是最小粒度,类似于前端按钮权限

访问地址跳转登录页面

Tips:这个是针对前后端不分离的情况来说的,如果你的项目前后端分离,这一步一般是前端直接做的。

        定义了SaServletFilter,指定某些url允许直接访问(一般就是登录地址、静态资源等)。
其他的url全都检查是否已登录。如果是未登录的异常,跳转登录页面;如果是其他异常,也就是认证操作本身的异常。

        同时在每次请求进来时,可以通过指定beforeAuth钩子函数来写一下通用处理,设置响应头之类的。(当然,这个filter所有的功能可以直接自己定义一个filter来实现,只是借用一下StpUtil.checkLogin()就行了,只不过sa-token提取了一个SaServletFilter把整个拦截过程拆分为了认证前、认证、错误处理,以及覆盖的url包含、排除匹配等)。

完成

        对于大部分情况来说,这就算完成了,能够检查是否登录,能够根据用户角色配置控制controller方法访问权限,已经足够满足需要了。整体上只需要实现一个用户信息获取、添加一个filter,一个handlerInterceptor,足够简单。

springboot服务后端安全痛点

考虑一般的安全需求:

1.用户登录状态的保持

2.用户会话过期控制

3.token创建、保存及传递

4.方法级后台权限控制

5.登录页面跳转(不是必要)

       在很多情况下,可能我们更多的只是想要第4点,其他的用系统原有的filter体系就足够了。当然对于新建系统,还是建议统一使用sa-token提供的机制,让sa-token帮你处理底层的繁杂细节处理。

        站在个人的思考角度,我能想到的也就这些情况了,对于这样的安全需求,我们该如何实现,如何选择呢?

spring-security的复杂性

        大家大都使用的spring全家桶,碰到问题,一般首先会想到从spring生态中去寻找现成的解决方案,认为这种方式集成起来更加可靠,也更容易操作上手,但就安全这块来说,在我个人看来,spring-security还是相当复杂的。虽然对于配置量而言,使用他的代码不是很多,但对于理解难度来说,特别是对于想探究源码层面原理的同学来说,还是相当复杂、相当难以理顺关系的。

配置举例        

如果是spring-security,大概的配置方式为:

public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {// 搜寻匿名标记 url: @AnonymousAccessRequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();// 获取匿名标记Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);httpSecurity// 禁用 CSRF.csrf().disable().addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)// 授权异常.exceptionHandling().authenticationEntryPoint(authenticationErrorHandler).accessDeniedHandler(jwtAccessDeniedHandler)// 防止iframe 造成跨域.and().headers().frameOptions().disable()// 不创建会话.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 静态资源等等.antMatchers(HttpMethod.GET,"/*.html","/**/*.html","/**/*.css","/**/*.js","/webSocket/**").permitAll()// swagger 文档.antMatchers("/swagger-ui.html").permitAll().antMatchers("/swagger-resources/**").permitAll().antMatchers("/webjars/**").permitAll().antMatchers("/*/api-docs").permitAll()// 文件.antMatchers("/avatar/**").permitAll().antMatchers("/file/**").permitAll()// 阿里巴巴 druid.antMatchers("/druid/**").permitAll().antMatchers("/actuator/**").permitAll().antMatchers("/instances/**").permitAll()// 放行OPTIONS请求.antMatchers(HttpMethod.OPTIONS, "/**").permitAll().antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()// 所有请求都需要认证.anyRequest().authenticated().and().apply(securityConfigurerAdapter());}
}

自动配置

通过这个配置,能看出来啥呢?他给我们暴露出来的可操作对象为HttpSecurity,而这只是spring-security体系中的一环,整体是怎么样的呢?我这里简单的梳理一下,仅供大家参考对比。

1.SecurityAutoConfiguration 这个是security的自动配置类,作为分析的起点
2.SpringBootWebSecurityConfiguration 这个是1引入的配置类,配置了一个默认的SecurityFilterChain

3.WebSecurityEnablerConfiguration 这个也是1引入的,是为了引入4

4.@EnableWebSecurity这个是3引入的,相当于不用咱们自己加了。

5.WebSecurityConfiguration这个是4引入的,下面详细描述

6.HttpSecurityConfiguration这个是4引入的,他主要就是创建了一个HttpSecurity,他创建的HttpSecurity对象其实是给了2注入用的,作为系统默认配置。也就是说完全使用springboot的yml配置来动态修改某些属性,而不添加自定义的配置类。

WebSecurityConfiguration

下面对5进行简单分析:

1.WebSecurityConfiguration的目标就是springSecurityFilterChain()函数生成一个filter,而这个filter最终的类型看WebSecurity的performBuild()方法,看到了,是FilterChainProxy对象。

2.1的目标filter,是通过webSecurity来生成的,这算是创建型设计模式里的建造者模式(Builder),不过他比一般而言的Builder模式要复杂的多,这里不做具体分析。

3.简单看下springSecurityFilterChain方法的代码,他这里直接操作了一个list,就是securityFilterChains,通过webSecurity.addSecurityFilterChainBuilder加进去了

4.还有注意的是标记了@Autowired的方法setFilterChainProxySecurityConfigurer,他注入了SecurityConfigurer的list,然后挨个webSecurity.apply应用给webSecurity了

5.第4步SecurityConfigurer因为泛型的缘故,其实他注入的就是WebSecurityConfigurer对象,也就是最经典的配置方式:用户继承WebSecurityConfigurerAdapter实现个性化配置。

6.WebSecurityConfigurerAdapter重点关注init(WebSecurity)方法,这个方法是WebSecurityConfigurer这个配置器的第一阶段(一共是两个阶段:init和configure,看SecurityConfigurer),这里调用web.addSecurityFilterChainBuilder方法(即webSecurity.addSecurityFilterChainBuilder)方法,也给webSecurity加进去了,效果类似3。

7.由此,用户自定义的继承自WebSecurityConfigurerAdapter的配置类,就相当于给webSecurity配置了一个filterChain过滤器链,而这个链具体是在HttpSecurity对象中配置的,WebSecurityConfiguerAdapter主要是配置AuthenticationManager、UserDetailsService等的。

WebSecurity建造

下面对2进行简单分析:

1.webSecurity看下他的继承关系,他的父类是AbstractConfiguredSecurityBuilder<Filter, WebSecurity>

2.建造者模式,最大的步骤,在顶层父类SecurityBuilder中,是build方法

3.下一层步骤,在SecurityBuilder的子类AbstractSecurityBuilder中,扩展了个doBuild方法。

4.下一层步骤,在类AbstractConfiguredSecurityBuilder中

protected final O doBuild() throws Exception {synchronized(this.configurers) {this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;this.beforeInit();this.init();this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;this.beforeConfigure();this.configure();this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;O result = this.performBuild();this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;return result;}}

可以看到这里又拆分成了很多子步骤,包括beforeInit,init,beforeConfiguer,configure,同时引入一个新步骤:performBuild。同时这个类主要是辅助子类(这里是WebSecurity)进行动态化配置的,他提供了两个方法add(C configurer)和removeConfigurer来添加删除配置器。

5.在WebSecurity类中,也就是实现了performBuild方法。

SecurityConfigurer配置器

再对上面的4里面的configurer进行简单分析:

1.这里的configurer是SecurityConfigurer<O, B>,这里的O表示目标,就是Filter,B表示构造器本身,这里是WebSecurity。

2.看WebSecurityConfigurerAdapter的类型,他实现了接口WebSecurityConfigurer<WebSecurity>

3.WebSecurityConfigurer继承了SecurityConfigurer<Filter, T>,这里的T是WebSecurity

4.所以归根结底,WebSecurityConfigurerAdapter就是个SecurityConfigurer

5.SecurityConfigurer的方法包括两个:init,configure,也就是说,我们可以通过添加一个个的SecurityConfigurer对webSecurity进行配置,最终通过webSecurity的build来产出我们想要的FilterChainProxy。

6.也就是说,一条security过滤器链,是由一组WebSecurityConfigurerAdapter具体配置的(当然这里只需要一个这样的configurer)。

7.但是呢,这里还不是最终的地方。可以看到,init方法里,web.addSecurityFilterChainBuilder(http),这里又出现了HttpSecurity对象。

HttpSecurity建造

下面对HttpSecurity简单分析:

1.HttpSecurity也是个建造者模式,AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>,与WebSecurity类似,他为了产出DefaultSecurityFilterChain

2.他依赖的配置器组configurers,类型是SecurityConfigurer<O,B>,O是DefaultSecurityFilterChain,B是HttpSecurity。这里重点关注HttpSecurity的getOrApply方法

3.这里与之相关的configurer有什么呢?大概有这么些:
AnonymousConfigurer
AuthorizeHttpRequestsConfigurer
ChannelSecurityConfigurer
CorsConfigurer
CsrfConfigurer
DefaultLoginPageConfigurer
ExceptionHandlingConfigurer
ExpressionUrlAuthorizationConfigurer
FormLoginConfigurer
HeadersConfigurer
HttpBasicConfigurer
JeeConfigurer
LogoutConfigurer
PermitAllSupport
PortMapperConfigurer
RememberMeConfigurer
RequestCacheConfigurer
SecurityContextConfigurer
ServletApiConfigurer
SessionManagementConfigurer
UrlAuthorizationConfigurer
X509Configurer

也就是我们在个性化配置的时候,重写configure(HttpSecurity httpSecurity)方法时做的那些事情。

而HttpSecurity也提供了辅助方法,方便我们函数式添加configurer。

4.这些configurer之间还能通过HttpSecurity的setSharedObject和getSharedObject之类的方法实现相互间的共享,还有更多的细节,无法一一详述。

CorsConfigurer举例

以CorsConfigurer举例,直接看他的configure方法:

    public void configure(H http) {ApplicationContext context = (ApplicationContext)http.getSharedObject(ApplicationContext.class);CorsFilter corsFilter = this.getCorsFilter(context);Assert.state(corsFilter != null, () -> {return "Please configure either a corsFilter bean or a corsConfigurationSourcebean.";});http.addFilter(corsFilter);}

简单来说,就是添加了一个corsFilter。

总结

        说了这么多,只是想说spring-security真的挺复杂。当然你可以说站在使用者的角度,我们配置的代码量并不大。那确实,不过对于比较想看源码、想进行比较底层一点定制的人的来说,理解难度太大了。

安全框架的选型折中

        如果你的水平较高,或者公司基础平台已经选型确定了spring-security,或者你不认为spring-security存在理解难、理解费劲的问题,那可以依然使用他。如果你水平有限,或者不想把安全这块做的那么重,那就完全可以考虑使用sa-token,或者借鉴sa-token的思路,通过加几个类,在Filter层面、Servlet层面、HandlerInterceptor层面操作一下,也能实现想要的效果。

        当然这里是推荐使用sa-token,至少可以写个demo或直接下载SpringBoot_v2的代码体验一下子,研究一下子。

从sa-token想到的破题法

不必纠结于框架,够用好用永远是最终目标。

如果项目有明确要求,或有明确的技术栈标准,那该用还得用,技术毕竟要为需要服务的。

扩展:如何根据需要自己写个小而美的框架

如何手动实现一个小框架并与springboot整合的基本思路流程。

在与springboot整合方面,我个人感觉,sa-token的spring-boot-starter写的一般,他没有加一个SaTokenAutoConfiguration(个人见解)。

一般我认为遵循以下步骤:

1.建模,甭管是画个示意图、流程图,还是用文字描述清楚,把要解决的问题点列出来、把具体的方案模型描述出来

2.提取接口、实体,提取关键逻辑类。一开始,我们可以不将其抽取出去,而是直接放到项目中,只是作为一个包独立放置。

3.在项目中实际调试、使用,注意这个独立包不要依赖项目,而是项目依赖他。

4.代码重构。将项目结构化优化,让步骤清晰明了。并提供给用户充分的便利,提供Customizer、Configurer之类的口子,让用户能方便的进行个性化微调、配置;如果可能,用户甚至可以自定义替换某块整个处理环节逻辑。

5.默认配置。提供某些接口的默认实现,减少用户的工作量;对于某些场景(如持久化到内存、redis、数据库),可以预置多个实现,供用户选择。

6.将代码抽取到独立工程中去,并提供starter自动配置。

7.项目中依赖starter,整合测试。

8.发布到私服或开源。

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

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

相关文章

MySQL:left join后用on与where的区别

一、前言 前几天项目中&#xff0c;写SQL时本想通过 A left B join on and 后面的条件来使查出的两条记录变成一条&#xff0c;奈何发现还是有两条。在此记录一下&#xff0c;on与where的区别。 二、ON 原始数据展示 SELECT t1.*,t2.* FROM t_test_staff t1 left join t_te…

ANX9833FN-AA-R ANX9833 ANALOGIX QFN48 VGA视频转换器件

ANX9833概述:ANX9833是VGA显示接口适配器集成电路设计一个显示端口1.2/1.1源连接到一个VGA显示。与芯片上的单片机和记忆,ANX9833不需要任何外部配置或设置。它自动引导VGA显示接口适配器的输出,有效地处理所有类型的遗产显示器、投影仪,和电视。ANX9833提供Gbps带宽在两车道到…

2025全平台短剧系统 : 快手、抖音、微信全覆盖

之前&#xff0c;我曾详细阐述过公司短剧系统的一些功能&#xff0c;它们共同构建了一个全面、高效的短剧制作与运营平台。这些功能&#xff0c;无论是媒资管理、剧场设定&#xff0c;还是后期运营&#xff0c;都是经过深思熟虑、精心设计的&#xff0c;是一个成熟的短剧系统所…

机圈白刃战,vivo聚势成风

金秋十月&#xff0c;国产手机市场进入了空前激烈的竞争局势&#xff0c;几乎每天都有发布会&#xff0c;甚至隔段时间就有新机话题登上热搜。网友戏称&#xff0c;发布会密度高到“工作日都不够用了”。 10月14日&#xff0c;vivo X200系列率先登场&#xff0c;拉开了国产旗舰…

scp 或 ssh 报错no matching host key type found. Their offer: ssh-rsa 解决方案

报错如下&#xff1a; 解决方案&#xff1a; 在 scp 或 ssh 命令后面增加参数&#xff1a; -o HostKeyAlgorithmsssh-rsa 可以解决此问题&#xff0c; scp格式如下&#xff1a; scp -o HostKeyAlgorithmsssh-rsa [local_file_path] [user][hosts]:[remote_path]

ElasticSearch概述

ElasticSearch概述 Elaticsearch&#xff0c;简称为es&#xff0c; es是一个开源的高扩展的分布式全文检索引擎&#xff0c;它可以近乎实时的存储、检索数据&#xff1b;本身扩展性很好&#xff0c;可以扩展到上百台服务器&#xff0c;处理PB级别的数据。es也使用Java开发并使…

一文彻底了解UDHCP源码核心☝️

&#x1f344;参考学习: udhcp源码剖析&#xff08;一&#xff09;——DHCP服务器和客户端的工作流程_udhcpc源码v1.29.2-CSDN博客 前言介绍 本文深入探讨了DHCP服务器和客户端的工作流程&#xff0c;以udhcp为例&#xff0c;详细阐述了udhcpd&#xff08;服务器&#xff09;…

开启鸿蒙开发之旅:静态页面搭建

写在前面 了解了一些常用的系统组件及其属性之后&#xff0c;我准备开始搭建我第一个页面&#xff0c;本次鸿蒙Next初体验我准备模仿这款“提醒事项”APP&#xff0c;从页面搭建到基本功能实现。今天从入口页开始&#xff1a; 布局思路 整体结构 从该页面的整体布局结构来看&…

C++20 STL CookBook 7 Containers(II)

让vector在插入删除的时候仍然保证是有序的 首先&#xff0c;STL的确提供了一种办法来检查我们的目标容器是不是有序的&#xff1a;std::is_sorted - cppreference.com&#xff0c;也就是std::is_sorted。我们当然可以这样做&#xff1a; #include <iostream> #include…

二叉树搜索树(下)

二叉树搜索树&#xff08;下&#xff09; 二叉搜索树key和key/value使用场景 key搜索场景 只有key作为关键码&#xff0c;结构中只需要存储key即可&#xff0c;关键码即为需要搜索到的值&#xff0c;搜索场景只需要判断 key在不在。key的搜索场景实现的二叉树搜索树支持增删查…

人力资源招聘系统-提升招聘效率与质量的关键工具

在当今这个竞争激烈的商业环境中&#xff0c;企业要想在市场中立于不败之地&#xff0c;关键在于拥有高素质的人才队伍。然而&#xff0c;传统的招聘方式往往效率低下&#xff0c;难以精准匹配企业需求与人才特质&#xff0c;这无疑给企业的发展带来了不小的挑战。 随着科技的飞…

【C++】类中的“默认成员函数“--构造、析构、赋值

目录 概念引入&#xff1a; 一、构造函数 问题引入&#xff1a; 1&#xff09;构造函数的概念 2&#xff09;构造函数的特性 二、析构函数 1&#xff09;析构函数概念 2&#xff09;析构函数特性 三、拷贝构造函数 1)拷贝构造函数概念 示例代码&#xff1a; 2)深拷…

环丙烷环辛炔聚乙二醇磷脂,淡黄色固体,BCN-PEG-DSPE

中文名称&#xff1a;环丙烷环辛炔聚乙二醇磷脂 英文名称&#xff1a;BCN-PEG-DSPE 外观&#xff1a;通常为黄色或淡黄色固体 材料来源&#xff1a;为华生物 溶解性&#xff1a;在有机溶剂&#xff08;如氯仿、乙醇&#xff09;中具有良好的溶解性&#xff0c;而在水中的溶…

202409电子学会青少年机器人技术等级考试(六级)理论综合真题

青少年机器人技术等级考试理论综合试卷&#xff08;六级&#xff09; 分数&#xff1a; 100 题数&#xff1a; 30 一、 单选题(共 20 题&#xff0c; 共 80 分) 1. 使用 ESP32 for Arduino SPI 类库&#xff0c; 下列选项中&#xff0c; 具有设置时钟模式功能的成员函数是&…

如何学习VBA_3.2.14:字符串的处理

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的劳动效率&#xff0c;而且可以提高数据处理的准确度。我推出的VBA系列教程共九套和一部VBA汉英手册&#xff0c;现在已经全部完成&#xff0c;希望大家利用、学习。 如果…

ABeam News | ABeam中国受邀参加2024中国知识管理年会暨第14届China MIKE颁奖典礼,并荣获大奖

“ABeam/ News ” 近日&#xff0c;2024中国知识管理年会暨第14届China MIKE颁奖典礼圆满召开&#xff0c;大会结合AI赋能新质生产力的热点话题&#xff0c;以“AI超能力KM新价值” 作为主题&#xff0c;为与会观众带来知识管理的一场盛宴。ABeam中国受邀参会并荣获2024 China…

Error: Could not find or load main class org.apache.catalina.startup.Bootstrap

#现象&#xff1a; 官网下载tomcat source包后&#xff0c;启动报错&#xff0c;等一系列缺包造成服务无法启动 Error: Could not find or load main class org.apache.catalina.startup.Bootstrapjava.lang.ClassNotFoundException: org.apache.juli.logging.LogFactory原因 …

论文解读《CTRLsum: Towards Generic Controllable Text Summarization》

引言&#xff1a;一篇上交大佬的著作 ✅ NLP 研 2 选手的学习笔记 笔者简介&#xff1a;Wang Linyong&#xff0c;NPU&#xff0c;2023级&#xff0c;计算机技术 研究方向&#xff1a;文本生成、大语言模型 论文链接&#xff1a;https://aclanthology.org/2022.emnlp-main.396.…

【spotfire】脚本相关

文章目录 ironpython脚本使用JS实现弹出窗口思路实现效果 脚本的使用可以极大扩展spotfire的功能&#xff0c;但如何使用脚本一直不得其门而入&#xff0c;咨询厂商、查询资料&#xff0c;特此记录备忘。 ironpython脚本使用 参见官网教程&#xff1b; 部分参考资料如下&#…

嵌入式硬件杂谈(一)-推挽 开漏 高阻态 上拉电阻

引言&#xff1a;对于嵌入式硬件这个庞大的知识体系而言&#xff0c;太多离散的知识点很容易疏漏&#xff0c;因此对于这些容易忘记甚至不明白的知识点做成一个梳理&#xff0c;供大家参考以及学习&#xff0c;本文主要针对推挽、开漏、高阻态、上拉电阻这些知识点的学习。 目…