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 的注解拦截器,打开注解式鉴权功能 
	 */
    @Override
    public 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 {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        // 搜寻匿名标记 url: @AnonymousAccess
        RequestMappingHandlerMapping 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.发布到私服或开源。

### sa-token Spring Security 认证框架特性的对比 #### 安全模型 sa-token 提供了一种基于令牌的身份验证机制,这使得其在实现上类似于传统的Token Auth[^1]。而Spring Security则提供了一个更为全面的安全框架,不仅支持多种身份验证方式(如表单登录、HTTP Basic、OAuth2等),还提供了细粒度的访问控制功能。 #### 易用性集成难度 对于开发者而言,sa-token 的设计更加简洁直观,易于快速集成到现有项目中。相比之下,Spring Security虽然配置较为复杂,但是通过丰富的文档支持社区,能够满足更广泛的应用场景需求。 #### 性能表现 由于sa-token专注于轻量级的身份验证流程,在处理速度方面可能具有一定优势;然而具体性能差异取决于实际应用场景下的优化程度以及所使用的其他组件情况。 #### 扩展能力 两者都具备良好的扩展性,不过Spring Security凭借其庞大的生态系统插件体系,在应对复杂的业务逻辑定制化需求时显得尤为强大。 ```java // 示例:Spring Security 配置类 @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated(); } } ``` ```python # 示例:sa-token 登录接口 from flask import Flask, request import satoken app = Flask(__name__) @app.route('/login', methods=['POST']) def login(): username = request.form.get('username') password = request.form.get('password') # 假设这里进行了用户名密码校验并返回token token = satoken.create_token({'uid': user_id}) return {'code': 0, 'msg': "success", 'data': {"token": token}} ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

brimsullowr

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

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

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

打赏作者

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

抵扣说明:

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

余额充值