Sa-Token Documentation

开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名

19,094 stars Java Java 120 files · ~127,019 tokens #aouth2#authorization#java#springcloud#sso#token
RAW Doc

File: README.md

简体中文 | 繁體中文 | English | 日本語 | 한국어 | Русский

Sa-Token v1.46.0

✨ 开源、免费、一站式 java 权限认证框架,让鉴权变得简单、优雅!


📝 前言:

回望 2020 年初,我为 Sa-Token 提交第一行代码之际,彼时市面上 Java 缺少的不仅是一个简洁好用的鉴权框架,更是一整套清晰、自洽的权限架构设计思想。

因此,这几年间我将大量时间倾注在 Sa-Token 的文档编写,几乎每一章节、每一句话、每一个字都经过反复修改、精细打磨,以求做到最清晰、干练、易懂的表述。用心阅读文档,你学习到的将不止是 Sa-Token 框架本身,更是绝大多数场景下权限认证设计的最佳实践。

🛠️ Sa-Token 介绍

Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。

你还在手搓以下功能?Stop,让 Sa-Token 来!

要在 SpringBoot 项目中使用 Sa-Token,你只需要在 pom.xml 中引入依赖:

xml
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.com -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-spring-boot-starter</artifactId>
	<version>1.46.0</version>
</dependency>

除了支持 SpringBoot2、Sa-Token 还为 SpringBoot3/4、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。

简单示例展示:(点击展开 / 折叠)

Sa-Token 旨在以简单、优雅的方式完成系统的权限认证部分,以登录认证为例,你只需要:

java
// 会话登录,参数填登录人的账号id 
StpUtil.login(10001);

无需实现任何接口,无需创建任何配置文件,只需要这一句静态代码的调用,便可以完成会话登录认证。

如果一个接口需要登录后才能访问,我们只需调用以下代码:

java
// 校验当前客户端是否已经登录,如果未登录则抛出 `NotLoginException` 异常
StpUtil.checkLogin();

在 Sa-Token 中,大多数功能都可以一行代码解决:

踢人下线:

java
// 将账号id为 10077 的会话踢下线 
StpUtil.kickout(10077);

权限认证:

java
// 注解鉴权:只有具备 `user:add` 权限的会话才可以进入方法
@SaCheckPermission("user:add")
public String insert(SysUser user) {
    // ... 
    return "用户增加";
}

路由拦截鉴权:

java
// 根据路由划分模块,不同模块不同鉴权 
registry.addInterceptor(new SaInterceptor(handler -> {
	SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
	SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
	SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
	SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
	SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
	// 更多模块... 
})).addPathPatterns("/**");

如果您曾经使用过 Shiro、SpringSecurity,在切换到 Sa-Token 后,您将体会到质的飞跃。

核心模块一览:(点击展开 / 折叠)

  • 登录认证 —— 单端登录、多端登录、同端互斥登录、七天内免登录。
  • 权限认证 —— 权限认证、角色认证、会话二级认证。
  • 踢人下线 —— 根据账号id踢人下线、根据Token值踢人下线。
  • 注解式鉴权 —— 优雅的将鉴权与业务代码分离。
  • 路由拦截式鉴权 —— 根据路由拦截鉴权,可适配 restful 模式。
  • Session会话 —— 全端共享Session,单端独享Session,自定义Session,方便的存取值。
  • 持久层扩展 —— 可集成 Redis,重启数据不丢失。
  • 前后台分离 —— APP、小程序等不支持 Cookie 的终端也可以轻松鉴权。
  • Token风格定制 —— 内置六种 Token 风格,还可:自定义 Token 生成策略。
  • 记住我模式 —— 适配 [记住我] 模式,重启浏览器免验证。
  • 二级认证 —— 在已登录的基础上再次认证,保证安全性。
  • 模拟他人账号 —— 实时操作任意用户状态数据。
  • 临时身份切换 —— 将会话身份临时切换为其它账号。
  • 同端互斥登录 —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录。
  • 账号封禁 —— 登录封禁、按照业务分类封禁、按照处罚阶梯封禁。
  • 密码加密 —— 提供基础加密算法,可快速 MD5、SHA1、SHA256、AES 加密。
  • 会话查询 —— 提供方便灵活的会话查询接口。
  • Http Basic认证 —— 一行代码接入 Http Basic、Digest 认证。
  • 全局侦听器 —— 在用户登陆、注销、被踢下线等关键性操作时进行一些AOP操作。
  • 全局过滤器 —— 方便的处理跨域,全局设置安全响应头等操作。
  • 多账号体系认证 —— 一个系统多套账号分开鉴权(比如商城的 User 表和 Admin 表)
  • 单点登录 —— 内置三种单点登录模式:同域、跨域、同Redis、跨Redis、前后端分离等架构都可以搞定。
  • 单点注销 —— 任意子系统内发起注销,即可全端下线。
  • OAuth2.0认证 —— 轻松搭建 OAuth2.0 服务,支持openid模式 。
  • 分布式会话 —— 提供共享数据中心分布式会话方案。
  • 微服务网关鉴权 —— 适配Gateway、ShenYu、Zuul等常见网关的路由拦截认证。
  • RPC调用鉴权 —— 网关转发鉴权,RPC调用鉴权,让服务调用不再裸奔
  • 临时Token认证 —— 解决短时间的 Token 授权问题。
  • 独立Redis —— 将权限缓存与业务缓存分离。
  • Quick快速登录认证 —— 为项目零代码注入一个登录页面。
  • 标签方言 —— 提供 Thymeleaf 标签方言集成包,提供 beetl 集成示例。
  • jwt集成 —— 提供三种模式的 jwt 集成方案,提供 token 扩展参数能力。
  • RPC调用状态传递 —— 提供 dubbo、grpc 等集成包,在RPC调用时登录状态不丢失。
  • 参数签名 —— 提供跨系统API调用签名校验模块,防参数篡改,防请求重放。
  • 自动续签 —— 提供两种Token过期策略,灵活搭配使用,还可自动续签。
  • 开箱即用 —— 提供SpringMVC、WebFlux、Solon 等常见框架集成包,开箱即用。
  • 最新技术栈 —— 适配最新技术栈:支持 SpringBoot 3.x,jdk 17。

🍃 SSO 单点登录

Sa-Token SSO 分为三种模式,可解决:同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离、纯 js、vue2、vue3、java 项目、非 java 项目 等架构下的 SSO 认证需求:

| 系统架构 | 采用模式 | 简介 | 文档链接 |
| :-------- | :-------- |:----------------| :-------- |
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | 文档、示例 |
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | 文档、示例 |
| 前端不同域 + 后端 不同Redis | 模式三 | HTTP请求获取会话 | 文档、示例 |

  1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:c1.domain.com、c2.domain.com、c3.domain.com
  2. 后端同 Redis:就是指多个系统可以连接同一个 Redis,共享会话数据。
  3. 如果无法做到前端同域、后端同 Redis,可以走托底的模式三:Http请求校验 ticket 获取会话。
  4. 提供:NoSdk 模式示例 + sso-server 接口文档,非 Sa-Token 项目、非 java 项目也可以对接。
  5. 提供:多重安全校验:域名校验、ticket校验、参数签名校验,有效防 ticket 劫持,防请求重放等攻击。
  6. 提供:大量实战痛点教学:sso-server 前后端分离设计、sso-client 前后端分离设计、用户数据同步/迁移方案设计。
  7. 提供:直接可运行的 demo 示例,助你快速熟悉 SSO 大致登录流程。
  8. 提供:深度细节优化,参数防丢:笔者曾试验多个SSO框架,均有参数丢失情况,比如登录前是:http://a.com?id=1&name=2,登录成功后就变成了:http://a.com?id=1,Sa-Token-SSO 内有专门算法保证了参数不丢失,登录成功后精准原路返回。

🍂 OAuth2 授权认证

Sa-Token OAuth2 模块分为四种授权模式,解决不同场景下的授权需求

| 授权模式 | 简介 |
| :-------- | :-------- |
| 授权码式 | OAuth2 标准授权步骤,server 端下放 code,client 端获取 code 码兑换 access_token |
| 隐藏式 | 备用选择,server 端使用 URL 重定向方式直接将 access_token 下放到 client 端页面 |
| 密码式 | client 直接拿着用户的账号密码换取授权 access_token |
| 客户端凭证式 | server 端针对 client 级别的 client_token,代表应用自身的资源授权 |

详细参考文档:https://sa-token.com/doc.html#/oauth2/readme

📖❓ 疑问解答

1、Sa-Token 功能全不全?

七年磨一剑:五大核心模块(登录、鉴权、SSO、OAuth2、微服务) + 众多实用插件 (短 token、jwt 集成、API 参数签名、API Key 秘钥授权...) 我们提供的不只是权限认证,我们提供的是一站式解决方案。

2、Sa-Token 好不好学?

中文文档 + 中文代码注释 + 中文交流社区 + 大量实战案例博客 + 多个视频教程 + 大量优秀开源项目集成案例。

3、Sa-Token 用的人多不多?

截止统计日 (2026-1-25) 起,Sa-Token 在:

  • Gitee 关注量达到 48627 Star,位列平台所有推荐项目排行榜第一名。
  • GitHub 关注量达到 18523 Star,是主要竞争框架 Spring Security 的 1.97 倍,Apache Shiro 的 4.19 倍。
  • 25+ 微信粉丝群 (500人),8+ QQ粉丝群 (1000人 or 2000人) ,在线文档访问量月PV 20万+。

这是众多开发者用脚投票的数据,相信这些数据比任何言语都能证明 Sa-Token 的热度。

4、Sa-Token 有哪些权威认证?

曾获荣誉包括但不限于:Gitee GVP 最有价值开源项目、GitCode G-Star 优质开源项目、OSCHINA 2021 人气指数 TOP 30 开源项目、OSCHINA 2022 年度最火热中国开源项目社区之一、开放原子基金会2023快速成长开源项目、 Dromara 组织顶尖项目(之一)、可信开源社区共同体预备成员、所在开源社区 “Dromara” 荣获《2024中国互联网发展创新与投资大赛(开源)》二等奖。 Gitee High Star 计划项目(5000+star)。Gitee 2025年度开源项目 Web应用开发 Top 2。

5、Sa-Token 收费吗?

Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永久免费开放。当然如果您有心赞助 Sa-Token,我们也不回避:赞助链接。
我们将定期同步赞助者名单到在线文档展示。(您需要注意的一点是:该赞助仅为友情赞助,不提供任何商业交换)

6、Sa-Token 是封装的 SpringSecurity 吗?是套壳 ApacheShiro 吗?

不是。Sa-Token 不是一个后台模板,也不是针对 xx 框架的二次封装套壳,而是从 0 开始的纯血自研框架,核心包零依赖,完全自主可控的架构内核 + 众多主流框架的集成适配。

证书 ⭐ 奖杯 🏆 荣誉展示

🚀 优秀开源集成案例

  • [ Snowy ]:国内首个国密前后分离快速开发平台,采用 Vue3 + Vite + SpringBoot + Mp + HuTool + SaToken。
  • [ RuoYi-Vue-Plus ]:重写RuoYi-Vue所有功能 集成 Sa-Token、Mybatis-Plus、Xxl-Job、knife4j、OSS 定期同步。
  • [ Smart-Admin ]:SmartAdmin 国内首个以「高质量代码」为核心,「简洁、高效、安全」中后台快速开发平台。
  • [ 橙单 ]: 橙单中台化低代码生成器。可完整支持多应用、多租户、多渠道、工作流、框架技术栈自由组合等。
  • [ 灯灯 ]: 专注于多租户解决方案的中后台快速开发平台。支持独立数据库、共享数据架构 和 非租户模式 ✨
  • [ 拾壹博客 ]:一款 vue + springboot 前后端分离的博客系统。

更多案例请参考:Sa-Token 优秀开源集成案例

🌍 其它语言版本

Sa-Token 社区成员贡献了多语言实现版本:

我们诚邀对上述语言较为熟练的开发者,一起建设相关版本。🤝

🔗 友情链接

  • [ OkHttps ]:轻量级 http 通信框架,API无比优雅,支持 WebSocket、Stomp 协议
  • [ Forest ]:声明式与编程式双修,让天下没有难以发送的 HTTP 请求
  • [ Bean Searcher ]:REST 版的 GraphQL — 实体定义边界,参数驱动查询,一行代码实现复杂查询!
  • [ Jpom ]:简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件。
  • [ TLog ]:一个轻量级的分布式日志标记追踪神器。
  • [ hippo4j ]:强大的动态线程池框架,附带监控报警功能。
  • [ hertzbeat ]:易用友好的开源实时监控告警系统,无需Agent,高性能集群,强大自定义监控能力。
  • [ Solon ]:一个更现代感的应用开发框架:更快、更小、更自由。
  • [ Chat2DB ]:一个AI驱动的数据库管理和BI工具,支持Mysql、pg、Oracle、Redis等22种数据库的管理。

📦 代码托管

📚 示例大全

我们为框架几乎所有技术点均单独制作了对应的集成示例,此压缩包共计 60+ Demo:涵盖 Sa-Token 登录认证、权限认证、SSO 单点登录、OAuth2 统一认证、微服务鉴权、API Key 认证、JWT集成、跨系统调用参数签名校验 等鉴权认证的方方面面。

下载地址:https://sa-token.com/doc.html#/more/download-demos

💬 交流群

QQ交流群:1098917026 点击加入

微信交流群:

PS:扫码添加微信 (备注:sa-token),邀您加入群聊。

加入群聊的好处:

  • 第一时间收到框架更新通知。
  • 第一时间收到框架 bug 通知。
  • 第一时间收到新增开源案例通知。
  • 和众多大佬一起互相 (huá shuǐ) 交流 (mō yú) 🖐️🐟️。

File: README_en.md

简体中文 | 繁體中文 | English | 日本語 | 한국어 | Русский

Sa-Token v1.46.0

✨ Open source, free, one-stop java permission authentication framework, making authentication simple and elegant!


📝 Foreword:

Looking back at the beginning of 2020, when I submitted the first line of code for Sa-Token, what Java on the market lacked at that time was not only a simple and easy-to-use authentication framework, but also a complete set of clear and self-consistent permission architecture design ideas.

Therefore, in the past few years, I have devoted a lot of time to writing Sa-Token documentation. Almost every chapter, every sentence, and every word has been repeatedly revised and finely polished in order to achieve the clearest, most concise, and easy-to-understand expression. Read the documentation carefully, and you will learn not only the Sa-Token framework itself, but also the best practices for authentication and authorization design in most scenarios.

🛠️ Sa-Token Introduction

Sa-Token is a lightweight Java permission authentication framework that currently has five core modules: login authentication, permission authentication, single sign-on, OAuth2.0, and microservice authentication.

Still hand-rolling these features? Stop, let Sa-Token handle them!

To use Sa-Token in a SpringBoot project, you only need to introduce dependencies in pom.xml:

xml
<!-- Sa-Token authority authentication, online document: https://sa-token.com -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-spring-boot-starter</artifactId>
	<version>1.46.0</version>
</dependency>

In addition to Spring Boot 2, Sa-Token also provides integration packages for common web frameworks such as Spring Boot 3/4, Solon, and JFinal — truly out of the box.

Simple examples: (click to expand / collapse)

Sa-Token aims to complete the permission authentication part of the system in a simple and elegant way. Taking login authentication as an example, you only need:

java
// Session login, the parameter is filled in the account id of the login person
StpUtil.login(10001);

There is no need to implement any interface or create any configuration file. You only need to call this static code to complete the session login authentication.

If an interface requires login before accessing it, we only need to call the following code:

java
// Verify whether the current client has logged in, if not, throw `NotLoginException` exception
StpUtil.checkLogin();

In Sa-Token, most functions can be solved with one line of code:

Kick people offline:

java
// Kick the session with account ID 10077 offline
StpUtil.kickout(10077);

Permission authentication:

java
// Annotation authentication: Only sessions with `user:add` permission can enter the method
@SaCheckPermission("user:add")
public String insert(SysUser user) {
    // ... 
    return "用户增加";
}

Route interception authentication:

java
// Divide modules according to routes, and different modules have different authentication
registry.addInterceptor(new SaInterceptor(handler -> {
	SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
	SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
	SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
	SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
	SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
	// More modules...
})).addPathPatterns("/**");

If you have used Shiro or SpringSecurity before, you will experience a qualitative leap after switching to Sa-Token.

Core module list: (click to expand/collapse)

  • Login Authentication - Single-end login, multi-end login, same-end mutually exclusive login, no login required within seven days.
  • Permission authentication——Permission authentication, role authentication, session secondary authentication.
  • Kick people offline - Kick people offline based on account ID and kick people offline based on Token value.
  • Annotation Authentication - Elegantly separates authentication from business code.
  • Route interception authentication - Based on route interception authentication, the restful mode can be adapted.
  • Session - Shared Session for all ends, exclusive Session for one end, customized Session, convenient access to values.
  • Persistence layer extension - Redis can be integrated, and data will not be lost after restarting.
  • Separation of front and backend - APPs, applets and other terminals that do not support cookies can also be easily authenticated.
  • Token style customization - six built-in Token styles, you can also customize the Token generation strategy.
  • Remember Me Mode - Adapt to [Remember Me] mode and restart the browser without verification.
  • Second Level Authentication - Authentication again based on logged in to ensure security.
  • Impersonate other accounts - Manipulate any user status data in real time.
  • Temporary identity switch - Temporarily switch the session identity to another account.
  • Same-end mutually exclusive login - Like QQ, the mobile phone and computer are online at the same time, but login is mutually exclusive on both mobile phones.
  • Account Banning - Login ban, business classification ban, and penalty ladder ban.
  • Password Encryption - Provides basic encryption algorithms, which can quickly encrypt MD5, SHA1, SHA256, and AES.
  • Session Query - Provides a convenient and flexible session query interface.
  • Http Basic Authentication - One line of code to access Http Basic and Digest authentication.
  • Global Listener - Perform some AOP operations during key operations such as user login, logout, and being kicked offline.
  • Global Filter - Conveniently handle cross-domain, global setting of security response first and other operations.
  • Multiple Account System Authentication - Multiple accounts in one system are authenticated separately (such as the User table and Admin table of the mall)
  • Single Sign-On - Built-in three single sign-on modes: same domain, cross-domain, same Redis, cross-Redis, front-end and back-end separation and other architectures can be handled.
  • Single point logout - Initiate a logout in any subsystem, and the entire system will be offline.
  • OAuth2.0 authentication - Easily build OAuth2.0 service, supporting openid mode.
  • Distributed Session - Provides a distributed session solution in a shared data center.
  • Microservice Gateway Authentication - Adapted to route interception authentication of common gateways such as Gateway, ShenYu, Zuul and so on.
  • RPC call authentication - Gateway forwarding authentication, RPC call authentication, so that service calls are no longer left unprotected
  • Temporary Token Authentication - Solve the short-term Token authorization problem.
  • Independent Redis - Separate permission cache and business cache.
  • Quick Login Authentication - Inject a login page into the project with zero code.
  • Tag Dialect - Provides Thymeleaf tag dialect integration package and provides beetl integration examples.
  • jwt integration - Provides three modes of jwt integration solutions and provides token expansion parameter capabilities.
  • RPC call status transfer - Provides integration packages such as dubbo and grpc, so that the login status is not lost when RPC is called.
  • Parameter Signature - Provides a cross-system API call signature verification module to prevent parameter tampering and request replay.
  • Automatic Renewal - Provides two Token expiration strategies, which can be used flexibly and automatically renewed.
  • Out-of-the-box - Provides common framework integration packages such as SpringMVC, WebFlux, Solon, etc., ready-to-use.
  • Latest technology stack - Adapt to the latest technology stack: support SpringBoot 3.x, jdk 17.

🍃 SSO single sign-on

Sa-Token SSO is divided into three modes, which can solve the SSO authentication requirements under 同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离、纯 js、vue2、vue3、java 项目、非 java 项目 and other architectures:

| System Architecture | Adoption Patterns | Introduction | Documentation Links |
| :-------- | :-------- |:----------------| :-------- |
| Front-end in the same domain + back-end in the same domain as Redis | Mode 1 | Shared cookie synchronization session | Document, Example |
| Different domains on the front end + the same backend as Redis | Mode 2 | URL redirection propagation session | Document, Example |
| Different front-end domains + different back-end Redis | Mode 3 | HTTP request to obtain session | Document, Example |

  1. Front-end same domain: means that multiple systems can be deployed under the same main domain name, such as: c1.domain.com, c2.domain.com, c3.domain.com
  2. The backend is the same as Redis: This means that multiple systems can connect to the same Redis and share session data.
  3. If it is impossible to ensure that the front-end is in the same domain and the back-end is in the same Redis, you can use the third mode of support: Http request verification ticket to obtain the session.
  4. Provided: NoSdk mode example + sso-server interface document, non-Sa-Token projects and non-java projects can also be connected.
  5. Provide: multiple security verification: domain name verification, ticket verification, parameter signature verification, effectively preventing ticket hijacking, request replay and other attacks.
  6. Provide: A large number of practical pain point teachings: sso-server front-end and back-end separation design, sso-client front-end and front-end separation design, user data synchronization/migration solution design.
  7. Provide: Directly runnable demo examples to help you quickly become familiar with the general SSO login process.
  8. Provide: In-depth detail optimization, parameter loss prevention: The author has tested multiple SSO frameworks, and all parameters have been lost. For example, before logging in, it was: http://a.com?id=1&name=2, and after successful login, it became: http://a.com?id=1. Sa-Token-SSO has a special algorithm to ensure that parameters are not lost, and the original path is accurately returned after successful login.

🍂 OAuth2 authorization authentication

The Sa-Token OAuth2 module is divided into four authorization modes to solve the authorization needs in different scenarios.

| Authorization Model | Introduction |
| :-------- | :-------- |
| Authorization code format | OAuth2 standard authorization steps, the server side releases the code, and the client side obtains the code code and redeems it for access_token |
| Hidden | Alternative option, the server uses URL redirection to directly transfer the access_token to the client page |
| Password type | The client directly exchanges the user's account password for authorization access_token |
| Client certificate type | The server-side client_token for the client level represents the resource authorization of the application itself |

Detailed reference document: https://sa-token.com/doc.html#/oauth2/readme

📖❓ Q&A

1. Does Sa-Token have a complete feature set?

Seven years of hard work: five core modules (login, authentication, SSO, OAuth2, microservices) + many practical plug-ins (short token, jwt integration, API parameter signature, API Key authorization...) We provide not only permission authentication, we provide a one-stop solution.

2. Is Sa-Token easy to learn?

Chinese documentation + Chinese code comments + Chinese communication community + a large number of practical case blogs + multiple video tutorials + a large number of outstanding open source project integration cases.

3. Are there many people using Sa-Token?

As of the statistics date (2026-1-25), Sa-Token is in:

  • Gitee followers reached 48627 Stars, ranking first in the list of all recommended projects on the platform.
  • GitHub attention reached 18523 Stars, which is 1.97 times that of the main competing framework Spring Security and 4.19 times that of Apache Shiro.
  • 25+ WeChat fan groups (500 people), 8+ QQ fan groups (1000 people or 2000 people), online document visits with monthly PV of 200,000+.

This is the data that many developers voted with their feet. I believe these data can prove the popularity of Sa-Token better than any words.

4. What authoritative certification does Sa-Token have?

Honors include but are not limited to: Gitee GVP Most Valuable Open Source Project, GitCode G-Star High-Quality Open Source Project, OSCHINA 2021 Popularity Index TOP 30 Open Source Project, OSCHINA 2022 One of the Most Popular Chinese Open Source Project Communities, Open Atomic Foundation 2023 Fast-Growing Open Source Project, Dromara Organization Top Project (One), Preparatory Member of the Trusted Open Source Community Community, and the open source community "Dromara" where he works Won the second prize in the "2024 China Internet Development Innovation and Investment Competition (Open Source)". Gitee High Star project (5000+star). Gitee's Top 2 Open Source Projects for Web Application Development in 2025.

5. Is there any charge for Sa-Token?

Sa-Token adopts the Apache-2.0 open source protocol and promises that the framework itself and online documents will always be free and open. Of course, if you are interested in sponsoring Sa-Token, we will not shy away from it: Sponsorship Link.
We will regularly synchronize the sponsor list to the online document display. (One thing you need to note is: this sponsorship is only a friendly sponsorship and does not provide any commercial exchange)

6. Is Sa-Token an encapsulated SpringSecurity? Is it a shell of ApacheShiro?

No. Sa-Token is not a backend template, nor is it a secondary encapsulation shell for the xx framework, but a pure self-developed framework starting from scratch, with zero dependencies on the core package, a completely independent and controllable architecture core + integrated adaptation of many mainstream frameworks.

Certificate ⭐ Trophy 🏆 Honor Display

🚀 Excellent open source integration cases

  • [ Snowy ]: China’s first rapid development platform that separates national secrets before and after, using Vue3 + Vite + SpringBoot + MP + HuTool + SaToken.
  • [ RuoYi-Vue-Plus ]: Rewrite all functions of RuoYi-Vue and integrate Sa-Token, Mybatis-Plus, Xxl-Job, knife4j, and OSS for regular synchronization.
  • [ Smart-Admin ]: SmartAdmin is the first domestic rapid development platform with "high-quality code" as the core and "simple, efficient and safe" middle and backend.
  • [ Chengdan ]: Chengdan’s low-code generator in Taiwan. It can fully support multi-applications, multi-tenants, multi-channels, workflows, free combination of framework technology stacks, etc.
  • [ Deng Deng ]: A mid- and back-end rapid development platform focusing on multi-tenant solutions. Support independent database, shared data architecture and non-tenant mode ✨
  • [ Shiyi Blog ]: A vue + springboot blog system that separates the front and back ends.

For more cases, please refer to: Sa-Token Outstanding Open-Source Integrations

🌍 Other language versions

Sa-Token community members contributed multi-language implementation versions:

We sincerely invite developers who are more proficient in the above languages to build relevant versions together. 🤝

  • [ OkHttps ]: lightweight http communication framework, extremely elegant API, supports WebSocket and Stomp protocols
  • [ Forest ]: Declarative and programmatic dual cultivation, making the world no longer difficult to send HTTP requests
  • [ Bean Searcher ]: REST version of GraphQL — entity definition boundaries, parameter-driven queries, and one line of code to implement complex queries!
  • [ Jpom ]: Simple and lightweight low-intrusive online construction, automatic deployment, daily operation and maintenance, and project monitoring software.
  • [ TLog ]: A lightweight distributed log tag tracking artifact.
  • [ hippo4j ]: A powerful dynamic thread pool framework with monitoring and alarm functions.
  • [ hertzbeat ]: An easy-to-use and friendly open source real-time monitoring and alarm system, no Agent required, high-performance cluster, and powerful custom monitoring capabilities.
  • [ Solon ]: A more modern application development framework: faster, smaller, and more free.
  • [ Chat2DB ]: An AI-driven database management and BI tool that supports the management of 22 databases such as Mysql, pg, Oracle, and Redis.

📦 Code hosting

📚 Collection of examples

We have separately produced corresponding integration examples for almost all technical points of the framework. This compressed package has a total of 60+ Demo: covering all aspects of authentication and authentication such as Sa-Token login authentication, authority authentication, SSO single sign-on, OAuth2 unified authentication, microservice authentication, API Key authentication, JWT integration, cross-system call parameter signature verification, etc.

Download address: https://sa-token.com/doc.html#/more/download-demos

💬 Communication group

QQ communication group: 1098917026 Click to join

WeChat communication group:

PS: Scan the QR code to add WeChat (remark: sa-token) to invite you to join the group chat.

Benefits of joining a group chat:

  • Receive framework update notifications as soon as possible.
  • Receive framework bug notifications as soon as possible.
  • Receive notifications of new open source cases as soon as possible.
  • Communicate (huá shuǐ) with each other (mō yú) with many big guys 🖐️🐟️.

File: README_ja.md

简体中文 | 繁體中文 | English | 日本語 | 한국어 | Русский

Sa-Token v1.46.0

✨ オープンソース、無料、ワンストップの Java 権限認証フレームワークで、認証をシンプルかつエレガントにします。


📝 序文:

私が Sa-Token のコードの最初の行を提出した 2020 年の初めを振り返ると、当時市場に出ていた Java に欠けていたのは、シンプルで使いやすい認証フレームワークだけでなく、明確で自己一貫性のあるパーミッション アーキテクチャの設計アイデア一式でもありました。

したがって、ここ数年、私は Sa-Token ドキュメントの作成に多くの時間を費やしてきました。ほぼすべての章、すべての文、すべての単語が、最も明確で簡潔でわかりやすい表現を達成するために、繰り返し修正され、細かく磨き上げられています。ドキュメントを注意深く読むと、Sa-Token フレームワーク自体だけでなく、ほとんどのシナリオにおける権限認証設計のベストプラクティスについても学ぶことができます。

🛠️ SAトークンの紹介

Sa-Token は、軽量の Java 権限認証フレームワークであり、現在、ログイン認証、権限認証、シングル サインオン、OAuth2.0、およびマイクロサービス認証の 5 つのコア モジュールを備えています。

以下の機能をまだ手作業で作っていますか?Stop,Sa-Token にお任せを!

SpringBoot プロジェクトで Sa-Token を使用するには、pom.xml に依存関係を導入するだけです。

xml
<!-- Sa-Token 機関認証、オンラインドキュメント: https://sa-token.com -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-spring-boot-starter</artifactId>
	<version>1.46.0</version>
</dependency>

SpringBoot2 と Sa-Token のサポートに加えて、SpringBoot3/4、Solon、JFinal などの一般的な Web フレームワークの統合パッケージも提供しており、すぐに使用できるようになります。

簡単な表示例: (クリックして展開/折りたたみ)

Sa-Token は、システムの許可認証部分をシンプルかつエレガントな方法で完了することを目的としています。ログイン認証を例に挙げると、必要なのは以下だけです。

java
// セッションログイン。パラメータにはログイン者のアカウントIDが入力されます。
StpUtil.login(10001);

インターフェースを実装したり、構成ファイルを作成したりする必要はありません。セッションのログイン認証を完了するには、この静的コードを呼び出すだけです。

インターフェイスにアクセスする前にログインが必要な場合は、次のコードを呼び出すだけで済みます。

java
// 現在のクライアントがログインしているかどうかを確認し、ログインしていない場合は `NotLoginException` 例外をスローします
StpUtil.checkLogin();

Sa-Token では、ほとんどの関数は 1 行のコードで解決できます。

人々をオフラインに追い出す:

java
// アカウント ID 10077 のセッションをオフラインにキックします
StpUtil.kickout(10077);

権限認証:

java
// アノテーション認証: `user:add` 権限を持つセッションのみがメソッドに入ることができます
@SaCheckPermission("user:add")
public String insert(SysUser user) {
    // ... 
    return "用户增加";
}

ルートインターセプト認証:

java
// ルートに応じてモジュールを分割し、モジュールごとに認証が異なります
registry.addInterceptor(new SaInterceptor(handler -> {
	SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
	SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
	SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
	SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
	SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
	// さらにモジュール...
})).addPathPatterns("/**");

以前にShiroまたはSpringSecurityを使用したことがある場合は、Sa-Tokenに切り替えると質的な飛躍を経験するでしょう。

コアモジュールリスト: (クリックして展開/折りたたみ)

  • ログイン認証 - シングルエンド ログイン、マルチエンド ログイン、同一エンド相互排他ログイン、7 日以内はログイン不要。
  • 権限認証——権限認証、ロール認証、セッション二次認証。
  • ユーザーをオフラインにキックする - アカウント ID に基づいてユーザーをオフラインにキックし、トークンの値に基づいてユーザーをオフラインにキックします。
  • アノテーション認証 - 認証をビジネス コードからエレガントに分離します。
  • ルート インターセプト認証 - ルート インターセプト認証に基づいて、Restful モードを適応させることができます。
  • セッション - すべてのエンドの共有セッション、一方のエンドの排他的セッション、カスタマイズされたセッション、値への便利なアクセス。
  • 永続層拡張機能 - Redis を統合でき、再起動後にデータが失われることはありません。
  • フロントエンドとバックエンドの分離 - Cookie をサポートしていない APP、アプレット、その他の端末も簡単に認証できます。
  • トークン スタイルのカスタマイズ - 6 つの組み込みトークン スタイル。トークン生成戦略をカスタマイズすることもできます。
  • Remember Me モード - [Remember Me] モードに適応し、検証せずにブラウザを再起動します。
  • 第 2 レベルの認証 - セキュリティを確保するために、ログインに基づいて再度認証を行います。
  • 他の人のアカウントを模倣 - ユーザー ステータス データをリアルタイムで操作します。
  • 一時的な ID 切り替え - セッション ID を別のアカウントに一時的に切り替えます。
  • 同一エンドの相互排他ログイン - QQ と同様、携帯電話とコンピュータは同時にオンラインですが、ログインは両方の携帯電話で相互排他的です。
  • アカウント禁止 - ログイン禁止、ビジネス分類禁止、およびペナルティラダー禁止。
  • パスワード暗号化 - MD5、SHA1、SHA256、および AES を迅速に暗号化できる基本的な暗号化アルゴリズムを提供します。
  • セッション クエリ - 便利で柔軟なセッション クエリ インターフェイスを提供します。
  • HTTP 基本認証 - HTTP 基本認証およびダイジェスト認証にアクセスするための 1 行のコード。
  • グローバル リスナー - ユーザーのログイン、ログアウト、オフラインへのキックなどの主要な操作中に、一部の AOP 操作を実行します。
  • グローバル フィルター - クロスドメイン、最初のセキュリティ応答のグローバル設定、およびその他の操作を便利に処理します。
  • 複数アカウント システム認証 - 1 つのシステム内の複数のアカウントは個別に認証されます (モールのユーザー テーブルと管理者テーブルなど)
  • シングル サインオン - 3 つのシングル サインオン モードが組み込まれています: 同一ドメイン、クロスドメイン、同一 Redis、クロス Redis、フロントエンドとバックエンドの分離、およびその他のアーキテクチャを処理できます。
  • シングル ポイント ログアウト - 任意のサブシステムでログアウトを開始すると、システム全体がオフラインになります。
  • OAuth2.0 認証 - openid モードをサポートする OAuth2.0 サービスを簡単に構築します。
  • 分散セッション - 共有データセンターで分散セッション ソリューションを提供します。
  • マイクロサービス ゲートウェイ認証 - Gateway、ShenYu、Zuul などの一般的なゲートウェイのルート インターセプト認証に適合します。
  • RPC 呼び出し認証 - ゲートウェイ転送認証、RPC 呼び出し認証により、サービス呼び出しが無防備なままにならないようにします。
  • 一時的なトークン認証 - 短期的なトークン認証の問題を解決します。
  • 独立した Redis - 権限キャッシュとビジネス キャッシュを分離します。
  • Quick クイックログイン認証 - ゼロコードでログイン ページをプロジェクトに挿入します。
  • タグ方言 - Thymeleaf タグ方言統合パッケージを提供し、beetl 統合サンプルを提供します。
  • jwt 統合 - jwt 統合ソリューションの 3 つのモードを提供し、トークン拡張パラメータ機能を提供します。
  • RPC 呼び出し状態の転送 - RPC 呼び出し時にログイン状態が失われないように、dubbo や grpc などの統合パッケージを提供します。
  • パラメータ署名 - パラメータの改ざんとリクエストのリプレイを防止するために、クロスシステム API 呼び出し署名検証モジュールを提供します。
  • 自動更新 - 柔軟に使用でき、自動的に更新できる 2 つのトークン有効期限戦略を提供します。
  • すぐに使える - SpringMVC、WebFlux、Solon など、すぐに使用できる一般的なフレームワーク統合パッケージを提供します。
  • 最新のテクノロジー スタック - 最新のテクノロジー スタックに適応: SpringBoot 3.x、jdk 17 をサポートします。

🍃 SSO シングル サインオン

Sa-Token SSO は 3 つのモードに分かれており、同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离、纯 js、vue2、vue3、java 项目、非 java 项目 およびその他のアーキテクチャでの SSO 認証要件を解決できます。

|システムアーキテクチャ |導入パターン |はじめに |ドキュメントへのリンク |
| :-------- | :-------- |:----------------| :-------- |
|同じドメイン内のフロントエンド + Redis と同じドメイン内のバックエンド |モード 1 |共有 Cookie 同期セッション | 文書、例 |
|フロントエンドの異なるドメイン + Redis と同じバックエンド |モード 2 | URL リダイレクト伝播セッション | 文書、例 |
|異なるフロントエンド ドメイン + 異なるバックエンド Redis |モード 3 |セッションを取得するための HTTP リクエスト | 文書、例 |

  1. フロントエンドの同じドメイン: c1.domain.com、c2.domain.com、c3.domain.com など、同じメイン ドメイン名の下に複数のシステムを展開できることを意味します。
  2. バックエンドは Redis と同じです。これは、複数のシステムが同じ Redis に接続してセッション データを共有できることを意味します。
  3. フロントエンドが同じドメイン内にあり、バックエンドが同じ Redis 内にあることを確認できない場合は、3 番目のサポート モードである Http リクエスト検証チケットを使用してセッションを取得できます。
  4. 提供: NoSdk モードのサンプル + sso サーバー インターフェイスのドキュメント、非 Sa-Token プロジェクトおよび非 Java プロジェクトも接続できます。
  5. 複数のセキュリティ検証を提供します: ドメイン名検証、チケット検証、パラメータ署名検証、チケットハイジャック、リクエストリプレイ、その他の攻撃を効果的に防止します。
  6. 提供: 実践的な問題点に関する多数の教示: sso サーバーのフロントエンドとバックエンドの分離設計、sso クライアントのフロントエンドとフロントエンドの分離設計、ユーザー データの同期/移行ソリューションの設計。
  7. 提供: 一般的な SSO ログイン プロセスにすぐに慣れるのに役立つ、直接実行可能なデモ サンプル。
  8. 提供: 詳細な最適化、パラメーター損失の防止: 著者は複数の SSO フレームワークをテストしましたが、すべてのパラメーターが失われました。たとえば、ログイン前は http://a.com?id=1&name=2 でしたが、ログインに成功すると http://a.com?id=1 になりました。 Sa-Token-SSO には、パラメーターが失われないようにする特別なアルゴリズムがあり、ログイン成功後に元のパスが正確に返されます。

🍂 OAuth2認可認証

Sa-Token OAuth2 モジュールは、さまざまなシナリオでの承認ニーズを解決するために 4 つの承認モードに分割されています。

|認可モデル |はじめに |
| :-------- | :-------- |
|認証コードの形式 | OAuth2 標準の認証手順では、サーバー側がコードを解放し、クライアント側がコード code を取得して、それを access_token | と引き換えます。
|非表示 |代替オプションとして、サーバーは URL リダイレクトを使用して、access_token をクライアント ページに直接転送します。
|パスワードの種類 |クライアントは、認証のためにユーザーのアカウントのパスワードを直接交換します。
|クライアント証明書の種類 |クライアント レベルのサーバー側 client_token は、アプリケーション自体のリソース認可を表します。

詳細な参考資料: https://sa-token.com/doc.html#/oauth2/readme

📖❓ Q&A

1. Sa-Token の機能は十分ですか?

7 年間の努力: 5 つのコア モジュール (ログイン、認証、SSO、OAuth2、マイクロサービス) + 多くの実用的なプラグイン (ショート トークン、jwt 統合、API パラメーターの署名、API キー認証...) 権限認証だけでなく、ワンストップ ソリューションを提供します。

2. Sa-Tokenは簡単に習得できますか?

中国語のドキュメント + 中国語のコード コメント + 中国語のコミュニケーション コミュニティ + 多数の実践的な事例ブログ + 複数のビデオ チュートリアル + 多数の優れたオープンソース プロジェクト統合事例。

3. Sa-Tokenを使っている人は多いのでしょうか?

統計日 (2026-1-25) の時点で、Sa-Token は次のとおりです。

  • Gitee フォロワーは 48627 スターに達し、プラットフォーム上のすべての推奨プロジェクトのリストで 1 位にランクされました。
  • GitHub の注目度は 18523 スターに達し、これは主要な競合フレームワーク Spring Security の 1.97 倍、Apache Taro の 4.19 倍です。
  • 25 個以上の WeChat ファン グループ (500 人)、8 個以上の QQ ファン グループ (1000 人または 2000 人)、オンライン ドキュメント アクセス (月間 PV は 200,000 人以上)。

これは多くの開発者が自分の足で投票したデータです。これらのデータがSa-Tokenの人気をどんな言葉よりも証明できると信じています。

4. Sa-Token はどのような権威ある認証を持っていますか?

栄誉には、Gitee GVP Most Valuable オープンソース プロジェクト、GitCode G-Star 高品質オープンソース プロジェクト、OSCHINA 2021 人気指数 TOP 30 オープンソース プロジェクト、OSCHINA 2022 中国で最も人気のあるオープンソース プロジェクト コミュニティの 1 つ、Open Atomic Foundation 2023 急成長オープンソース プロジェクト、Dromara Organization トップ プロジェクト (One)、Trusted Open Source Community Community の準備メンバー、および自身が活動するオープンソースコミュニティ「Dromara」「2024年中国インターネット発展イノベーション・投資コンペティション(オープンソース)」で準優勝を受賞。 Gitee ハイスター プロジェクト (5000+スター)。 2025 年の Web アプリケーション開発における Gitee のトップ 2 オープンソース プロジェクト。

5. Sa-Tokenには料金がかかりますか?

Sa-Token は、Apache-2.0 オープン ソース プロトコルを採用し、フレームワーク自体とオンライン ドキュメントが常に無料でオープンであることを約束します。もちろん、Sa-Token のスポンサーに興味がある場合は、遠慮はしません: スポンサーリンク。
スポンサーリストをオンラインドキュメント表示と定期的に同期させます。 (注意すべき点が 1 つあります。このスポンサーシップは単なる友好的なスポンサーシップであり、商業的な交流を提供するものではありません)

6. Sa-Token はカプセル化された SpringSecurity ですか? ApacheShiroのシェルでしょうか?

いいえ。 Sa-Token はバックエンド テンプレートでも、xx フレームワークの二次カプセル化シェルでもありません。コア パッケージへの依存関係がなく、完全に独立した制御可能なアーキテクチャ コア + 多くの主流フレームワークの統合適応を備えた、ゼロから開始する純粋な自社開発フレームワークです。

証明書 ⭐ トロフィー 🏆 栄誉の展示

🚀 優れたオープンソース統合事例

  • [ Snowy ]: Vue3 + Vite + SpringBoot + MP + HuTool + SaToken を使用して、国家機密を前後に分離する中国初の高速開発プラットフォーム。
  • [ RuoYi-Vue-Plus ]: RuoYi-Vue のすべての機能を書き直し、定期的な同期のために Sa-Token、Mybatis-Plus、Xxl-Job、knife4j、OSS を統合します。
  • [ Smart-Admin ]: SmartAdmin は、「高品質のコード」をコアとして、「シンプル、効率的、安全」なミドルエンドとバックエンドを備えた初の国内迅速開発プラットフォームです。
  • [ Chengdan ]: 台湾の Chengdan のローコード ジェネレーター。マルチアプリケーション、マルチテナント、マルチチャネル、ワークフロー、フレームワーク技術スタックの自由な組み合わせなどを完全にサポートできます。
  • [ Deng Deng ]: マルチテナント ソリューションに焦点を当てたミッドエンドおよびバックエンドの迅速な開発プラットフォーム。独立したデータベース、共有データ アーキテクチャ、非テナント モードをサポートします ✨
  • [ Shiyi Blog ]: フロントエンドとバックエンドを分離した vue + springboot ブログ システム。

その他の事例はこちら:Sa-Token 優秀なオープンソース統合事例

🌍 他の言語バージョン

Sa-Token コミュニティのメンバーは、多言語実装バージョンを提供しました。

上記の言語に精通した開発者を心から招待し、関連するバージョンを一緒に構築してください。 🤝

🔗 フレンドリーリンク

  • [ OkHttps ]: 軽量の http 通信フレームワーク、非常にエレガントな API、WebSocket および Stomp プロトコルをサポート
  • [ Forest ]: 宣言型とプログラム型の二重栽培により、HTTP リクエストの送信が難しくなくなりました。
  • [ Bean Searcher ]: GraphQL の REST バージョン — エンティティ定義境界、パラメーター駆動クエリ、および複雑なクエリを実装するための 1 行のコード!
  • [ Jpom ]: シンプルで軽量、低侵入型のオンライン構築、自動展開、日常の運用と保守、およびプロジェクト監視ソフトウェア。
  • [ TLog ]: 軽量の分散ログタグ追跡アーティファクト。
  • [ hippo4j ]: 監視およびアラーム機能を備えた強力な動的スレッド プール フレームワーク。
  • [ hertzbeat ]: 使いやすくフレンドリーなオープンソースのリアルタイム監視および警報システム、エージェント不要、高性能クラスター、強力なカスタム監視機能。
  • [ Solon ]: より現代的なアプリケーション開発フレームワーク: より速く、より小さく、より無料。
  • [ Chat2DB ]: Mysql、pg、Oracle、Redis など 22 のデータベースの管理をサポートする AI 主導のデータベース管理および BI ツール。

📦 コードホスティング

📚 事例集

フレームワークのほぼすべての技術点に対応する統合例を別途作成しました。この圧縮パッケージには合計 60 以上のデモが含まれており、Sa-Token ログイン認証、権限認証、SSO シングル サインオン、OAuth2 統合認証、マイクロサービス認証、API キー認証、JWT 統合、システム間呼び出しパラメータ署名検証など、認証と認証のあらゆる側面をカバーしています。

ダウンロードアドレス: https://sa-token.com/doc.html#/more/download-demos

💬コミュニケーショングループ

QQ コミュニケーション グループ: 1098917026 クリックして参加

WeChatコミュニケーショングループ:

PS: QR コードをスキャンして WeChat (注釈: sa-token) を追加し、グループ チャットへの参加を招待します。

グループチャットに参加するメリット:

  • フレームワークの更新通知をできるだけ早く受け取ります。
  • フレームワークのバグ通知をできるだけ早く受け取ります。
  • 新しいオープンソース ケースの通知をできるだけ早く受け取ります。
  • 多くの偉い人たち 🖐️🐟️ とお互いに (mō yú) コミュニケーション (huá shuiq) しましょう。

File: README_ko.md

简体中文 | 繁體中文 | English | 日本語 | 한국어 | Русский

Sa-Token v1.46.0

✨ 오픈 소스, 무료, 원스톱 Java 권한 인증 프레임워크로 인증을 간단하고 우아하게 만듭니다!


📝 서문:

2020년 초를 되돌아보면 Sa-Token의 첫 번째 코드 라인을 제출했을 때 당시 시장에 출시된 Java에는 간단하고 사용하기 쉬운 인증 프레임워크뿐 아니라 명확하고 일관된 권한 아키텍처 설계 사상이 부족했습니다.

따라서 지난 몇 년 동안 저는 Sa-Token 문서를 작성하는 데 많은 시간을 투자했습니다. 가장 명확하고 간결하며 이해하기 쉬운 표현을 얻기 위해 거의 모든 장, 모든 문장, 모든 단어가 반복적으로 수정되고 세밀하게 다듬어졌습니다. 문서를 주의 깊게 읽으면 Sa-Token 프레임워크 자체뿐만 아니라 대부분의 시나리오에서 권한 인증 설계에 대한 모범 사례도 배울 수 있습니다.

🛠️ Sa-Token 소개

Sa-Token은 현재 로그인 인증, 권한 인증, Single Sign-On, OAuth2.0 및 마이크로서비스 인증의 5가지 핵심 모듈이 있는 경량 Java 권한 인증 프레임워크입니다.

아직도 다음 기능을 손수 만들고 계신가요? Stop, Sa-Token에게 맡기세요!

SpringBoot 프로젝트에서 Sa-Token을 사용하려면 pom.xml에 종속성을 도입하기만 하면 됩니다.

xml
<!-- Sa-Token 권한 인증, 온라인 문서: https://sa-token.com -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-spring-boot-starter</artifactId>
	<version>1.46.0</version>
</dependency>

SpringBoot2 및 Sa-Token을 지원하는 것 외에도 SpringBoot3/4, Solon 및 JFinal과 같은 일반적인 웹 프레임워크에 대한 통합 패키지도 제공하므로 즉시 사용할 수 있습니다.

간단한 디스플레이 예: (확장/축소하려면 클릭)

Sa-Token은 간단하고 우아한 방식으로 시스템의 권한 인증 부분을 완성하는 것을 목표로 합니다. 로그인 인증을 예로 들면 다음만 필요합니다.

java
// 세션 로그인, 매개변수는 로그인하는 사람의 계정 ID로 채워집니다.
StpUtil.login(10001);

인터페이스를 구현하거나 구성 파일을 만들 필요가 없습니다. 세션 로그인 인증을 완료하려면 이 정적 코드만 호출하면 됩니다.

인터페이스에 액세스하기 전에 로그인이 필요한 경우 다음 코드만 호출하면 됩니다.

java
// 현재 클라이언트가 로그인되어 있는지 확인하십시오. 로그인되어 있지 않으면 `NotLoginException` 예외가 발생합니다.
StpUtil.checkLogin();

Sa-Token에서는 대부분의 기능을 한 줄의 코드로 해결할 수 있습니다.

사람들을 오프라인으로 몰아내기:

java
// 계정 ID 10077로 오프라인에서 세션 시작
StpUtil.kickout(10077);

권한 인증:

java
// 주석 인증: `user:add` 권한이 있는 세션만 메서드에 들어갈 수 있습니다.
@SaCheckPermission("user:add")
public String insert(SysUser user) {
    // ... 
    return "用户增加";
}

경로 차단 인증:

java
// 경로에 따라 모듈을 나누고, 모듈마다 인증이 다릅니다.
registry.addInterceptor(new SaInterceptor(handler -> {
	SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
	SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
	SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
	SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
	SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
	// 더 많은 모듈...
})).addPathPatterns("/**");

이전에 Shiro 또는 SpringSecurity를 사용한 적이 있다면 Sa-Token으로 전환한 후 질적 도약을 경험하게 될 것입니다.

핵심 모듈 개요: (확장/축소하려면 클릭)

  • 로그인 인증 - 단일 엔드 로그인, 멀티 엔드 로그인, 동일 엔드 상호 배타적 로그인, 7일 이내에는 로그인이 필요하지 않습니다.
  • 권한 인증——권한 인증, 역할 인증, 세션 보조 인증.
  • 오프라인 추방 - 계정 ID를 기준으로 사람들을 오프라인으로 추방하고 토큰 가치를 기준으로 사람들을 오프라인으로 추방합니다.
  • 주석 인증 - 인증을 비즈니스 코드와 우아하게 분리합니다.
  • 경로차단 인증 - 경로차단 인증을 기반으로 Restful 모드를 적용할 수 있습니다.
  • 세션 - 모든 끝을 위한 공유 세션, 한쪽 끝을 위한 독점 세션, 맞춤형 세션, 가치에 대한 편리한 접근.
  • 지속성 레이어 확장 - Redis를 통합할 수 있으며 다시 시작해도 데이터가 손실되지 않습니다.
  • 프런트엔드와 백엔드 분리 - 쿠키를 지원하지 않는 앱, 애플릿, 기타 단말기도 쉽게 인증할 수 있습니다.
  • 토큰 스타일 사용자 정의 - 6가지 기본 제공 토큰 스타일, 토큰 생성 전략을 사용자 정의할 수도 있습니다.
  • 기억하기 모드 - [기억하기] 모드에 적응하고 확인 없이 브라우저를 다시 시작합니다.
  • 2차 인증 - 보안 확보를 위해 로그인 기반으로 다시 인증합니다.
  • 다른 사람의 계정을 모방 - 사용자 상태 데이터를 실시간으로 조작합니다.
  • 임시 ID 전환 - 세션 ID를 일시적으로 다른 계정으로 전환합니다.
  • 동일 엔드 상호 배타적 로그인 - QQ와 마찬가지로 휴대폰과 컴퓨터가 동시에 온라인 상태이지만 로그인은 두 휴대폰 모두에서 상호 배타적입니다.
  • 계정 차단 - 로그인 차단, 업종 분류 차단, 페널티 래더 차단.
  • 비밀번호 암호화 - MD5, SHA1, SHA256, AES를 빠르게 암호화할 수 있는 기본 암호화 알고리즘을 제공합니다.
  • 세션 쿼리 - 편리하고 유연한 세션 쿼리 인터페이스를 제공합니다.
  • Http 기본 인증 - Http 기본 및 다이제스트 인증에 액세스하기 위한 코드 한 줄입니다.
  • 전역 리스너 - 사용자 로그인, 로그아웃, 오프라인 추방과 같은 주요 작업 중에 일부 AOP 작업을 수행합니다.
  • 글로벌 필터 - 크로스 도메인, 보안 대응 우선 글로벌 설정 및 기타 작업을 편리하게 처리합니다.
  • 다중 계정 시스템 인증 - 하나의 시스템에서 여러 계정을 별도로 인증합니다. (몰의 사용자 테이블, 관리 테이블 등)
  • Single Sign-On - 3가지 Single Sign-On 모드 내장: 동일한 도메인, 교차 도메인, 동일한 Redis, 교차 Redis, 프런트엔드 및 백엔드 분리 및 기타 아키텍처를 처리할 수 있습니다.
  • 단일 지점 로그아웃 - 모든 하위 시스템에서 로그아웃을 시작하면 전체 시스템이 오프라인이 됩니다.
  • OAuth2.0 인증 - openid 모드를 지원하는 OAuth2.0 서비스를 쉽게 구축할 수 있습니다.
  • 분산 세션 - 공유 데이터 센터에서 분산 세션 솔루션을 제공합니다.
  • 마이크로서비스 게이트웨이 인증 - Gateway, ShenYu, Zuul 등과 같은 일반 게이트웨이의 경로 차단 인증에 적합합니다.
  • RPC 호출 인증 - 게이트웨이 전달 인증, RPC 호출 인증을 통해 서비스 호출이 더 이상 무방비 상태가 되지 않도록 함
  • 임시 토큰 인증 - 단기 토큰 인증 문제를 해결합니다.
  • 독립적인 Redis - 권한 캐시와 비즈니스 캐시를 분리합니다.
  • Quick 빠른 로그인 인증 - 제로 코드로 프로젝트에 로그인 페이지를 삽입합니다.
  • Tag Dialect - Thymeleaf 태그 Dialect 통합 패키지를 제공하고 beetl 통합 예제를 제공합니다.
  • jwt 통합 - jwt 통합 솔루션의 세 가지 모드를 제공하고 토큰 확장 매개변수 기능을 제공합니다.
  • RPC 호출 상태 전송 - RPC 호출 시 로그인 상태가 손실되지 않도록 dubbo, grpc 등의 통합 패키지를 제공합니다.
  • 매개변수 서명 - 매개변수 변조를 방지하고 재생을 요청하기 위해 시스템 간 API 호출 서명 확인 모듈을 제공합니다.
  • 자동 갱신 - 유연하게 자동으로 갱신할 수 있는 두 가지 토큰 만료 전략을 제공합니다.
  • 즉시 사용 가능 - 즉시 사용 가능한 SpringMVC, WebFlux, Solon 등과 같은 공통 프레임워크 통합 패키지를 제공합니다.
  • 최신 기술 스택 - 최신 기술 스택에 적응: SpringBoot 3.x, jdk 17을 지원합니다.

🍃 SSO 싱글 사인온

Sa-Token SSO는 同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离、纯 js、vue2、vue3、java 项目、非 java 项目과 같은 아키텍처에서 SSO 인증 요구 사항을 해결할 수 있는 세 가지 모드로 구분됩니다.

| 시스템 아키텍처 | 채택 패턴 | 소개 | 문서 링크 |
| :-------- | :-------- |:----------------| :-------- |
| Redis와 동일한 도메인의 프런트엔드 + 동일한 도메인의 백엔드 | 모드 1 | 공유 쿠키 동기화 세션 | 문서, 예제 |
| 프런트 엔드의 다른 도메인 + Redis와 동일한 백엔드 | 모드 2 | URL 리디렉션 전파 세션 | 문서, 예제 |
| 다양한 프런트엔드 도메인 + 다양한 백엔드 Redis | 모드 3 | 세션을 얻기 위한 HTTP 요청 | 문서, 예제 |

  1. 프런트엔드 동일 도메인: c1.domain.com, c2.domain.com, c3.domain.com와 같이 동일한 기본 도메인 이름으로 여러 시스템을 배포할 수 있음을 의미합니다.
  2. 백엔드는 Redis와 동일합니다. 즉, 여러 시스템이 동일한 Redis에 연결하고 세션 데이터를 공유할 수 있음을 의미합니다.
  3. 프런트엔드가 동일한 도메인에 있고 백엔드가 동일한 Redis에 있는지 확인할 수 없는 경우 세 번째 지원 모드인 Http 요청 확인 티켓을 사용하여 세션을 얻을 수 있습니다.
  4. 제공 : NoSdk 모드 예시 + sso-server 인터페이스 문서, Sa-Token이 아닌 프로젝트, Java가 아닌 프로젝트도 연결 가능합니다.
  5. 제공: 다중 보안 확인: 도메인 이름 확인, 티켓 확인, 매개변수 서명 확인, 티켓 하이재킹, 요청 재생 및 기타 공격을 효과적으로 방지합니다.
  6. 제공: 다양한 실제 문제점 교육: sso-서버 프런트엔드 및 백엔드 분리 설계, sso-클라이언트 프런트엔드 및 프런트엔드 분리 설계, 사용자 데이터 동기화/마이그레이션 솔루션 설계.
  7. 제공: 일반 SSO 로그인 프로세스에 빠르게 익숙해지는 데 도움이 되는 직접 실행 가능한 데모 예제입니다.
  8. 제공: 심층적인 세부 최적화, 매개변수 손실 방지: 저자는 여러 SSO 프레임워크를 테스트했으며 모든 매개변수가 손실되었습니다. 예를 들어, 로그인하기 전에는 http://a.com?id=1&name=2이었고, 로그인에 성공한 후에는 http://a.com?id=1이 되었습니다. Sa-Token-SSO에는 매개변수가 손실되지 않고 로그인 성공 후 원래 경로가 정확하게 반환되도록 보장하는 특별한 알고리즘이 있습니다.

🍂 OAuth2 인증 인증

Sa-Token OAuth2 모듈은 다양한 시나리오의 인증 요구 사항을 해결하기 위해 4가지 인증 모드로 구분됩니다.

| 인증 모델 | 소개 |
| :-------- | :-------- |
| 인증 코드 형식 | OAuth2 표준 인증 단계에서 서버측은 코드를 릴리스하고 클라이언트측은 코드 코드를 획득하여 access_token |
| 숨겨진 | 대체 옵션으로, 서버는 URL 리디렉션을 사용하여 access_token을 클라이언트 페이지로 직접 전송합니다. |
| 비밀번호 유형 | 클라이언트는 인증을 위해 사용자의 계정 비밀번호를 직접 교환합니다 access_token |
| 클라이언트 인증서 유형 | 클라이언트 레벨의 서버측 client_token은 애플리케이션 자체의 자원 인증을 나타냅니다. |

자세한 참조 문서: https://sa-token.com/doc.html#/oauth2/readme

📖❓Q&A

1. Sa-Token 기능은 충분한가요?

7년간의 노력: 5개의 핵심 모듈(로그인, 인증, SSO, OAuth2, 마이크로서비스) + 많은 실용적인 플러그인(짧은 토큰, jwt 통합, API 매개변수 서명, API 키 인증...) 권한 인증뿐만 아니라 원스톱 솔루션을 제공합니다.

2. Sa-Token은 배우기 쉬운가요?

중국어 문서 + 중국어 코드 주석 + 중국어 커뮤니케이션 커뮤니티 + 수많은 실제 사례 블로그 + 여러 비디오 튜토리얼 + 수많은 우수한 오픈 소스 프로젝트 통합 사례.

3. Sa-Token을 사용하는 사람들이 많이 있나요?

통계일(2026-1-25) 현재 Sa-Token의 위치는 다음과 같습니다.

  • Gitee 팔로어는 48627개의 별에 도달하여 플랫폼의 모든 추천 프로젝트 목록에서 1위를 차지했습니다.
  • GitHub 관심은 18523 Stars에 도달했는데, 이는 주요 경쟁 프레임워크인 Spring Security의 1.97배, Apache Shiro의 4.19배입니다.
  • 25개 이상의 WeChat 팬 그룹(500명), 8개 이상의 QQ 팬 그룹(1000명 또는 2000명), 월간 PV 200,000+ 이상의 온라인 문서 방문.

많은 개발자들이 발로 투표한 데이터입니다. 나는 이 데이터가 어떤 말보다 Sa-Token의 인기를 더 잘 증명할 수 있다고 믿습니다.

4. Sa-Token은 어떤 권위 있는 인증을 보유하고 있나요?

명예에는 Gitee GVP 최우수 오픈 소스 프로젝트, GitCode G-Star 고품질 오픈 소스 프로젝트, OSCHINA 2021 인기 지수 TOP 30 오픈 소스 프로젝트, OSCHINA 2022 가장 인기 있는 중국 오픈 소스 프로젝트 커뮤니티 중 하나, Open Atomic Foundation 2023 빠르게 성장하는 오픈 소스 프로젝트, Dromara 조직 상위 프로젝트(1개), 신뢰할 수 있는 오픈 소스 커뮤니티의 예비 회원 및 그가 활동하고 있는 오픈소스 커뮤니티 'Dromara'가 '2024년 중국 인터넷 발전 혁신 및 투자 대회(오픈소스)'에서 2등상을 수상했습니다. Gitee High Star 프로젝트(5000+star). 2025년 웹 애플리케이션 개발을 위한 Gitee의 상위 2개 오픈 소스 프로젝트입니다.

5. Sa-Token에는 요금이 부과되나요?

Sa-Token은 Apache-2.0 오픈 소스 프로토콜을 채택하고 프레임워크 자체와 온라인 문서가 항상 무료이고 개방적임을 약속합니다. 물론 Sa-Token 후원에 관심이 있으시면 후원 링크를 주저하지 않으셔도 됩니다.
우리는 후원자 목록을 온라인 문서 표시와 정기적으로 동기화합니다. (주의하셔야 할 점은 본 후원은 우호적인 후원일 뿐 어떠한 상업적인 교류도 제공하지 않는다는 점입니다)

6. Sa-Token은 캡슐화된 SpringSecurity입니까? ApacheShiro의 쉘입니까?

아니요. Sa-Token은 백엔드 템플릿도 아니고 xx 프레임워크를 위한 보조 캡슐화 셸도 아니지만 처음부터 시작하는 순수한 자체 개발 프레임워크이며 코어 패키지에 대한 종속성이 전혀 없으며 완전히 독립적이고 제어 가능한 아키텍처 코어 + 많은 주류 프레임워크의 통합 적응입니다.

인증서 ⭐ 트로피 🏆 명예 전시

🚀 우수한 오픈소스 통합 사례

  • [ Snowy ]: Vue3 + Vite + SpringBoot + MP + HuTool + SaToken을 사용하여 국가 기밀 전후를 분리하는 중국 최초의 급속 개발 플랫폼입니다.
  • [ RuoYi-Vue-Plus ]: RuoYi-Vue의 모든 기능을 다시 작성하고 정기적인 동기화를 위해 Sa-Token, Mybatis-Plus, Xxl-Job, Knife4j 및 OSS를 통합합니다.
  • [ Smart-Admin ] : SmartAdmin은 "고품질 코드"를 핵심으로 하고 "간단하고 효율적이며 안전한" 미들과 백엔드를 갖춘 국내 최초의 고속 개발 플랫폼입니다.
  • [ Orange Order ]: Orange Order는 대만의 로우 코드 생성기입니다. 다중 애플리케이션, 다중 테넌트, 다중 채널, 워크플로우, 프레임워크 기술 스택의 자유로운 조합 등을 완벽하게 지원할 수 있습니다.
  • [ Deng Deng ]: 멀티 테넌트 솔루션에 초점을 맞춘 미드엔드 및 백엔드 신속한 개발 플랫폼입니다. 독립 데이터베이스, 공유 데이터 아키텍처 및 비테넌트 모드 지원 ✨
  • [ Shiyi 블로그 ]: 프런트엔드와 백엔드를 분리하는 vue + springboot 블로그 시스템입니다.

더 많은 사례는 다음을 참고하세요: Sa-Token 우수 오픈소스 통합 사례

🌍 다른 언어 버전

Sa-Token 커뮤니티 회원은 다국어 구현 버전에 기여했습니다.

위의 언어에 좀 더 능숙한 개발자들이 함께 관련 버전을 만들어 보도록 진심으로 초대합니다. 🤝

🔗 친절한 링크

  • [ OkHttps ]: 경량 http 통신 프레임워크, 매우 우아한 API, WebSocket 및 Stomp 프로토콜 지원
  • [ Forest ]: 선언적 및 프로그래밍적 이중 재배로 세상이 더 이상 HTTP 요청을 보내는 것이 어렵지 않게 만듭니다.
  • [ Bean Searcher ]: GraphQL의 REST 버전 - 엔터티는 경계를 정의하고 매개변수 기반 쿼리 및 복잡한 쿼리를 한 줄의 코드로 구현할 수 있습니다!
  • [ Jpom ]: 간단하고 가벼운 저침해 온라인 구축, 자동 배포, 일일 운영 및 유지 관리, 프로젝트 모니터링 소프트웨어.
  • [ TLog ]: 경량 분산 로그 태그 추적 아티팩트입니다.
  • [ hippo4j ]: 모니터링 및 경보 기능을 갖춘 강력한 동적 스레드 풀 프레임워크입니다.
  • [ hertzbeat ]: 사용하기 쉽고 친숙한 오픈 소스 실시간 모니터링 및 경보 시스템, 에이전트가 필요하지 않음, 고성능 클러스터 및 강력한 사용자 정의 모니터링 기능.
  • [ Solon ]: 보다 현대적인 애플리케이션 개발 프레임워크: 더 빠르고, 더 작고, 더 무료입니다.
  • [ Chat2DB ]: Mysql, pg, Oracle, Redis 등 22개의 데이터베이스 관리를 지원하는 AI 기반 데이터베이스 관리 및 BI 도구입니다.

📦 코드 호스팅

📚 예시 모음

우리는 프레임워크의 거의 모든 기술적 사항에 대해 해당 통합 예제를 별도로 제작했습니다. 이 압축 패키지에는 Sa-Token 로그인 인증, 권한 인증, SSO 싱글 사인온, OAuth2 통합 인증, 마이크로서비스 인증, API 키 인증, JWT 통합, 시스템 간 호출 매개변수 서명 확인 등과 같은 인증 및 인증의 모든 측면을 다루는 총 60개 이상의 데모가 있습니다.

다운로드 주소: https://sa-token.com/doc.html#/more/download-demos

💬 커뮤니케이션 그룹

QQ 커뮤니케이션 그룹: 1098917026 가입하려면 클릭

WeChat 커뮤니케이션 그룹:

추신: QR 코드를 스캔하여 WeChat(비고: sa-token)을 추가하여 그룹 채팅에 참여하도록 초대하세요.

그룹 채팅에 참여하면 얻을 수 있는 이점:

  • 가능한 한 빨리 프레임워크 업데이트 알림을 받으세요.
  • 가능한 한 빨리 프레임워크 버그 알림을 받습니다.
  • 새로운 오픈소스 사례에 대한 알림을 최대한 빨리 받아보세요.
  • 많은 덩치🖐️🐟️와 서로(모유) 소통(화수)합니다.

File: README_ru.md

简体中文 | 繁體中文 | English | 日本語 | 한국어 | Русский

Sa-Token v1.46.0

✨ Бесплатная универсальная платформа аутентификации Java-разрешений с открытым исходным кодом, делающая аутентификацию простой и элегантной!


📝 Предисловие:

Оглядываясь назад на начало 2020 года, когда я представил первую строку кода для Sa-Token, я понимаю, что Java на рынке в то время не хватало не только простой и удобной в использовании инфраструктуры аутентификации, но и целостного набора четких и самосогласованных идей проектирования архитектуры разрешений.

Поэтому в последние несколько лет я посвятил много времени написанию документации Sa-Token. Почти каждая глава, каждое предложение и каждое слово были неоднократно переработаны и тщательно отшлифованы, чтобы добиться максимально ясного, лаконичного и легкого для понимания выражения. Внимательно прочитайте документацию, и вы изучите не только саму структуру Sa-Token, но и лучшие практики проектирования аутентификации и авторизации в большинстве сценариев.

🛠️ Введение Sa-Token

Sa-Token — это облегченная платформа аутентификации разрешений Java, которая в настоящее время имеет пять основных модулей: аутентификация входа, аутентификация разрешений, единый вход, OAuth2.0 и аутентификация микросервиса.

Вы всё ещё пишете эти функции вручную? Stop, пусть поможет Sa-Token!

Чтобы использовать Sa-Token в проекте SpringBoot, вам нужно всего лишь ввести зависимости в pom.xml:

xml
<!-- Аутентификация органа Sa-Token, онлайн-документ: https://sa-token.com -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-spring-boot-starter</artifactId>
	<version>1.46.0</version>
</dependency>

Помимо поддержки SpringBoot2 и Sa-Token, он также предоставляет пакеты интеграции для распространенных веб-платформ, таких как SpringBoot3/4, Solon и JFinal, что делает его по-настоящему пригодным для использования прямо из коробки.

Простой пример отображения: (нажмите, чтобы развернуть/свернуть)

Sa-Token стремится завершить часть системы аутентификации разрешений простым и элегантным способом. Если взять в качестве примера аутентификацию при входе, вам нужно всего лишь:

java
// Вход в сеанс, параметр заполняется идентификатором учетной записи пользователя, вошедшего в систему.
StpUtil.login(10001);

Нет необходимости реализовывать какой-либо интерфейс или создавать какой-либо файл конфигурации. Вам нужно только вызвать этот статический код для завершения аутентификации входа в сеанс.

Если интерфейс требует входа в систему перед доступом к нему, нам нужно всего лишь вызвать следующий код:

java
// Проверьте, вошел ли текущий клиент в систему, если нет, выдайте исключение `NotLoginException`.
StpUtil.checkLogin();

В Sa-Token большинство функций можно решить одной строкой кода:

Выкидывать людей из сети:

java
// Отключить сеанс с идентификатором учетной записи 10077 в автономном режиме.
StpUtil.kickout(10077);

Аутентификация разрешения:

java
// Аутентификация аннотаций: только сеансы с разрешением `user:add` могут войти в этот метод.
@SaCheckPermission("user:add")
public String insert(SysUser user) {
    // ... 
    return "用户增加";
}

Аутентификация перехвата маршрута:

java
// Разделите модули по маршрутам, и разные модули имеют разную аутентификацию.
registry.addInterceptor(new SaInterceptor(handler -> {
	SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
	SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
	SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
	SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
	SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
	// Еще модули...
})).addPathPatterns("/**");

Если вы раньше использовали Shiro или SpringSecurity, после перехода на Sa-Token вы испытаете качественный скачок.

Список основных модулей: (нажмите, чтобы развернуть/свернуть)

  • Аутентификация при входе - Односторонний вход, многосторонний вход, взаимоисключающий вход на одном конце, вход в систему не требуется в течение семи дней.
  • Аутентификация разрешений ——Аутентификация разрешений, аутентификация ролей, вторичная аутентификация сеанса.
  • Выключать людей из сети. Выключать людей из сети на основе идентификатора учетной записи и исключать людей из сети на основе значения токена.
  • Аутентификация по аннотациям — элегантно отделяет аутентификацию от бизнес-кода.
  • Аутентификация перехвата маршрута - На основе аутентификации перехвата маршрута можно адаптировать под RESTful-режим.
  • Сеанс — общий сеанс для всех концов, эксклюзивный сеанс для одного конца, настраиваемый сеанс, удобный доступ к значениям.
  • Расширение уровня сохранения. Redis можно интегрировать, и данные не будут потеряны после перезапуска.
  • Разделение внешней и внутренней части. Приложения, апплеты и другие терминалы, не поддерживающие файлы cookie, также можно легко аутентифицировать.
  • Настройка стиля токена — шесть встроенных стилей токенов, вы также можете настроить стратегию генерации токенов.
  • Режим «Запомнить меня» — адаптируйтесь к режиму [Запомнить меня] и перезапустите браузер без проверки.
  • Аутентификация второго уровня — Повторная аутентификация на основе входа в систему для обеспечения безопасности.
  • Имитация учетных записей других людей - Манипулируйте любыми данными о статусе пользователя в режиме реального времени.
  • Временное переключение удостоверения — Временное переключение удостоверения сеанса на другую учетную запись.
  • Взаимоисключающий вход с одного и того же конца - Как и в случае с QQ, мобильный телефон и компьютер находятся в сети одновременно, но вход в систему является взаимоисключающим на обоих мобильных телефонах.
  • Блокировка учетной записи. Блокировка входа в систему, блокировка бизнес-класса и блокировка по шкале штрафов.
  • Шифрование паролей. Предоставляет базовые алгоритмы шифрования, позволяющие быстро шифровать MD5, SHA1, SHA256 и AES.
  • Запрос сеанса. Предоставляет удобный и гибкий интерфейс запроса сеанса.
  • Базовая аутентификация HTTP — одна строка кода для доступа к базовой и дайджест-аутентификации Http.
  • Глобальный прослушиватель. Выполнение некоторых операций АОП во время ключевых операций, таких как вход пользователя в систему, выход из системы и выход из сети.
  • Глобальный фильтр. Удобная обработка междоменных, глобальных настроек ответа безопасности в первую очередь и других операций.
  • Системная аутентификация с несколькими учетными записями. Несколько учетных записей в одной системе аутентифицируются отдельно (например, таблица пользователей и таблица администраторов торгового центра).
  • Единый вход. Встроены три режима единого входа: тот же домен, междоменный, тот же Redis, перекрестный Redis, разделение внешнего и внутреннего интерфейса, а также могут обрабатываться другие архитектуры.
  • Одноточечный выход — инициируйте выход из системы в любой подсистеме, и вся система перейдет в автономный режим.
  • Аутентификация OAuth2.0 - Легко создавать службу OAuth2.0, поддерживающую режим openid.
  • Распределенный сеанс — предоставляет решение для распределенного сеанса в общем центре обработки данных.
  • Аутентификация микросервисного шлюза - Адаптировано для аутентификации перехвата маршрутов общих шлюзов, таких как Gateway, ShenYu, Zuul и т. д.
  • Аутентификация вызовов RPC - Аутентификация пересылки шлюза, аутентификация вызовов RPC, чтобы вызовы сервисов больше не оставались без защиты
  • Временная аутентификация по токену - Решите краткосрочную проблему авторизации по токену.
  • Независимый Redis - Отдельный кеш разрешений и бизнес-кеш.
  • Quick — быстрая аутентификация входа - Добавьте в проект страницу входа без кода.
  • Диалект тегов — предоставляет пакет интеграции диалекта тегов Thymeleaf и примеры интеграции Beetl.
  • Интеграция jwt — предоставляет три режима решений интеграции jwt и предоставляет возможности расширения параметров токена.
  • Передача статуса вызова RPC - Предоставляет пакеты интеграции, такие как dubbo и grpc, чтобы статус входа не терялся при вызове RPC.
  • Подпись параметра. Предоставляет межсистемный модуль проверки подписи вызова API для предотвращения подделки параметров и повторного запроса.
  • Автоматическое продление. Обеспечивает две стратегии истечения срока действия токена, которые можно гибко использовать и автоматически продлевать.
  • Готово - Предоставляет готовые к использованию общие пакеты интеграции с инфраструктурой, такие как SpringMVC, WebFlux, Solon и т. д.
  • Новейший стек технологий. Адаптируйтесь к новейшему стеку технологий: поддержка SpringBoot 3.x, jdk 17.

🍃 Единый вход

Единый вход Sa-Token разделен на три режима, которые могут удовлетворить требования аутентификации единого входа в 同域、跨域、共享Redis、跨Redis、前后端一体、前后端分离、纯 js、vue2、vue3、java 项目、非 java 项目 и других архитектурах:

| Системная архитектура | Модели принятия | Введение | Ссылки на документацию |
| :-------- | :-------- |:----------------| :-------- |
| Интерфейсная часть в том же домене + серверная часть в том же домене, что и Redis | Режим 1 | Общий сеанс синхронизации файлов cookie | Документ, Пример |
| Разные домены во внешнем интерфейсе + тот же серверный интерфейс, что и у Redis | Режим 2 | Сеанс распространения перенаправления URL | Документ, Пример |
| Разные внешние домены + разные внутренние Redis | Режим 3 | HTTP-запрос для получения сеанса | Документ, Пример |

  1. Один и тот же домен внешнего интерфейса: означает, что несколько систем могут быть развернуты под одним и тем же основным именем домена, например: c1.domain.com, c2.domain.com, c3.domain.com.
  2. Серверная часть такая же, как у Redis. Это означает, что несколько систем могут подключаться к одному и тому же Redis и совместно использовать данные сеанса.
  3. Если невозможно гарантировать, что интерфейсная часть находится в одном домене, а серверная часть находится в одном Redis, вы можете использовать третий режим поддержки: HTTP-запрос проверочного билета для получения сеанса.
  4. Предоставлено: пример режима NoSdk + документ интерфейса sso-сервера, также можно подключить проекты, не относящиеся к Sa-Token, и проекты, не относящиеся к Java.
  5. Обеспечить: множественную проверку безопасности: проверку доменного имени, проверку билета, проверку подписи параметров, эффективное предотвращение перехвата билетов, повтора запроса и других атак.
  6. Предоставить: Большое количество практических уроков по болевым точкам: проектирование разделения внешнего и внутреннего интерфейса sso-сервера, проектирование разделения внешнего и внешнего интерфейса sso-клиента, разработка решения для синхронизации/миграции пользовательских данных.
  7. Предоставьте: Демонстрационные примеры, которые можно запускать напрямую, которые помогут вам быстро ознакомиться с общим процессом входа в систему единого входа.
  8. Обеспечьте: Глубокую детальную оптимизацию, предотвращение потери параметров: автор протестировал несколько инфраструктур единого входа, и все параметры были потеряны. Например, до входа в систему это было: http://a.com?id=1&name=2, а после успешного входа стало: http://a.com?id=1. Sa-Token-SSO имеет специальный алгоритм, гарантирующий, что параметры не будут потеряны, а исходный путь будет точно возвращен после успешного входа в систему.

🍂 аутентификация авторизации OAuth2

Модуль Sa-Token OAuth2 разделен на четыре режима авторизации для удовлетворения потребностей авторизации в различных сценариях.

| Модель авторизации | Введение |
| :-------- | :-------- |
| Формат кода авторизации | Стандартные шаги авторизации OAuth2: серверная сторона выпускает код, а клиентская сторона получает код кода и выкупает его для access_token |
| Скрытый | Альтернативный вариант: сервер использует перенаправление URL-адресов для прямой передачи access_token на клиентскую страницу |
| Тип пароля | Клиент напрямую обменивает пароль учетной записи пользователя на авторизацию access_token |
| Тип сертификата клиента | Серверный client_token для уровня клиента представляет авторизацию ресурса самого приложения |

Подробный справочный документ: https://sa-token.com/doc.html#/oauth2/readme

📖❓ Вопросы и ответы

1. Достаточно ли функционален Sa-Token?

Семь лет напряженной работы: пять основных модулей (вход в систему, аутентификация, единый вход, OAuth2, микросервисы) + множество практических плагинов (короткий токен, интеграция jwt, подпись параметров API, авторизация ключа API...). Мы обеспечиваем не только аутентификацию разрешений, мы предоставляем универсальное решение.

2. Легко ли освоить Sa-Token?

Документация на китайском языке + комментарии к коду на китайском языке + китайское коммуникационное сообщество + большое количество блогов с практическими примерами + множество видеоуроков + большое количество выдающихся примеров интеграции проектов с открытым исходным кодом.

3. Много ли людей используют Sa-Token?

По состоянию на дату статистики (25 января 2026 г.) Sa-Token находится в:

  • Подписчики Gitee получили 48627 звезд, заняв первое место в списке всех рекомендуемых проектов на платформе.
    — Внимание GitHub достигло 18523 звезд, что в 1,97 раза больше, чем у основного конкурирующего фреймворка Spring Security, и в 4,19 раза больше, чем у Apache Shiro.
  • 25+ фан-групп WeChat (500 человек), 8+ фан-групп QQ (1000 или 2000 человек), онлайн-посещения документов с ежемесячным PV более 200 000 человек.

Это данные, за которые многие разработчики проголосовали ногами. Я считаю, что эти данные могут доказать популярность Sa-Token лучше любых слов.

4. Какую авторитетную сертификацию имеет Sa-Token?

Награды включают, помимо прочего: Самый ценный проект с открытым исходным кодом Gitee GVP, Высококачественный проект с открытым исходным кодом GitCode G-Star, Индекс популярности TOP 30 проектов с открытым исходным кодом OSCHINA 2021, Одно из самых популярных китайских сообществ проектов с открытым исходным кодом OSCHINA 2022, Быстрорастущий проект с открытым исходным кодом Open Atomic Foundation 2023, Лучший проект организации Dromara (один), Подготовительный член Trusted Open Source Сообщество сообщества и сообщество открытого исходного кода «Dromara», где он работает. Выиграли второй приз в «Конкурсе инноваций и инвестиций в области развития Интернета в Китае 2024 года (с открытым исходным кодом)». Проект Gitee High Star (5000+звезд). Два лучших проекта Gitee с открытым исходным кодом для разработки веб-приложений в 2025 году.

5. Взимается ли плата за Sa-Token?

Sa-Token использует протокол с открытым исходным кодом Apache-2.0 и обещает, что сама платформа и онлайн-документы всегда будут бесплатными и открытыми. Конечно, если вы заинтересованы в спонсировании Sa-Token, мы не будем уклоняться от этого: Спонсорская ссылка.
Мы будем регулярно синхронизировать список спонсоров с онлайн-дисплеем документов. (Одна вещь, которую вам следует отметить: это спонсорство является только дружественным спонсорством и не предполагает какого-либо коммерческого обмена)

6. Является ли Sa-Token инкапсулированным SpringSecurity? Это оболочка ApacheShiro?

нет. Sa-Token — это не бэкэнд-шаблон и не вторичная оболочка инкапсуляции для платформы xx, а чистый фреймворк собственной разработки, начинающийся с нуля, с нулевой зависимостью от основного пакета, полностью независимым и управляемым ядром архитектуры + интегрированной адаптацией многих основных фреймворков.

Сертификат ⭐ Трофей 🏆 Почетный знак

🚀 Отличные примеры интеграции с открытым исходным кодом

  • [ Snowy ]: первая в Китае платформа быстрой разработки, которая разделяет национальные секреты до и после, используя Vue3 + Vite + SpringBoot + MP + HuTool + SaToken.
  • [ RuoYi-Vue-Plus ]: переписать все функции RuoYi-Vue и интегрировать Sa-Token, Mybatis-Plus, Xxl-Job, нож4j и OSS для регулярной синхронизации.
  • [ Smart-Admin ]: SmartAdmin — это первая отечественная платформа быстрой разработки с «высококачественным кодом» в качестве ядра и «простым, эффективным и безопасным» промежуточным и серверным компонентами.
  • [ Chengdan ]: низкокодовый генератор Chengdan на Тайване. Он может полностью поддерживать несколько приложений, несколько арендаторов, несколько каналов, рабочие процессы, свободное сочетание стеков технологий платформы и т. д.
  • [ Deng Deng ]: платформа быстрой разработки среднего и внутреннего уровня, ориентированная на многопользовательские решения. Поддержка независимой базы данных, общей архитектуры данных и режима без арендатора ✨
  • [ Блог Shiyi ]: система блогов vue + Springboot, которая разделяет переднюю и заднюю части.

Другие кейсы см. здесь: Sa-Token — лучшие интеграции с открытым исходным кодом

🌍 Другие языковые версии

Члены сообщества Sa-Token предоставили многоязычные версии реализации:

Мы искренне приглашаем разработчиков, более владеющих вышеуказанными языками, вместе создавать соответствующие версии. 🤝

🔗 Дружественные ссылки

  • [ OkHttps ]: облегченная платформа HTTP-связи, чрезвычайно элегантный API, поддержка протоколов WebSocket и Stomp.
  • [ Forest ]: декларативная и программная двойная обработка, благодаря которой отправка HTTP-запросов становится проще.
  • [ Bean Searcher ]: REST-версия GraphQL — границы определения сущностей, запросы на основе параметров и одна строка кода для реализации сложных запросов!
  • [ Jpom ]: простое и легкое программное обеспечение для онлайн-сборки с низким уровнем вмешательства, автоматического развертывания, ежедневной эксплуатации и обслуживания, а также программного обеспечения для мониторинга проекта.
  • [ TLog ]: облегченный артефакт отслеживания тегов распределенного журнала.
  • [ hippo4j ]: мощная платформа динамического пула потоков с функциями мониторинга и оповещения.
  • [ hertzbeat ]: простая в использовании и дружественная система мониторинга и сигнализации в реальном времени с открытым исходным кодом, не требующая агента, высокопроизводительный кластер и мощные возможности настраиваемого мониторинга.
  • [ Solon ]: более современная среда разработки приложений: более быстрая, компактная и бесплатная.
  • [ Chat2DB ]: инструмент управления базами данных и бизнес-аналитики на основе искусственного интеллекта, который поддерживает управление 22 базами данных, такими как Mysql, pg, Oracle и Redis.

📦 Хостинг кода

📚 Сборник примеров

Мы отдельно подготовили соответствующие примеры интеграции почти для всех технических моментов платформы. Этот сжатый пакет содержит в общей сложности более 60 демо: охватывающих все аспекты аутентификации и аутентификации, такие как аутентификация входа в систему Sa-Token, аутентификация органа, единый вход в систему, унифицированная аутентификация OAuth2, аутентификация микросервисов, аутентификация по ключу API, интеграция JWT, проверка подписи параметров межсистемного вызова и т. д.

Адрес загрузки: https://sa-token.com/doc.html#/more/download-demos

💬 Группа связи

Группа связи QQ: 1098917026 Нажмите, чтобы присоединиться

Группа связи WeChat:

PS: Отсканируйте QR-код, чтобы добавить WeChat (примечание: sa-token), чтобы пригласить вас присоединиться к групповому чату.

Преимущества присоединения к групповому чату:

  • Получайте уведомления об обновлениях платформы как можно скорее.
  • Получайте уведомления об ошибках фреймворка как можно скорее.
  • Получайте уведомления о новых случаях с открытым исходным кодом как можно скорее.
  • Общайтесь (хуа шуу) друг с другом (мо ю) со многими большими парнями 🖐️🐟️.

File: README_zh_TW.md

简体中文 | 繁體中文 | English | 日本語 | 한국어 | Русский

Sa-Token v1.46.0

✨ 開源、免費、一站式 java 權限認證框架,讓鑑權變得簡單、優雅!


📝 前言:

回望 2020 年初,我為 Sa-Token 提交第一行代碼之際,彼時市面上 Java 缺少的不僅是一個簡潔好用的鑑權框架,更是一整套清晰、自洽的權限架構設計思想。

因此,這幾年間我將大量時間傾注在 Sa-Token 的文檔編寫,幾乎每一章節、每一句話、每一個字都經過反覆修改、精細打磨,以求做到最清晰、幹練、易懂的表述。用心閱讀文檔,你學習到的將不止是 Sa-Token 框架本身,更是絕大多數場景下權限認證設計的最佳實踐。

🛠️ Sa-Token 介紹

Sa-Token 是一個輕量級 Java 權限認證框架,目前擁有五大核心模塊:登錄認證、權限認證、單點登錄、OAuth2.0、微服務鑑權。

你還在手搓以下功能?Stop,讓 Sa-Token 來!

要在 SpringBoot 項目中使用 Sa-Token,你只需要在 pom.xml 中引入依賴:

xml
<!-- Sa-Token 權限認證, 在線文檔:https://sa-token.com -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-spring-boot-starter</artifactId>
	<version>1.46.0</version>
</dependency>

除了支持 SpringBoot2、Sa-Token 還為 SpringBoot3/4、Solon、JFinal 等常見 Web 框架提供集成包,做到真正的開箱即用。

簡單示例展示:(點擊展開 / 摺疊)

Sa-Token 旨在以簡單、優雅的方式完成系統的權限認證部分,以登錄認證為例,你只需要:

java
// 會話登錄,參數填登錄人的賬號id 
StpUtil.login(10001);

無需實現任何接口,無需創建任何配置文件,只需要這一句靜態代碼的調用,便可以完成會話登錄認證。

如果一個接口需要登錄後才能訪問,我們只需調用以下代碼:

java
// 校驗當前客戶端是否已經登錄,如果未登錄則拋出 `NotLoginException` 異常
StpUtil.checkLogin();

在 Sa-Token 中,大多數功能都可以一行代碼解決:

踢人下線:

java
// 將賬號id為 10077 的會話踢下線 
StpUtil.kickout(10077);

權限認證:

java
// 註解鑑權:只有具備 `user:add` 權限的會話才可以進入方法
@SaCheckPermission("user:add")
public String insert(SysUser user) {
    // ... 
    return "用戶增加";
}

路由攔截鑑權:

java
// 根據路由劃分模塊,不同模塊不同鑑權 
registry.addInterceptor(new SaInterceptor(handler -> {
	SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
	SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
	SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
	SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
	SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
	// 更多模塊... 
})).addPathPatterns("/**");

如果您曾經使用過 Shiro、SpringSecurity,在切換到 Sa-Token 後,您將體會到質的飛躍。

核心模塊一覽:(點擊展開 / 摺疊)

  • 登錄認證 —— 單端登錄、多端登錄、同端互斥登錄、七天內免登錄。
  • 權限認證 —— 權限認證、角色認證、會話二級認證。
  • 踢人下線 —— 根據賬號id踢人下線、根據Token值踢人下線。
  • 註解式鑑權 —— 優雅的將鑑權與業務代碼分離。
  • 路由攔截式鑑權 —— 根據路由攔截鑑權,可適配 restful 模式。
  • Session會話 —— 全端共享Session,單端獨享Session,自定義Session,方便的存取值。
  • 持久層擴展 —— 可集成 Redis,重啟數據不丟失。
  • 前後臺分離 —— APP、小程序等不支持 Cookie 的終端也可以輕鬆鑑權。
  • Token風格定製 —— 內置六種 Token 風格,還可:自定義 Token 生成策略。
  • 記住我模式 —— 適配 [記住我] 模式,重啟瀏覽器免驗證。
  • 二級認證 —— 在已登錄的基礎上再次認證,保證安全性。
  • 模擬他人賬號 —— 實時操作任意用戶狀態數據。
  • 臨時身份切換 —— 將會話身份臨時切換為其它賬號。
  • 同端互斥登錄 —— 像QQ一樣手機電腦同時在線,但是兩個手機上互斥登錄。
  • 賬號封禁 —— 登錄封禁、按照業務分類封禁、按照處罰階梯封禁。
  • 密碼加密 —— 提供基礎加密算法,可快速 MD5、SHA1、SHA256、AES 加密。
  • 會話查詢 —— 提供方便靈活的會話查詢接口。
  • Http Basic認證 —— 一行代碼接入 Http Basic、Digest 認證。
  • 全局偵聽器 —— 在用戶登陸、註銷、被踢下線等關鍵性操作時進行一些AOP操作。
  • 全局過濾器 —— 方便的處理跨域,全局設置安全響應頭等操作。
  • 多賬號體系認證 —— 一個系統多套賬號分開鑑權(比如商城的 User 表和 Admin 表)
  • 單點登錄 —— 內置三種單點登錄模式:同域、跨域、同Redis、跨Redis、前後端分離等架構都可以搞定。
  • 單點註銷 —— 任意子系統內發起註銷,即可全端下線。
  • OAuth2.0認證 —— 輕鬆搭建 OAuth2.0 服務,支持openid模式 。
  • 分佈式會話 —— 提供共享數據中心分佈式會話方案。
  • 微服務網關鑑權 —— 適配Gateway、ShenYu、Zuul等常見網關的路由攔截認證。
  • RPC調用鑑權 —— 網關轉發鑑權,RPC調用鑑權,讓服務調用不再裸奔
  • 臨時Token認證 —— 解決短時間的 Token 授權問題。
  • 獨立Redis —— 將權限緩存與業務緩存分離。
  • Quick快速登錄認證 —— 為項目零代碼注入一個登錄頁面。
  • 標籤方言 —— 提供 Thymeleaf 標籤方言集成包,提供 beetl 集成示例。
  • jwt集成 —— 提供三種模式的 jwt 集成方案,提供 token 擴展參數能力。
  • RPC調用狀態傳遞 —— 提供 dubbo、grpc 等集成包,在RPC調用時登錄狀態不丟失。
  • 參數簽名 —— 提供跨系統API調用簽名校驗模塊,防參數篡改,防請求重放。
  • 自動續簽 —— 提供兩種Token過期策略,靈活搭配使用,還可自動續簽。
  • 開箱即用 —— 提供SpringMVC、WebFlux、Solon 等常見框架集成包,開箱即用。
  • 最新技術棧 —— 適配最新技術棧:支持 SpringBoot 3.x,jdk 17。

🍃 SSO 單點登錄

Sa-Token SSO 分為三種模式,可解決:同域、跨域、共享Redis、跨Redis、前後端一體、前後端分離、純 js、vue2、vue3、java 項目、非 java 項目 等架構下的 SSO 認證需求:

| 系統架構 | 採用模式 | 簡介 | 文檔鏈接 |
| :-------- | :-------- |:----------------| :-------- |
| 前端同域 + 後端同 Redis | 模式一 | 共享Cookie同步會話 | 文檔、示例 |
| 前端不同域 + 後端同 Redis | 模式二 | URL重定向傳播會話 | 文檔、示例 |
| 前端不同域 + 後端 不同Redis | 模式三 | HTTP請求獲取會話 | 文檔、示例 |

  1. 前端同域:就是指多個系統可以部署在同一個主域名之下,比如:c1.domain.com、c2.domain.com、c3.domain.com
  2. 後端同 Redis:就是指多個系統可以連接同一個 Redis,共享會話數據。
  3. 如果無法做到前端同域、後端同 Redis,可以走託底的模式三:Http請求校驗 ticket 獲取會話。
  4. 提供:NoSdk 模式示例 + sso-server 接口文檔,非 Sa-Token 項目、非 java 項目也可以對接。
  5. 提供:多重安全校驗:域名校驗、ticket校驗、參數簽名校驗,有效防 ticket 劫持,防請求重放等攻擊。
  6. 提供:大量實戰痛點教學:sso-server 前後端分離設計、sso-client 前後端分離設計、用戶數據同步/遷移方案設計。
  7. 提供:直接可運行的 demo 示例,助你快速熟悉 SSO 大致登錄流程。
  8. 提供:深度細節優化,參數防丟:筆者曾試驗多個SSO框架,均有參數丟失情況,比如登錄前是:http://a.com?id=1&name=2,登錄成功後就變成了:http://a.com?id=1,Sa-Token-SSO 內有專門算法保證了參數不丟失,登錄成功後精準原路返回。

🍂 OAuth2 授權認證

Sa-Token OAuth2 模塊分為四種授權模式,解決不同場景下的授權需求

| 授權模式 | 簡介 |
| :-------- | :-------- |
| 授權碼式 | OAuth2 標準授權步驟,server 端下放 code,client 端獲取 code 碼兌換 access_token |
| 隱藏式 | 備用選擇,server 端使用 URL 重定向方式直接將 access_token 下放到 client 端頁面 |
| 密碼式 | client 直接拿著用戶的賬號密碼換取授權 access_token |
| 客戶端憑證式 | server 端針對 client 級別的 client_token,代表應用自身的資源授權 |

詳細參考文檔:https://sa-token.com/doc.html#/oauth2/readme

📖❓ 疑問解答

1、Sa-Token 功能全不全?

七年磨一劍:五大核心模塊(登錄、鑑權、SSO、OAuth2、微服務) + 眾多實用插件 (短 token、jwt 集成、API 參數簽名、API Key 秘鑰授權...) 我們提供的不只是權限認證,我們提供的是一站式解決方案。

2、Sa-Token 好不好學?

中文文檔 + 中文代碼註釋 + 中文交流社區 + 大量實戰案例博客 + 多個視頻教程 + 大量優秀開源項目集成案例。

3、Sa-Token 用的人多不多?

截止統計日 (2026-1-25) 起,Sa-Token 在:

  • Gitee 關注量達到 48627 Star,位列平臺所有推薦項目排行榜第一名。
  • GitHub 關注量達到 18523 Star,是主要競爭框架 Spring Security 的 1.97 倍,Apache Shiro 的 4.19 倍。
  • 25+ 微信粉絲群 (500人),8+ QQ粉絲群 (1000人 or 2000人) ,在線文檔訪問量月PV 20萬+。

這是眾多開發者用腳投票的數據,相信這些數據比任何言語都能證明 Sa-Token 的熱度。

4、Sa-Token 有哪些權威認證?

曾獲榮譽包括但不限於:Gitee GVP 最有價值開源項目、GitCode G-Star 優質開源項目、OSCHINA 2021 人氣指數 TOP 30 開源項目、OSCHINA 2022 年度最火熱中國開源項目社區之一、開放原子基金會2023快速成長開源項目、 Dromara 組織頂尖項目(之一)、可信開源社區共同體預備成員、所在開源社區 “Dromara” 榮獲《2024中國互聯網發展創新與投資大賽(開源)》二等獎。 Gitee High Star 計劃項目(5000+star)。Gitee 2025年度開源項目 Web應用開發 Top 2。

5、Sa-Token 收費嗎?

Sa-Token 採用 Apache-2.0 開源協議,承諾框架本身與在線文檔永久免費開放。當然如果您有心贊助 Sa-Token,我們也不迴避:贊助鏈接。
我們將定期同步贊助者名單到在線文檔展示。(您需要注意的一點是:該贊助僅為友情贊助,不提供任何商業交換)

6、Sa-Token 是封裝的 SpringSecurity 嗎?是套殼 ApacheShiro 嗎?

不是。Sa-Token 不是一個後臺模板,也不是針對 xx 框架的二次封裝套殼,而是從 0 開始的純血自研框架,核心包零依賴,完全自主可控的架構內核 + 眾多主流框架的集成適配。

證書 ⭐ 獎盃 🏆 榮譽展示

🚀 優秀開源集成案例

  • [ Snowy ]:國內首個國密前後分離快速開發平臺,採用 Vue3 + Vite + SpringBoot + Mp + HuTool + SaToken。
  • [ RuoYi-Vue-Plus ]:重寫RuoYi-Vue所有功能 集成 Sa-Token、Mybatis-Plus、Xxl-Job、knife4j、OSS 定期同步。
  • [ Smart-Admin ]:SmartAdmin 國內首個以「高質量代碼」為核心,「簡潔、高效、安全」中後臺快速開發平臺。
  • [ 橙單 ]: 橙單中臺化低代碼生成器。可完整支持多應用、多租戶、多渠道、工作流、框架技術棧自由組合等。
  • [ 燈燈 ]: 專注於多租戶解決方案的中後臺快速開發平臺。支持獨立數據庫、共享數據架構 和 非租戶模式 ✨
  • [ 拾壹博客 ]:一款 vue + springboot 前後端分離的博客系統。

更多案例請參考:Sa-Token 優秀開源集成案例

🌍 其它語言版本

Sa-Token 社區成員貢獻了多語言實現版本:

我們誠邀對上述語言較為熟練的開發者,一起建設相關版本。🤝

🔗 友情鏈接

  • [ OkHttps ]:輕量級 http 通信框架,API無比優雅,支持 WebSocket、Stomp 協議
  • [ Forest ]:聲明式與編程式雙修,讓天下沒有難以發送的 HTTP 請求
  • [ Bean Searcher ]:REST 版的 GraphQL — 實體定義邊界,參數驅動查詢,一行代碼實現複雜查詢!
  • [ Jpom ]:簡而輕的低侵入式在線構建、自動部署、日常運維、項目監控軟件。
  • [ TLog ]:一個輕量級的分佈式日誌標記追蹤神器。
  • [ hippo4j ]:強大的動態線程池框架,附帶監控報警功能。
  • [ hertzbeat ]:易用友好的開源實時監控告警系統,無需Agent,高性能集群,強大自定義監控能力。
  • [ Solon ]:一個更現代感的應用開發框架:更快、更小、更自由。
  • [ Chat2DB ]:一個AI驅動的數據庫管理和BI工具,支持Mysql、pg、Oracle、Redis等22種數據庫的管理。

📦 代碼託管

📚 示例大全

我們為框架幾乎所有技術點均單獨製作了對應的集成示例,此壓縮包共計 60+ Demo:涵蓋 Sa-Token 登錄認證、權限認證、SSO 單點登錄、OAuth2 統一認證、微服務鑑權、API Key 認證、JWT集成、跨系統調用參數簽名校驗 等鑑權認證的方方面面。

下載地址:https://sa-token.com/doc.html#/more/download-demos

💬 交流群

QQ交流群:1098917026 點擊加入

微信交流群:

PS:掃碼添加微信 (備註:sa-token),邀您加入群聊。

加入群聊的好處:

  • 第一時間收到框架更新通知。
  • 第一時間收到框架 bug 通知。
  • 第一時間收到新增開源案例通知。
  • 和眾多大佬一起互相 (huá shuǐ) 交流 (mō yú) 🖐️🐟️。

File: SECURITY.md

简体中文 | English

Security Policy

Thanks for helping keep Sa-Token and the wider dromara ecosystem secure.

Reporting a vulnerability

Please do not open a public issue, discussion, or pull request that describes a suspected vulnerability — that gives attackers a head-start before a fix lands.

Use one of these private channels instead:

  1. GitHub's Private Vulnerability Reporting (preferred).
    Open a private security advisory at https://github.com/dromara/Sa-Token/security/advisories/new.
    GitHub automatically routes it to the maintainers, keeps the discussion private until you and the maintainers agree to publish, and (optionally) assigns a CVE on publish.

  2. Email.
    If GitHub PVR is unavailable to you for any reason, email the project lead directly: [email protected].

  3. WeChat.
    If you already use WeChat, you can also add the project lead at kite2020_ for more timely follow-up. Please set the friend-request note to Sa-Token 漏洞提交.

What to include

A good report contains:

  • A clear description of the vulnerability and its impact.
  • Steps to reproduce against a specific commit SHA or release tag.
  • Affected code locations (file:line) where useful.
  • A suggested fix or mitigation if you have one.
  • Whether you'd like credit in the published advisory and, if so, under what name.

Scope and supported versions

Sa-Token is a Java authentication / authorization framework (login, permissions, session, SSO, OAuth2, JWT). The maintainers support security fixes on the latest minor release. Older releases will not generally receive backports.

In-scope vulnerability classes include:

  • Authentication / authorization bypass
  • Session fixation, token forgery, JWT signature / claims handling flaws
  • SSO redirect / open-redirect / ticket exfiltration
  • OAuth2 client / authorization-code flow flaws
  • Cryptographic misuse in stored secrets / signing keys
  • Insecure deserialization in token / cookie codecs

Out-of-scope:

  • Findings against intentionally trusted-admin features (config-driven behavior that the framework explicitly delegates to the integrator).
  • Issues that require an attacker to already have full server / database access.
  • Best-practice complaints without a concrete impact (e.g. "this header should be set", "TLS version should be raised").

Process

After you submit:

  1. A maintainer will acknowledge receipt within roughly 1 week.
  2. We'll triage the report: confirm severity, scope, and reproducibility.
  3. We'll work with you on a fix and a coordinated disclosure timeline (typically up to 90 days, longer if the fix is structural).
  4. On publication, we credit you in the advisory unless you ask not to be credited.

Hall of fame

Reporters who have helped harden Sa-Token via responsible disclosure will be listed here once the corresponding advisory is published.


This policy is suggested via GitHub's "Suggest a security policy" workflow. Maintainers can edit any section freely; the most important thing is that a private reporting channel exists so researchers can submit findings responsibly.


File: SECURITY_zh-CN.md

[Binary file]


File: sa-token-demo/sa-token-demo-alone-redis/src/main/resources/application.yml

端口

server:
port: 8081

Sa-Token配置

sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# 配置Sa-Token单独使用的Redis连接
alone-redis:
# Redis模式(默认单体)
# pattern: single
# Redis数据库索引(默认为0)
database: 2
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# 连接超时时间(毫秒)
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

spring:
# 配置业务使用的Redis连接
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-alone-redis-cluster/src/main/resources/application.yml

端口

server:
port: 8081

Sa-Token配置

sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# 配置Sa-Token单独使用的Redis连接
alone-redis:
# 普通集群
pattern: cluster
# Redis服务器连接用户名(默认为空)
username:
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10s
cluster:
# Redis集群服务器节点地址
nodes: 127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002
# 最大重定向次数
maxRedirects: 2
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

spring:
# 配置业务使用的Redis连接
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-alone-redis-cluster/src/main/resources/application_sentinel.yml

[Binary file]


File: sa-token-demo/sa-token-demo-alone-redis-sb4/src/main/resources/application.yml

端口

server:
port: 8083

Sa-Token配置

sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# 配置Sa-Token单独使用的Redis连接
alone-redis:
# Redis数据库索引(默认为0)
database: 2
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# 连接超时时间(毫秒)
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

配置业务使用的Redis连接

spring:
data:
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-alone-redisson/src/main/resources/application.yml

端口

server:
port: 8084

Sa-Token配置

sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# Sa-Token 单独使用的 Redisson 连接(Redisson 原生 yaml)
alone-redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
database: 2

业务使用的 Redisson 连接

spring:
redis:
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
database: 0


File: sa-token-demo/sa-token-demo-alone-redisson-cluster/src/main/resources/application.yml

端口

server:
port: 8085

Sa-Token配置

sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# Sa-Token 单独使用的 Redisson 集群连接(Redisson 原生 yaml)
alone-redisson:
config: |
clusterServersConfig:
nodeAddresses:
- "redis://127.0.0.1:3000"
- "redis://127.0.0.1:3001"
- "redis://127.0.0.1:3002"

业务仍使用单机 Redis

spring:
redis:
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
database: 0


File: sa-token-demo/sa-token-demo-alone-redisson-sb3/src/main/resources/application.yml

端口

server:
port: 8087

Sa-Token配置

sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# Sa-Token 单独使用的 Redisson 连接(Redisson 原生 yaml)
alone-redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
database: 2

业务使用的 Redisson 连接(Boot 3 前缀为 spring.data.redis)

spring:
data:
redis:
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
database: 0


File: sa-token-demo/sa-token-demo-alone-redisson-sb4/src/main/resources/application.yml

端口

server:
port: 8086

Sa-Token配置

sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# Sa-Token 单独使用的 Redisson 连接(Redisson 原生 yaml)
alone-redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
database: 2

业务使用的 Redisson 连接

spring:
redis:
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
database: 0


File: sa-token-demo/sa-token-demo-apikey/src/main/resources/application.yml

端口

server:
port: 8081

############## Sa-Token 配置 (文档: https://sa-token.com) ##############
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# 开启日志信息
is-log: true
# API Key 相关配置
api-key:
# API Key 前缀
prefix: AK-
# API Key 有效期,-1=永久有效,默认30天 (修改此配置项不会影响到已创建的 API Key)
timeout: 2592000
# 框架是否记录索引信息
is-record-index: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-async/src/main/resources/application.yml

端口

server:
port: 8081

############## Sa-Token 配置 (文档: https://sa-token.com) ##############
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-beetl/src/main/resources/application.yml

端口

server:
port: 8081


File: sa-token-demo/sa-token-demo-bom-import/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true
# jwt秘钥
jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-bug-reproduce/README.md

sa-token-demo-bug-reproduce

临时复现 Issue / Bug 的沙盒(用例随 Issue 更换,平时保持空壳)。

怎么跑

bash
mvn -DskipTests spring-boot:run

健康检查:http://localhost:8092/ok


File: sa-token-demo/sa-token-demo-bug-reproduce/src/main/resources/application.yml

server:
port: 8092

sa-token:
token-name: satoken


File: sa-token-demo/sa-token-demo-caffeine/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true


File: sa-token-demo/sa-token-demo-case/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true
# jwt 秘钥
jwt-secret-key: JfdDSgfCmPsDfmsAaQwnXk

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-device-lock/src/main/resources/application.yml

端口

server:
port: 8081

############## Sa-Token 配置 (文档: https://sa-token.com) ##############
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-consumer/src/main/resources/application.yml

server:
# 端口号
port: 8081

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间

dubbo:
application:
# 服务名称
name: dubbo-consumer-demo
registry:
# 注册中心地址
address: nacos://127.0.0.1:8001


File: sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo-provider/src/main/resources/application.yml

server:
# 端口号
port: 8080

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s

Dubbo

dubbo:
# 服务名
application:
name: dubbo-provider-demo
# 扫描包
scan:
base-packages: com.pj
# 注册中心地址
registry:
address: nacos://127.0.0.1:8001
# 协议
protocol:
name: dubbo
port: 12345


File: sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-consumer/src/main/resources/application.yml

server:
# 端口号
port: 8081

spring:
data:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间

dubbo:
application:
# 服务名称
name: dubbo-consumer-demo
registry:
# 注册中心地址
address: nacos://127.0.0.1:8001


File: sa-token-demo/sa-token-demo-dubbo/sa-token-demo-dubbo3-provider/src/main/resources/application.yml

server:
# 端口号
port: 8080

spring:
data:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s

Dubbo

dubbo:
# 服务名
application:
name: dubbo-provider-demo
# 扫描包
scan:
base-packages: com.pj
# 注册中心地址
registry:
address: nacos://127.0.0.1:8001
# 协议
protocol:
name: dubbo
port: 12345


File: sa-token-demo/sa-token-demo-first-run/src/main/resources/application.yml

端口

server:
port: 8099

sa-token 配置

sa-token:
token-name: satoken
timeout: 2592000
active-timeout: -1
is-concurrent: true
is-share: false
token-style: uuid
is-log: true


File: sa-token-demo/sa-token-demo-freemarker/src/main/resources/application.yml

端口

server:
port: 8081

spring:
# Freemarker 相关配置
freemarker:
# 指定模板文件的目录
template-loader-path: classpath:/templates
# 指定Freemarker模板文件的后缀名
suffix: .ftl
# 关闭模板缓存,方便测试
cache: false
# 检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
settings:
template_update_delay: 0


File: sa-token-demo/sa-token-demo-grpc/client/src/main/resources/application.yml

server:
port: 2222
spring:
application:
name: test-client
redis:
host: localhost
cloud:
nacos:
server-addr: localhost:8848
sa-token:
is-read-cookie: false
grpc:
server:
port: 2223
client:
test-server:
negotiation-type: PLAINTEXT


File: sa-token-demo/sa-token-demo-grpc/server/src/main/resources/application.yml

server:
port: 5555
spring:
application:
name: test-server
redis:
host: localhost
cloud:
nacos:
server-addr: localhost:8848
sa-token:
is-read-cookie: false

grpc:
server:
port: 5556


File: sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-json-typing-security/README.md

sa-token-demo-json-typing-security

长期保留的 Jackson DefaultTyping 反序列化白名单回归 Demo。
与 sa-token-demo-bug-reproduce(临时 Issue 沙盒)分离,避免新漏洞复现时覆盖本用例。

验证内容

模拟 Redis 中被写入带 @class 的恶意 JSON,经 SaTokenDao.getObject() 反序列化时:

  • 期望:SaJsonStrategy 白名单拦截,抛出 SaJsonConvertException(消息含 无法反序列化的类型:…)
  • 失败:未授权类型被实例化(PoC gadget 会尝试弹出计算器)

关联实现:SaJsonStrategy、SaJsonTemplateForJackson

前提

  • 本地 Redis 已启动(默认 127.0.0.1:6379,database 1)
  • 开发本仓库时,请先根目录 mvn install,并将 pom.xml 中 sa-token.version 改为当前开发版本

怎么跑

IDEA 运行 JsonTypingSecurityApplication,或:

bash
mvn -DskipTests spring-boot:run

启动后会自动执行一次 PoC。也可 HTTP 重复触发:

bash
curl http://127.0.0.1:8093/ok
curl http://127.0.0.1:8093/poc/jackson-default-typing

警告: 仅用于本地验证,勿在生产或公网环境使用。


File: sa-token-demo/sa-token-demo-json-typing-security/src/main/resources/application.yml

server:
port: 8093

sa-token:
token-name: satoken
is-log: true

spring:
redis:
database: 1
host: 127.0.0.1
port: 6379
password:
timeout: 10s
lettuce:
pool:
max-active: 200
max-wait: -1ms
max-idle: 10
min-idle: 0


File: sa-token-demo/sa-token-demo-json-typing-security-for-solon/README.md

sa-token-demo-json-typing-security-for-solon

长期保留的 Snack4 AutoType 反序列化白名单回归 Demo(Solon + Snack4 + Redisx)。

与 sa-token-demo-json-typing-security(Spring Boot + Jackson)对应,便于分别验证不同 JSON 插件的白名单行为。

验证内容

模拟 Redis 中被写入带 @type 的恶意 JSON,经 SaTokenDao.getObject() 反序列化时:

  • 期望:SaJsonStrategy 白名单拦截,抛出 SaJsonConvertException(消息含 无法反序列化的类型:…)
  • 失败:未授权类型被实例化(PoC gadget 会尝试弹出计算器)

关联实现:SaJsonStrategy、SaJsonTemplateForSnack4

前提

  • 本地 Redis 已启动(默认 127.0.0.1:6379,database 1)
  • 开发本仓库时,请先根目录 mvn install,并将 pom.xml 中 sa-token.version 改为当前开发版本

怎么跑

IDEA 运行 JsonTypingSecurityForSolonApp,或:

bash
mvn -DskipTests compile exec:java -Dexec.mainClass="com.pj.JsonTypingSecurityForSolonApp"

启动后会自动执行一次 PoC。也可 HTTP 重复触发:

bash
curl http://127.0.0.1:8094/ok
curl http://127.0.0.1:8094/poc/snack4-autotype

警告: 仅用于本地验证,勿在生产或公网环境使用。


File: sa-token-demo/sa-token-demo-json-typing-security-for-solon/src/main/resources/app.yml

server:
port: 8094

sa-token:
token-name: satoken
is-log: true

sa-token-dao:
redis:
server: "127.0.0.1:6379"
db: 1
maxTotal: 200


File: sa-token-demo/sa-token-demo-jwt/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true
# jwt秘钥
jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-loveqq-boot/src/main/resources/application.yml

端口

k:
server:
port: 8081


File: sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/application.yml

server:
port: 8002

sa-token配置

sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken-client


File: sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml

[Binary file]


File: sa-token-demo/sa-token-demo-quick-login/src/main/resources/application.yml

端口

server:
port: 8081

Sa-Token-Quick-Login 配置

sa:
# 登录账号
name: sa
# 登录密码
pwd: 123456
# 是否自动随机生成账号密码 (此项为true时, name与pwd失效)
auto: false
# 是否开启全局认证(关闭后将不再强行拦截)
auth: true
# 登录页标题
title: Sa-Token 登录
# 是否显示底部版权信息
copr: true
# 指定拦截路径
# include: /**
# 指定排除路径
# exclude: /1.jpg
# 将本地磁盘的某个路径作为静态资源开放
# dir: file:E:\static

静态文件路径映射

spring:
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/, ${sa.dir:}


File: sa-token-demo/sa-token-demo-quick-login-sb3/src/main/resources/application.yml

端口

server:
port: 8081

Sa-Token-Quick-Login 配置

sa:
# 登录账号
name: sa
# 登录密码
pwd: 123456
# 是否自动随机生成账号密码 (此项为true时, name与pwd失效)
auto: false
# 是否开启全局认证(关闭后将不再强行拦截)
auth: true
# 登录页标题
title: Sa-Token 登录
# 是否显示底部版权信息
copr: true
# 指定拦截路径
# include: /**
# 指定排除路径
# exclude: /1.jpg
# 将本地磁盘的某个路径作为静态资源开放
# dir: file:E:\static

静态文件路径映射

spring:
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/, ${sa.dir:}


File: sa-token-demo/sa-token-demo-remember-me/page_project/README.md

Vue 3 + Vite

Node下载地址

安装最新版本Node环境, 然后执行如下命令开启开发服务:

text
npm install
npm run dev

cookie/sessionstorage/localstorage三者的区别


File: sa-token-demo/sa-token-demo-remember-me/page_project/package.json

{
"name": "page_project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.3.4",
"element-plus": "^2.2.33",
"qs": "^6.11.0",
"vue": "^3.2.45",
"vue-axios": "^3.5.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"vite": "^4.1.0"
}
}


File: sa-token-demo/sa-token-demo-remember-me/sa-token-demo-remember-me-server/src/main/resources/application.yml

端口

server:
port: 80

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-solon/src/main/resources/app.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

sa-token-dao: #名字可以随意取
redis:
server: "localhost:6379"

password: 123456

text
db: 1
    maxTotal: 200

File: sa-token-demo/sa-token-demo-solon-redisson/src/main/resources/app.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

sa-token-dao:
config: |
singleServerConfig:
password: "123456"
address: "redis://localhost:6379"
database: 0


File: sa-token-demo/sa-token-demo-springboot/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-springboot-low-version/src/main/resources/application.yml

端口

server:
port: 8081

############## Sa-Token 配置 (文档: https://sa-token.com) ##############
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-springboot-redisson/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 1
# Redis服务器地址
host: localhost
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码 为空需注释掉
# password:
# 连接超时时间
timeout: 10s

#redisson:

# 线程池数量

threads: 8

# Netty线程池数量

nettyThreads: 32

# 单节点配置

singleServerConfig:

# 客户端名称

clientName: test

# 最小空闲连接数

connectionMinimumIdleSize: 8

# 连接池大小

connectionPoolSize: 32

# 连接空闲超时,单位:毫秒

idleConnectionTimeout: 10000

# 命令等待超时,单位:毫秒

timeout: 3000

# 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。

retryAttempts: 3

# 命令重试发送时间间隔,单位:毫秒

retryInterval: 1500

# 发布和订阅连接池大小

subscriptionConnectionPoolSize: 50


File: sa-token-demo/sa-token-demo-springboot3-redis/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
data:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-springboot4-redis/src/main/resources/application.yml

端口

server:
port: 8082

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
data:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-sse/src/main/resources/application.yml

端口

server:
port: 8081

############## Sa-Token 配置 (文档: https://sa-token.com) ##############
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-ssm/README.md

SSM 架构集成 Sa-Token 示例

说是SSM,其实没有M,仅仅是给使用 SpringMVC 非 SpringBoot 的项目提供一个简单的 Sa-Token 集成示例。

集成要点(v1.42.0+)

  1. 在 web.xml 注册 SaTokenContextFilter(本示例已配置,逻辑同官方 SaTokenContextFilterForServlet)
  2. 在 SaTokenBeanInjection 中手动注册 SaStrategy(路由匹配、SaRequest/SaResponse/SaStorage 创建策略)
  3. 不再注入 SaTokenContextForSpring,上下文由 Filter + ThreadLocal 方案自动管理

直接运行项目即可,里面注释挺全的,也不必做过多说明了
(其实就是我懒,光搭建起来这个架子就累瘫了,各种版本兼容问题报起错来大汗淋漓,推荐新项目能上 SpringBoot 就赶紧上吧,千万别在SSM上浪费生命)。

推荐 jdk8 + tomcat8。

常见问题

  • Filter 启动失败(One or more Filters failed to start):① javax.servlet-api 必须 provided,不能打进 WAR;② 不要开 async-supported(SpringBoot 会自动给 DispatcherServlet 开异步,纯 SSM 不会);③ web.xml 注册本工程内的 com.pj.satoken.SaTokenContextFilter,不要直接引用 starter 里的 Filter 类。
  • NoSuchMethodError: Expiration.keepTtl():spring-data-redis 版本过低(须 2.7.x,与 sa-token-redis-jackson 对齐),勿手动锁死 2.3.x。

File: sa-token-demo/sa-token-demo-ssm/src/main/resources/application.yml

端口

server:
port: 8081

############## Sa-Token 配置 (文档: https://sa-token.com) ##############
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-vue2/README.md

sa-token-demo-sso-client-vue2

Sa-Token SSO-Client 应用端(前后端分离版-Vue2)

在线文档:https://sa-token.com/

运行

先安装依赖

bat
npm install --registry=https://registry.npm.taobao.org

运行

bat
npm run serve

打包

bat
npm run build

File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-vue2/jsconfig.json

{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/": [
"src/
"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-vue2/package.json

{
"name": "hello-world",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^1.1.3",
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-router": "^3.6.5"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-vue3/README.md

sa-token-demo-sso-client-vue3

Sa-Token SSO-Client 应用端(前后端分离版-Vue3)

在线文档:https://sa-token.com/

运行

先安装依赖

bat
npm install --registry=https://registry.npm.taobao.org

运行

bat
npm run dev

打包

bat
npm run build

File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-vue3/package.json

{
"name": "sa-token-demo-sso-client-vue3",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.1.3",
"vue": "^3.2.41",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.2.0",
"vite": "^3.2.7"
}
}


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/resources/application.yml

端口

server:
port: 9000

Sa-Token 配置

sa-token:
# 打印操作日志
is-log: true

text
# SSO 模式一配置  (非模式一不需要配置)

domain: stp.com

text
# SSO-Server 配置
sso-server:
    # Ticket有效期 (单位: 秒),默认五分钟 
    ticket-timeout: 300
    # 主页路由:在 /sso/auth 登录页不指定 redirect 参数时,默认跳转的地址
    home-route: /home
    # 是否启用匿名 client (开启匿名 client 后,允许客户端接入时不提交 client 参数)
    allow-anon-client: true
    # 所有允许的授权回调地址 (匿名 client 使用)
    allow-url: "*"
    # API 接口调用秘钥 (全局默认 + 匿名 client 使用)
    secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
    # 应用列表:配置接入的应用信息
    clients:
        # 应用 sso-client1:采用模式一对接 (同域、同Redis)
        sso-client1:
            client: sso-client1
            allow-url: "*"
        # 应用 sso-client2:采用模式二对接 (跨域、同Redis)
        sso-client2:
            client: sso-client2
            allow-url: "*"
            secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
        # 应用 sso-client3:采用模式三对接 (跨域、跨Redis)
        sso-client3:
            # 应用名称
            client: sso-client3
            # 允许授权地址
            allow-url: "*"
            # 是否接收消息推送
            is-push: true
            # 消息推送地址
            push-url: http://sa-sso-client1.com:9003/sso/pushC
            # 接口调用秘钥 (如果不配置则使用全局默认秘钥)
            secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
        # 应用 sso-client3-nosdk:采用 NoSdk 模式对接 (不依赖 Sa-Token 客户端 SDK,手动实现协议)
        sso-client3-nosdk:
            # 应用名称
            client: sso-client3-nosdk
            # 允许授权地址
            allow-url: "*"
            # 是否接收消息推送
            is-push: true
            # 消息推送地址
            push-url: http://sa-sso-client1.com:9004/sso/pushC
            # 接口调用秘钥 (如果不配置则使用全局默认秘钥)
            secret-key: SSO-C3-NoSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
        # 应用 sso-client3-resdk:采用 ReSdk 模式对接
        sso-client3-resdk:
            # 应用名称
            client: sso-client3-resdk
            # 允许授权地址
            allow-url: "*"
            # 是否接收消息推送
            is-push: true
            # 消息推送地址
            push-url: http://sa-sso-client1.com:9005/sso/pushC
            # 接口调用秘钥 (如果不配置则使用全局默认秘钥)
            secret-key: SSO-C3-ReSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

spring:
# Redis配置 (SSO模式一和模式二使用 Redis 来同步会话)
redis:
# Redis数据库索引(默认为0)
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

forest:
# 关闭 forest 请求日志打印
log-enabled: false


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso1-client/src/main/resources/application.yml

[Binary file]


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/resources/application.yml

端口

server:
port: 9002

sa-token配置

sa-token:
# 打印操作日志
is-log: true

text
# SSO-相关配置
sso-client:
    # 应用标识
    client: sso-client2
    # SSO-Server 端主机地址
    server-url: http://sa-sso-server.com:9000
    # 在 sso-server 端前后端分离时需要单独配置 auth-url 参数(上面的不要注释,auth-url 配置项和 server-url 要同时存在)
    # auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
    # API 接口调用秘钥 (单点注销时会用到)
    secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

# 配置 Sa-Token 单独使用的Redis连接(此处需要和 SSO-Server 端连接同一个 Redis)
# 注:使用 alone-redis 需要在 pom.xml 引入 sa-token-alone-redis 依赖
alone-redis:
    # Redis数据库索引
    database: 1
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    password: 
    # 连接超时时间
    timeout: 10s
    lettuce:
        pool:
            # 连接池最大连接数
            max-active: 200
            # 连接池最大阻塞等待时间(使用负值表示没有限制)
            max-wait: -1ms
            # 连接池中的最大空闲连接
            max-idle: 10
            # 连接池中的最小空闲连接
            min-idle: 0

forest:
# 关闭 forest 请求日志打印
log-enabled: false


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml

端口

server:
port: 9003

sa-token配置

sa-token:
# 打印操作日志
is-log: true

text
# sso-client 相关配置
sso-client:
    # 应用标识
    client: sso-client3
    # sso-server 端主机地址
    server-url: http://sa-sso-server.com:9000
    # 在 sso-server 端前后端分离时需要单独配置 auth-url 参数(上面的不要注释,auth-url 配置项和 server-url 要同时存在)
    # auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
    # 使用 Http 请求校验 ticket (模式三)
    is-http: true
    # API 接口调用秘钥
    secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

spring:
# 配置 Redis 连接 (此处与 SSO-Server 端连接不同的 Redis)
redis:
# Redis数据库索引
database: 3
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

forest:
# 关闭 forest 请求日志打印
log-enabled: false


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client--sb4/src/main/resources/application.yml

[Binary file]


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-anon/src/main/resources/application.yml

端口

server:
port: 9006

sa-token配置

sa-token:
# 配置一个不同的 token-name,以避免在和模式三 demo 一起测试时发生数据覆盖
token-name: satoken-client-anon
# sso-client 相关配置
sso-client:
# client 标识 匿名应用就是指不配置 client 标识的应用
# client: sso-client3
# sso-server 端主机地址
server-url: http://sa-sso-server.com:9000
# 使用 Http 请求校验ticket (模式三)
is-http: true
# 是否在登录时注册单点登录回调接口 (匿名应用想要参与单点注销必须打开这个)
reg-logout-call: true
# API 接口调用秘钥
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

spring:
# 配置 Redis 连接 (此处与SSO-Server端连接不同的Redis)
redis:
# Redis数据库索引
database: 6
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

forest:
# 关闭 forest 请求日志打印
log-enabled: false


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/resources/application.yml

端口

server:
port: 9004

forest:
# 打开/关闭Forest请求日志(默认为 true)
log-request: true

spring:
# Redis连接
redis:
# Redis数据库索引
database: 2
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-resdk/src/main/resources/application.yml

端口

server:
port: 9005

sa-token 配置

sa-token:
# 是否打印操作日志
is-log: true
# sso-client 相关配置
sso-client:
# client 标识
client: sso-client3-resdk
# sso-server 端主机地址
server-url: http://sa-sso-server.com:9000
# 使用 Http 请求校验ticket (模式三)
is-http: true
# API 接口调用秘钥
secret-key: SSO-C3-ReSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor


File: sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso-server-solon/src/main/resources/app.yml

端口

server:
port: 9000

Sa-Token 配置

sa-token:
# 打印操作日志
is-log: true

text
# SSO 模式一配置  (非模式一不需要配置)

domain: stp.com

text
# SSO-Server 配置
sso-server:
    # Ticket有效期 (单位: 秒),默认五分钟
    ticket-timeout: 300
    # 主页路由:在 /sso/auth 登录页不指定 redirect 参数时,默认跳转的地址
    home-route: /home
    # 是否启用匿名 client (开启匿名 client 后,允许客户端接入时不提交 client 参数)
    allow-anon-client: true
    # 所有允许的授权回调地址 (匿名 client 使用)
    allow-url: "*"
    # API 接口调用秘钥 (全局默认 + 匿名 client 使用)
    secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
    # 应用列表:配置接入的应用信息
    clients:
        # 应用 sso-client1:采用模式一对接 (同域、同Redis)
        sso-client1:
            client: sso-client1
            allowUrl: "*"
        # 应用 sso-client2:采用模式二对接 (跨域、同Redis)
        sso-client2:
            client: sso-client2
            allowUrl: "*"
            secretKey: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
        # 应用 sso-client3:采用模式三对接 (跨域、跨Redis)
        sso-client3:
            # 应用名称
            client: sso-client3
            # 允许授权地址
            allowUrl: "*"
            # 是否接收消息推送
            isPush: true
            # 消息推送地址
            pushUrl: http://sa-sso-client1.com:9003/sso/pushC
            # 接口调用秘钥 (如果不配置则使用全局默认秘钥)
            secretKey: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

sa-token.dao: #名字可以随意取
redis:
server: "localhost:6379"

password: 123456

text
db: 1
    maxTotal: 200

File: sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso1-client-solon/src/main/resources/app.yml

端口

server:
port: 9001

Sa-Token 配置

sa-token:
# 打印操作日志
is-log: true

text
# SSO-相关配置
sso-client:
    # client 标识
    client: sso-client1
    # SSO-Server端 - 主机地址
    server-url: http://sso.stp.com:9000

配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)

sa-token.dao: #名字可以随意取
redis:
server: "localhost:6379"

password: 123456

text
db: 1
    maxTotal: 200

File: sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso2-client-solon/src/main/resources/app.yml

端口

server:
port: 9002

sa-token配置

sa-token:
# 打印操作日志
is-log: true

text
# SSO-相关配置
sso-client:
    # 应用标识
    client: sso-client2
    # SSO-Server 端主机地址
    server-url: http://sa-sso-server.com:9000
    # 在 sso-server 端前后端分离时需要单独配置 auth-url 参数(上面的不要注释,auth-url 配置项和 server-url 要同时存在)
    # auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
    # API 接口调用秘钥 (单点注销时会用到)
    secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)

sa-token.dao: #名字可以随意取
redis:
server: "localhost:6379"

password: 123456

text
db: 1
    maxTotal: 200

File: sa-token-demo/sa-token-demo-sso-for-solon/sa-token-demo-sso3-client-solon/src/main/resources/app.yml

端口

server:
port: 9003

sa-token配置

sa-token:
# 打印操作日志
is-log: true

text
# sso-client 相关配置
sso-client:
    # 应用标识
    client: sso-client3
    # sso-server 端主机地址
    server-url: http://sa-sso-server.com:9000
    # 在 sso-server 端前后端分离时需要单独配置 auth-url 参数(上面的不要注释,auth-url 配置项和 server-url 要同时存在)
    # auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
    # 使用 Http 请求校验 ticket (模式三)
    is-http: true
    # API 接口调用秘钥
    secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

配置 Sa-Token Dao(此处与SSO-Server端连接不同的Redis)

sa-token.dao: #名字可以随意取
redis:
server: "localhost:6379"

password: 123456

text
db: 4
    maxTotal: 200

File: sa-token-demo/sa-token-demo-test/src/main/resources/application.yml

端口

server:
port: 8081

############## Sa-Token 配置 (文档: https://sa-token.com) ##############
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-thymeleaf/src/main/resources/application.yml

端口

server:
port: 8081


File: sa-token-demo/sa-token-demo-webflux/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-webflux-springboot3/src/main/resources/application.yml

[Binary file]


File: sa-token-demo/sa-token-demo-webflux-springboot4/src/main/resources/application.yml

[Binary file]


File: sa-token-demo/sa-token-demo-websocket/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-demo/sa-token-demo-websocket-spring/src/main/resources/application.yml

端口

server:
port: 8081

sa-token 配置

sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true

spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0


File: sa-token-doc-new/README.md

Sa-Token 文档站

本目录是 sa-token.com 的文档源码,基于 VitePress。

环境

  • Node 20.11+(建议 22)
  • 包管理:npm
bash
cd sa-token-doc-new
npm i

常用命令

命令 作用
npm run docs:dev 本地预览(http://localhost:5173)
npm run docs:build 编译到 dist/
npm run docs:preview 预览已构建的 dist

开发时:

  • / 是官网首页(public/index.html)
  • /readme.html 是文档介绍页
  • /blog/ 是独立博客页

改侧栏:直接改 .vitepress/sidebar.ts。

发布

bash
cd sa-token-doc-new
npm i
npm run docs:build

把 dist/ 里的内容 上传到 sa-token.com 根目录。上传的是编译后的 HTML / JS / CSS,不要上传 docs/ 下的 markdown。

目录说明

text
sa-token-doc-new/
  .vitepress/            配置、主题、侧栏
  public/                独立首页、博客、静态资源
  docs/                  文档正文(md)
  dist/                  构建产物(上传这个)

File: sa-token-doc-new/package.json

{
"name": "sa-token-doc",
"version": "1.46.0",
"private": true,
"type": "module",
"scripts": {
"docs:dev": "vitepress dev",
"docs:build": "vitepress build",
"docs:preview": "vitepress preview",
"bing:indexnow": "node scripts/indexnow-submit.mjs"
},
"engines": {
"node": ">=20.11"
},
"devDependencies": {
"medium-zoom": "^1.1.0",
"vitepress": "2.0.0-alpha.19",
"vitepress-plugin-tabs": "^0.9.1",
"vue": "^3.5.42"
}
}


File: sa-token-doc-new/docs/readme.md


title: Sa-Token 框架介绍 keywords: Sa-Token,satoken,sa-token文档,Java权限认证,Spring Boot,框架介绍

Sa-Token v1.46.0

✨ 开源、免费、一站式 java 权限认证框架,让鉴权变得简单、优雅!

📝 前言:️️

为了保证新同学不迷路,请允许我唠叨一下:无论您从何处看到本篇文章,最新开发文档永远在:https://sa-token.com,
建议收藏在浏览器书签 (快捷键 ctrl + D),如果您已经身处本网站下,则请忽略此条说明。

回望 2020 年初,我为 Sa-Token 提交第一行代码之际,彼时市面上 Java 缺少的不仅是一个简洁好用的鉴权框架,更是一整套清晰、自洽的权限架构设计思想。

因此,这几年间我们将大量时间倾注在 Sa-Token 的文档编写,几乎每一章节、每一句话、每一个字都经过反复修改、精细打磨,以求做到最清晰、干练、易懂的表述。用心阅读文档,你学习到的将不止是 Sa-Token 框架本身,更是绝大多数场景下权限认证设计的最佳实践。

🛠️ Sa-Token 介绍

Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权
等一系列权限相关问题。

Sa-Token 旨在以简单、优雅的方式完成系统的权限认证部分,以登录认证为例,你只需要:

java
// 会话登录,参数填登录人的账号id 
StpUtil.login(10001);

无需实现任何接口,无需创建任何配置文件,只需要这一句静态代码的调用,便可以完成会话登录认证。

如果一个接口需要登录后才能访问,我们只需调用以下代码:

java
// 校验当前客户端是否已经登录,如果未登录则抛出 `NotLoginException` 异常
StpUtil.checkLogin();

在 Sa-Token 中,大多数功能都可以一行代码解决:

踢人下线:

java
// 将账号id为 10077 的会话踢下线 
StpUtil.kickout(10077);

权限认证:

java
// 注解鉴权:只有具备 `user:add` 权限的会话才可以进入方法
@SaCheckPermission("user:add")
public String insert(SysUser user) {
    // ... 
    return "用户增加";
}

路由拦截鉴权:

java
// 根据路由划分模块,不同模块不同鉴权 
registry.addInterceptor(new SaInterceptor(handler -> {
	SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
	SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
	SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
	SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
	SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
	// 更多模块... 
})).addPathPatterns("/**");

当你受够 Shiro、SpringSecurity 等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,Sa-Token 的 API 设计是多么的简单、优雅!

🎉 Sa-Token 功能一览

Sa-Token 目前主要五大功能模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。

  • 登录认证 —— 单端登录、多端登录、同端互斥登录、七天内免登录。
  • 权限认证 —— 权限认证、角色认证、会话二级认证。
  • 踢人下线 —— 根据账号id踢人下线、根据Token值踢人下线。
  • 注解式鉴权 —— 优雅的将鉴权与业务代码分离。
  • 路由拦截式鉴权 —— 根据路由拦截鉴权,可适配 restful 模式。
  • Session会话 —— 全端共享Session,单端独享Session,自定义Session,方便的存取值。
  • 持久层扩展 —— 可集成 Redis,重启数据不丢失。
  • 前后台分离 —— APP、小程序等不支持 Cookie 的终端也可以轻松鉴权。
  • Token风格定制 —— 内置六种 Token 风格,还可:自定义 Token 生成策略。
  • 记住我模式 —— 适配 [记住我] 模式,重启浏览器免验证。
  • 二级认证 —— 在已登录的基础上再次认证,保证安全性。
  • 模拟他人账号 —— 实时操作任意用户状态数据。
  • 临时身份切换 —— 将会话身份临时切换为其它账号。
  • 同端互斥登录 —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录。
  • 账号封禁 —— 登录封禁、按照业务分类封禁、按照处罚阶梯封禁。
  • 密码加密 —— 提供基础加密算法,可快速 MD5、SHA1、SHA256、AES 加密。
  • 会话查询 —— 提供方便灵活的会话查询接口。
  • Http Basic认证 —— 一行代码接入 Http Basic、Digest 认证。
  • 全局侦听器 —— 在用户登陆、注销、被踢下线等关键性操作时进行一些AOP操作。
  • 全局过滤器 —— 方便的处理跨域,全局设置安全响应头等操作。
  • 多账号体系认证 —— 一个系统多套账号分开鉴权(比如商城的 User 表和 Admin 表)
  • 单点登录 —— 内置三种单点登录模式:同域、跨域、同Redis、跨Redis、前后端分离等架构都可以搞定。
  • 单点注销 —— 任意子系统内发起注销,即可全端下线。
  • OAuth2.0认证 —— 轻松搭建 OAuth2.0 服务,支持openid模式 。
  • 分布式会话 —— 提供共享数据中心分布式会话方案。
  • 微服务网关鉴权 —— 适配Gateway、ShenYu、Zuul等常见网关的路由拦截认证。
  • RPC调用鉴权 —— 网关转发鉴权,RPC调用鉴权,让服务调用不再裸奔
  • 临时Token认证 —— 解决短时间的 Token 授权问题。
  • 独立Redis —— 将权限缓存与业务缓存分离。
  • Quick快速登录认证 —— 为项目零代码注入一个登录页面。
  • 标签方言 —— 提供 Thymeleaf 标签方言集成包,提供 beetl 集成示例。
  • jwt集成 —— 提供三种模式的 jwt 集成方案,提供 token 扩展参数能力。
  • RPC调用状态传递 —— 提供 dubbo、grpc 等集成包,在RPC调用时登录状态不丢失。
  • 参数签名 —— 提供跨系统API调用签名校验模块,防参数篡改,防请求重放。
  • 自动续签 —— 提供两种Token过期策略,灵活搭配使用,还可自动续签。
  • 开箱即用 —— 提供SpringMVC、WebFlux、Solon 等常见框架集成包,开箱即用。
  • 最新技术栈 —— 适配最新技术栈:支持 SpringBoot 3.x,jdk 17。

功能结构图:

📖❓ 疑问解答

1、Sa-Token 功能全不全?

七年磨一剑:五大核心模块(登录、鉴权、SSO、OAuth2、微服务) + 众多实用插件 (短 token、jwt 集成、API 参数签名、API Key 秘钥授权...) 我们提供的不只是权限认证,我们提供的是一站式解决方案。

2、Sa-Token 好不好学?

中文文档 + 中文代码注释 + 中文交流社区 + 大量实战案例博客 + 多个视频教程 + 大量优秀开源项目集成案例。

3、Sa-Token 用的人多不多?

截止统计日 (2026-1-25) 起,Sa-Token 在:

  • Gitee 关注量达到 48627 Star,位列平台所有推荐项目排行榜第一名。
  • GitHub 关注量达到 18523 Star,是主要竞争框架 Spring Security 的 1.97 倍,Apache Shiro 的 4.19 倍。
  • 25+ 微信粉丝群 (500人),8+ QQ粉丝群 (1000人 or 2000人) ,在线文档访问量月PV 20万+。

这是众多开发者用脚投票的数据,相信这些数据比任何言语都能证明 Sa-Token 的热度。

4、Sa-Token 有哪些权威认证?

曾获荣誉包括但不限于:Gitee GVP 最有价值开源项目、GitCode G-Star 优质开源项目、OSCHINA 2021 人气指数 TOP 30 开源项目、OSCHINA 2022 年度最火热中国开源项目社区之一、开放原子基金会2023快速成长开源项目、 Dromara 组织顶尖项目(之一)、可信开源社区共同体预备成员、所在开源社区 “Dromara” 荣获《2024中国互联网发展创新与投资大赛(开源)》二等奖。 Gitee High Star 计划项目(5000+star)。Gitee 2025年度开源项目 Web应用开发 Top 2。

5、Sa-Token 收费吗?

Sa-Token 采用 Apache-2.0 开源协议,承诺框架本身与在线文档永久免费开放。当然如果您有心赞助 Sa-Token,我们也不回避:赞助链接。
我们将定期同步赞助者名单到在线文档展示。(您需要注意的一点是:该赞助仅为友情赞助,不提供任何商业交换)

6、Sa-Token 是封装的 SpringSecurity 吗?是套壳 ApacheShiro 吗?

不是。Sa-Token 不是一个后台模板,也不是针对 xx 框架的二次封装套壳,而是从 0 开始的纯血自研框架,核心包零依赖,完全自主可控的架构内核 + 众多主流框架的集成适配。

🌍 其它语言版本

Sa-Token 社区成员贡献了多语言实现版本:

我们诚邀对上述语言较为熟练的开发者,一起建设相关版本。🤝

📈 开源仓库 Star 趋势

如果 Sa-Token 帮助到了您,希望您可以为其点上一个 star:
码云、
AtomGit、
GitHub

🚀 使用 Sa-Token 的开源项目

参考:Sa-Token 优秀开源集成案例

📚 示例大全

我们为框架几乎所有技术点均单独制作了对应的集成示例,此压缩包共计 60+ Demo:涵盖 Sa-Token 登录认证、权限认证、SSO 单点登录、OAuth2 统一认证、微服务鉴权、API Key 认证、JWT集成、跨系统调用参数签名校验 等鉴权认证的方方面面。

下载地址:Sa-Token 集成示例大全下载

💬 交流群

加入 Sa-Token 框架 QQ、微信讨论群:点击加入


File: sa-token-doc-new/docs/api/sa-session.md

[Binary file]


File: sa-token-doc-new/docs/api/sa-strategy.md


title: "Sa-Token SaStrategy-全局策略" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,SaStrategy-全局策略,API手册"

SaStrategy-全局策略

SaStrategy-全局策略,核心逻辑的代理封装。


核心策略

java
/**
 * 创建 Token 的策略 
 * <p> 参数 [账号id, 账号类型] 
 */
public BiFunction<Object, String, String> createToken = (loginId, loginType) -> {
	// 默认,还是uuid 
	return "xxxxx-xxxxx-xxxxx-xxxxx";
};

/**
 * 创建 Session 的策略 
 * <p> 参数 [SessionId] 
 */
public Function<String, SaSession> createSession = (sessionId) -> {
	return new SaSession(sessionId);
};

/**
 * 反序列化 SaSession 时默认指定的类型
 */
public Class<? extends SaSession> sessionClassType = SaSession.class;

/**
 * 判断:集合中是否包含指定元素(模糊匹配) 
 * <p> 参数 [集合, 元素] 
 */
public BiFunction<List<String>, String, Boolean> hasElement = (list, element) -> {
	return false;
};


/**
 * 生成唯一式 token 的算法
 * <p> 参数:元素名称, 最大尝试次数, 创建 token 函数, 检查 token 函数 </p>
 */
public SaGenerateUniqueTokenFunction generateUniqueToken = (elementName, maxTryTimes, createTokenFunction, checkTokenFunction) -> {
	// ...
	return "xxxxxx";
};

/**
 * 是否自动续期,每次续期前都会执行,可以加入动态判断逻辑
 * <p> 参数 当前 stpLogic 实例对象
 * <p> 返回 true 自动续期 false 不自动续期
 */
public Function<StpLogic, Boolean> autoRenew = (stpLogic) -> {
	return stpLogic.getConfigOrGlobal().getAutoRenew();
};

/**
 * 创建 StpLogic 的算法
 * <p>  参数:账号体系标识  </p>
 * <p>  返回:创建好的 StpLogic 对象  </p>
 */
public SaCreateStpLogicFunction createStpLogic = (loginType) -> {
	return new StpLogic(loginType);
};

/**
 * 路由匹配策略
 * <p>  参数:pattern, path  </p>
 * <p>  返回:是否匹配  </p>
 */
public SaRouteMatchFunction routeMatcher = (pattern, path) -> {
	return true;
};

/**
 * CORS 策略处理函数
 * <p>  参数:请求包装对象, 响应包装对象, 数据读写对象  </p>
 */
public SaCorsHandleFunction corsHandle = (req, res, sto) -> {

};

/**
 * 获取 SaTokenConfig 的策略
 * <p>  默认 null,表示使用框架内置逻辑(先读 SaManager.config,为空时自动读取 sa-token.properties)  </p>
 * <p>  赋值后,每次调用 SaManager.getConfig() 时都会执行此策略并直接返回其结果  </p>
 * <p>  注意:策略内请勿再调用 SaManager.getConfig(),否则会陷入无限递归  </p>
 */
public SaGetSaTokenConfigFunction getSaTokenConfig = null;

重写策略(set 连缀风格)

java
SaStrategy.instance.setCreateToken(createToken);   // 重写创建 Token 的策略
SaStrategy.instance.setCreateSession(createSession);   // 重写创建 Session 的策略
SaStrategy.instance.setHasElement(hasElement);   // 重写集合模糊匹配策略
SaStrategy.instance.setGenerateUniqueToken(generateUniqueToken);   // 重写生成唯一 token 的策略
SaStrategy.instance.setCreateStpLogic(createStpLogic);   // 重写创建 StpLogic 的策略
SaStrategy.instance.setAutoRenew(autoRenew);   // 重写是否自动续期策略
SaStrategy.instance.setGetSaTokenConfig(getSaTokenConfig);   // 重写获取 SaTokenConfig 的策略

getSaTokenConfig 使用示例

适用于需要从数据库等外部数据源动态读取配置的场景:

java
SaStrategy.instance.setGetSaTokenConfig(() -> {
	// 从数据库读取配置,自行做好缓存
	SaTokenConfig config = new SaTokenConfig();
	config.setTokenName("satoken");
	config.setTimeout(30 * 24 * 60 * 60);
	return config;
});

注意:

  • 策略内不要调用 SaManager.getConfig(),否则会无限递归。
  • 启用后,每次 SaManager.getConfig() 都会走此策略;请做好缓存处理。

File: sa-token-doc-new/docs/api/sa-token-dao.md


title: "SaTokenDao-数据持久接口" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,SaTokenDao-数据持久接口,API手册"

SaTokenDao-数据持久接口

SaTokenDao 是数据持久层接口,负责所有会话数据的底层写入和读取。


1、常量

java
SaTokenDao.NEVER_EXPIRE = -1;   // 常量,表示一个key永不过期 (在一个key被标注为永远不过期时返回此值)
SaTokenDao.NOT_VALUE_EXPIRE = -2;   // 常量,表示系统中不存在这个缓存 (在对不存在的key获取剩余存活时间时返回此值)

2、字符串读写

java
dao.get(key);   // 获取Value,如无返空
dao.set(key, value, timeout);   // 写入Value,并设定存活时间 (单位: 秒)
dao.update(key, value);   // 更新Value (过期时间不变)
dao.delete(key);   // 删除Value
dao.getTimeout(key);   // 获取Value的剩余存活时间 (单位: 秒) 
dao.updateTimeout(key, timeout);   // 修改Value的剩余存活时间 (单位: 秒)

3、对象读写

java
dao.getObject(key);   // 获取Object,如无返空
dao.getObject(key, classType);   // 获取Object,并指定转换类型
dao.setObject(key, value, timeout);   // 写入Object,并设定存活时间 (单位: 秒)
dao.updateObject(key, value);   // 更新Object (过期时间不变)
dao.deleteObject(key);   // 删除Object
dao.getObjectTimeout(key);   // 获取Object的剩余存活时间 (单位: 秒) 
dao.updateObjectTimeout(key, timeout);   // 修改Object的剩余存活时间 (单位: 秒)

4、Session读写

java
dao.getSession(sessionId);   // 获取Session,如无返空
dao.setSession(session, timeout);   // 写入Session,并设定存活时间 (单位: 秒)
dao.updateSession(session);   // 更新Session (过期时间不变)
dao.deleteSession(sessionId);   // 删除Session
dao.getSessionTimeout(sessionId);   // 获取Session的剩余存活时间 (单位: 秒) 
dao.updateSessionTimeout(sessionId, timeout);   // 修改Session的剩余存活时间 (单位: 秒)

5、会话管理

java
dao.searchData(prefix, keyword, start, size, sortType);   // 搜索数据

File: sa-token-doc-new/docs/api/stp-util.md


title: "Sa-Token StpUtil-鉴权工具类" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,StpUtil-鉴权工具类,API手册"

StpUtil - 鉴权工具类

StpUtil 是 Sa-Token 整体功能的核心,大多数功能均由此工具类提供。


1、常规操作

java
StpUtil.getStpLogic();   // 获取底层 StpLogic 对象。
StpUtil.setStpLogic(newStpLogic);   // 安全的重置底层 StpLogic 引用。
StpUtil.getLoginType();   // 获取账号类型 (例如:login、user、admin、teacher、student等等)。
StpUtil.getTokenName();   // 获取 Token 的名称 
StpUtil.getTokenValue();   // 获取本次请求前端提交的 Token。
StpUtil.getTokenValueNotCut();   // 获取本次请求前端提交的 Token (不裁剪前缀) 。
StpUtil.setTokenValue(tokenValue);   // 在当前会话中写入 Token 值。
StpUtil.setTokenValue(tokenValue, timeout);   // 在当前会话中写入 Token 值,并指定 Cookie 有效期。
StpUtil.setTokenValue(tokenValue, loginParameter);   // 在当前会话中写入 Token 值,并指定登录参数。
StpUtil.setTokenValueToStorage(tokenValue);   // 将 Token 写入当前请求的 Storage 存储器。
StpUtil.getTokenInfo();   // 获取当前 Token 的详细参数。
StpUtil.createSaLoginParameter();   // 根据当前配置对象创建一个 SaLoginParameter 对象

2、登录相关

java
StpUtil.login(10001);   // 会话登录
StpUtil.login(10001, "APP");   // 会话登录,并指定设备类型
StpUtil.login(10001, true);   // 会话登录,并指定是否 [记住我]
StpUtil.login(10001, 86400);   // 会话登录,并指定此次 token 有效期(单位:秒)
StpUtil.login(10001, loginParameter);   // 会话登录,并指定所有登录参数Model
StpUtil.createLoginSession(10001);   // 创建指定账号id的登录会话,此方法不会将 Token 注入到上下文 
StpUtil.createLoginSession(10001, loginParameter);   // 创建指定账号id的登录会话,此方法不会将 Token 注入到上下文 
StpUtil.getOrCreateLoginSession(10001);   // 获取指定账号的登录会话,若不存在则创建并返回

更多 SaLoginParameter 登录参数的用法与示例,请参考 登录参数示例章节。

3、注销相关

java
StpUtil.logout();   // 会话注销 
StpUtil.logout(logoutParameter);   // 会话注销,根据注销参数
StpUtil.logout(10001);   // 会话注销,根据账号id
StpUtil.logout(10001, "PC");   // 会话注销,根据账号id 和 设备类型
StpUtil.logout(10001, logoutParameter);   // 会话注销,根据账号id 和 注销参数
StpUtil.logoutByTokenValue(token);   // 指定 Token 强制注销
StpUtil.logoutByTokenValue(token, logoutParameter);   // 指定 Token 强制注销,带注销参数
StpUtil.kickout(10001);   // 踢人下线,根据账号id
StpUtil.kickout(10001, "PC");   // 踢人下线,根据账号id 和 设备类型
StpUtil.kickout(10001, logoutParameter);   // 踢人下线,根据账号id 和 注销参数
StpUtil.kickoutByTokenValue(token);   // 踢人下线,根据token
StpUtil.kickoutByTokenValue(token, logoutParameter);   // 踢人下线,根据token,带注销参数
StpUtil.replacedByTokenValue(token);   // 顶人下线,根据token
StpUtil.replacedByTokenValue(token, logoutParameter);   // 顶人下线,根据token,带注销参数
StpUtil.replaced(10001);   // 顶人下线,根据账号id
StpUtil.replaced(10001, "PC");   // 顶人下线,根据账号id 和 设备类型
StpUtil.replaced(10001, logoutParameter);   // 顶人下线,根据账号id 和 注销参数

更多 SaLogoutParameter 注销参数的用法与示例,请参考 注销参数示例章节。

4、会话查询

java
StpUtil.isLogin();   // 当前会话是否已经登录 
StpUtil.isLogin(10001);   // 判断指定账号是否已登录
StpUtil.checkLogin();   // 检验当前会话是否已经登录,如未登录,则抛出异常
StpUtil.getLoginId();   // 获取当前会话账号id, 如果未登录,则抛出异常 
StpUtil.getLoginId(defaultValue);   // 获取当前会话账号id, 如果未登录,则返回默认值 
StpUtil.getLoginIdDefaultNull();   // 获取当前会话账号id, 如果未登录,则返回null 
StpUtil.getLoginIdAsString();   // 获取当前会话账号id, 并转换为String类型
StpUtil.getLoginIdAsInt();   // 获取当前会话账号id, 并转换为int类型
StpUtil.getLoginIdAsLong();   // 获取当前会话账号id, 并转换为long类型 
StpUtil.getLoginIdByToken(token);   // 获取指定 Token 对应的账号 id,若 token 无效/被踢/被顶/被冻结则返回 null 
StpUtil.getLoginIdByTokenNotThinkFreeze(token);   // 获取指定Token对应的账号id(不考虑冻结状态),如果未登录,则返回 null 
StpUtil.getExtra(key);   // 获取当前 Token 的扩展信息(此函数只在jwt模式下生效)
StpUtil.getExtra(token, key);   // 获取指定 Token 的扩展信息(此函数只在jwt模式下生效)

5、Session 相关

java
// Account-Session 相关 
StpUtil.getSession();   // 获取当前会话的Session,如果Session尚未创建,则新建并返回 
StpUtil.getSession(true);   // 获取当前会话的Session, 如果Session尚未创建,isCreate=是否新建并返回
StpUtil.getSessionByLoginId(10001);   // 获取指定账号id的Session,如果Session尚未创建,则新建并返回
StpUtil.getSessionByLoginId(10001, true);   // 获取指定账号id的Session, 如果Session尚未创建,isCreate=是否新建并返回

// Token-Session 相关 
StpUtil.getTokenSession();   // 获取当前 Token-Session,如果 Session 尚未创建,则新建并返回 
StpUtil.getTokenSessionByToken(token);   // 获取指定Token-Session,如果Session尚未创建,则新建并返回
StpUtil.getAnonTokenSession();   // 获取当前匿名 Token-Session (可在未登录情况下使用的Token-Session)

// 其它
StpUtil.getSessionBySessionId("xxxx-xxxx-xxxx");   // 获取指定 sessionId 的 Session,若不存在则返回 null
StpUtil.isTrustDeviceId(123456, "xxxxxxxxxxxxxxxxxxxxxxxx");   // 判断对于指定 loginId 来讲,指定设备 id 是否为可信任设备

6、Token有效期相关

java
// Token 最低活跃频率
StpUtil.getTokenActiveTimeout();   // 获取当前 token 距离被冻结还剩多少时间 (单位: 秒)
StpUtil.getTokenLastActiveTime();  // 获取当前 token 最后活跃时间
StpUtil.checkActiveTimeout();   // 检查当前token 是否已经被冻结,如果是则抛出异常  
StpUtil.updateLastActiveToNow();   // 续签当前token:(将 [最后操作时间] 更新为当前时间戳)   

// Token 有效期
StpUtil.getTokenTimeout();   // 获取当前登录者的 token 剩余有效时间 (单位: 秒)
StpUtil.getTokenTimeout(token);   // 获取指定 token 的剩余有效时间 (单位: 秒)
StpUtil.getSessionTimeout();   // 获取当前登录者的 Account-Session 剩余有效时间 (单位: 秒)
StpUtil.getTokenSessionTimeout();   // 获取当前 Token-Session 剩余有效时间 (单位: 秒) 
StpUtil.renewTimeout(timeout);   // 对当前 Token 的 timeout 值进行续期 
StpUtil.renewTimeout(token, timeout);   // 对指定 Token 的 timeout 值进行续期

7、角色认证

java
StpUtil.getRoleList();   // 获取:当前账号的角色集合
StpUtil.getRoleList(10001);   // 获取:指定账号的角色集合 
StpUtil.hasRole(role);   // 判断:当前账号是否拥有指定角色, 返回true或false 
StpUtil.hasRole(loginId, role);   // 判断:指定账号是否含有指定角色标识, 返回true或false 
StpUtil.hasRoleAnd(...roleArray);   // 判断:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过] 
StpUtil.hasRoleOr(...roleArray);   // 判断:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可] 
StpUtil.checkRole(role);   // 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException 
StpUtil.checkRoleAnd(...roleArray);   // 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过] 
StpUtil.checkRoleOr(...roleArray);   // 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]

8、权限认证

java
StpUtil.getPermissionList();   // 获取:当前账号的权限集合
StpUtil.getPermissionList(10001);   // 获取:指定账号的权限集合 
StpUtil.hasPermission(permission);   // 判断:当前账号是否拥有指定权限, 返回true或false 
StpUtil.hasPermission(loginId, permission);   // 判断:指定账号是否含有指定权限标识, 返回true或false 
StpUtil.hasPermissionAnd(...permissionArray);   // 判断:当前账号是否含有指定权限标识 [指定多个,必须全部验证通过] 
StpUtil.hasPermissionOr(...permissionArray);   // 判断:当前账号是否含有指定权限标识 [指定多个,只要其一验证通过即可] 
StpUtil.checkPermission(permission);   // 校验:当前账号是否含有指定权限标识, 如果验证未通过,则抛出异常: NotPermissionException 
StpUtil.checkPermissionAnd(...permissionArray);   // 校验:当前账号是否含有指定权限标识 [指定多个,必须全部验证通过] 
StpUtil.checkPermissionOr(...permissionArray);   // 校验:当前账号是否含有指定权限标识 [指定多个,只要其一验证通过即可]

9、id 反查 Token

java
StpUtil.getTokenValueByLoginId(10001);   // 获取指定账号id的tokenValue 
StpUtil.getTokenValueByLoginId(10001, "PC");   // 获取指定账号id指定设备类型端的tokenValue
StpUtil.getTokenValueListByLoginId(10001);   // 获取指定账号id的tokenValue集合 
StpUtil.getTokenValueListByLoginId(10001, "APP");   // 获取指定账号id指定设备类型端的tokenValue 集合 
StpUtil.getTerminalListByLoginId(10001);   // 获取指定账号 id 已登录设备信息集合
StpUtil.getTerminalListByLoginId(10001, "PC");   // 获取指定账号 id 指定设备类型端的已登录设备信息集合
StpUtil.forEachTerminalList(10001, (session, terminal) -> {});   // 遍历指定账号的已登录设备列表
StpUtil.getTerminalInfo();   // 获取当前会话的终端信息
StpUtil.getTerminalInfoByToken(token);   // 获取指定 token 的终端信息
StpUtil.getLoginDeviceId();   // 返回当前会话的登录设备 id
StpUtil.getLoginDeviceIdByToken(token);   // 返回指定 token 的登录设备 id
StpUtil.getLoginDeviceType();   // 返回当前会话的登录设备类型
StpUtil.getLoginDeviceTypeByToken(token);   // 返回指定 token 的登录设备类型

10、会话管理

java
StpUtil.searchTokenValue(keyword, start, size, sortType);   // 根据条件查询Token
StpUtil.searchSessionId(keyword, start, size, sortType);   // 根据条件查询SessionId 
StpUtil.searchTokenSessionId(keyword, start, size, sortType);   // 根据条件查询Token专属Session的Id

详细可参考:会话治理

11、账号封禁

java
StpUtil.disable(10001, 1200);   // 封禁:指定账号 指定时间(单位s)
StpUtil.isDisable(10001);   // 判断:指定账号是否已被封禁 (true=已被封禁, false=未被封禁) 
StpUtil.checkDisable(10001);   // 校验:指定账号是否已被封禁,如果被封禁则抛出异常 `DisableServiceException`
StpUtil.getDisableTime(10001);   // 获取:指定账号剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁)
StpUtil.untieDisable(loginId);   // 解封:指定账号

12、分类封禁 (version >= 1.31.0)

java
StpUtil.disable(10001, "<业务标识>", 86400);   // 封禁:指定账号的指定服务 指定时间(单位s)
StpUtil.isDisable(10001, "<业务标识>");   // 判断:指定账号的指定服务 是否已被封禁 (true=已被封禁, false=未被封禁) 
StpUtil.checkDisable(10001, "<业务标识>");   // 校验:指定账号的指定服务 是否已被封禁,如果被封禁则抛出异常 `DisableServiceException`(支持传入多个业务标识)
StpUtil.getDisableTime(10001, "<业务标识>");   // 获取:指定账号的指定服务 剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁)
StpUtil.untieDisable(loginId, "<业务标识>");   // 解封:指定账号的指定服务(支持传入多个业务标识)

13、阶梯封禁 (version >= 1.31.0)

java
StpUtil.disableLevel(10001, "comment", 3, 10000);   // 分类阶梯封禁,参数:封禁账号、封禁服务、封禁级别、封禁时间 
StpUtil.disableLevel(10001, 3, 10000);   // 阶梯封禁(无业务标识,使用默认服务),参数:封禁账号、封禁级别、封禁时间
StpUtil.getDisableLevel(10001, "comment");   // 获取:指定账号的指定服务 封禁的级别 (如果此账号未被封禁则返回 -2)
StpUtil.getDisableLevel(10001);   // 获取:指定账号的封禁等级(无业务标识,未封禁返回 -2)
StpUtil.isDisableLevel(10001, "comment", 3);   // 判断:指定账号的指定服务 是否已被封禁到指定级别,返回 true 或 false
StpUtil.isDisableLevel(10001, 3);   // 判断:指定账号是否被封禁到指定级别(无业务标识)
StpUtil.checkDisableLevel(10001, "comment", 2);   // 校验:指定账号的指定服务 是否已被封禁到指定级别(例如 comment服务 已被3级封禁,这里校验是否达到2级),如果已达到此级别,则抛出异常 
StpUtil.checkDisableLevel(10001, 2);   // 校验:指定账号是否被封禁到指定级别(无业务标识)

14、身份切换

java
StpUtil.switchTo(10044);   // 临时切换身份为指定账号id 
StpUtil.endSwitch();   // 结束临时切换身份
StpUtil.isSwitch();   // 当前是否正处于[身份临时切换]中 
StpUtil.switchTo(10044, () -> {});   // 在一个代码段里方法内,临时切换身份为指定账号id

15、二级认证

java
StpUtil.openSafe(safeTime);   // 在当前会话 开启二级认证 
StpUtil.isSafe();   // 当前会话 是否处于二级认证时间内 
StpUtil.checkSafe();   // 检查当前会话是否已通过二级认证,如未通过则抛出异常 
StpUtil.getSafeTime();   // 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证)
StpUtil.closeSafe();   // 在当前会话 结束二级认证

16、带有业务标识的二级认证

java
StpUtil.openSafe("<业务标识>", safeTime);   // 在当前会话 指定业务标识开启二级认证 
StpUtil.isSafe("<业务标识>");   // 当前会话 指定业务标识是否处于二级认证时间内 
StpUtil.isSafe(tokenValue, "<业务标识>");   // 判断指定 token 的指定业务是否处于二级认证时间内
StpUtil.checkSafe("<业务标识>");   // 检查当前会话,指定业务标识是否已通过二级认证,如未通过则抛出异常 
StpUtil.getSafeTime("<业务标识>");   // 获取当前会话的指定业务标识二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证)
StpUtil.closeSafe("<业务标识>");   // 在当前会话 结束指定业务标识二级认证

File: sa-token-doc-new/docs/arch/data-structure.md

[Binary file]


File: sa-token-doc-new/docs/arch/dir-intro.md


title: "Sa-Token 仓库目录" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,仓库目录,框架设计"

仓库目录介绍


1、仓库根目录介绍:

js
── sa-token
	├── sa-token-core                         // [核心] Sa-Token 核心模块
	├── sa-token-dependencies                 // [依赖] Sa-Token 依赖版本信息
	├── sa-token-special-dependencies         // [依赖] Sa-Token 特殊依赖(SpringBoot2/3/4 版本隔离)
	├── sa-token-bom                          // [核心] Sa-Token bom 包
	├── sa-token-starter                      // [整合] Sa-Token 与其它框架整合
	├── sa-token-plugin                       // [插件] Sa-Token 插件合集
	├── sa-token-demo                         // [示例] Sa-Token 示例合集
	├── sa-token-test                         // [测试] Sa-Token 单元测试合集
	├── sa-token-doc                          // [文档] Sa-Token 开发文档 
	├── MEMO                                  // [备忘] 内部备忘录、开发记录
	├── pom.xml                               // [依赖] 顶级pom文件 
	├── LICENSE                               // 开源协议
	├── mvn clean.bat                         // 一键 mvn clean 核心包+所有示例包
	├── mvn test.bat                          // 一键单元测试 
	├── preview-doc.bat                       // 一键预览开发文档
	├── README.md                             // 仓库自述文件

2、所有目录详细介绍:

js
── sa-token
	├── sa-token-core                         // [核心] Sa-Token 核心模块
	├── sa-token-dependencies                 // [依赖] Sa-Token 依赖版本信息
	├── sa-token-special-dependencies         // [依赖] Sa-Token 特殊依赖(SpringBoot2/3/4 版本隔离)
	├── sa-token-bom                          // [核心] Sa-Token bom 包
	├── sa-token-starter                      // [整合] Sa-Token 与其它框架整合
		├── sa-token-servlet                      // [整合] Sa-Token 整合 Servlet 容器实现类包
		├── sa-token-jakarta-servlet              // [整合] Sa-Token 整合 Jakarta-Servlet 容器实现类包
		├── sa-token-spring-boot-webmvc-reactor-v2v3v4-common  // [整合] Sa-Token SpringBoot WebMvc+Reactor 公共包 (2/3/4)
		├── sa-token-spring-boot-reactor-v3v4-common          // [整合] Sa-Token SpringBoot Reactor 公共包 (3/4)
		├── sa-token-spring-boot-starter                       // [整合] Sa-Token 整合 SpringBoot2 快速集成 
		├── sa-token-spring-boot-webmvc-v3v4-common            // [整合] Sa-Token SpringBoot WebMvc 公共包 (3/4)
		├── sa-token-spring-boot3-starter         // [整合] Sa-Token 整合 SpringBoot3 快速集成 
		├── sa-token-spring-boot4-starter         // [整合] Sa-Token 整合 SpringBoot4 快速集成 
		├── sa-token-reactor-spring-boot-starter  // [整合] Sa-Token 整合 SpringBoot2 Reactor 响应式编程 快速集成 
		├── sa-token-reactor-spring-boot3-starter // [整合] Sa-Token 整合 SpringBoot3 Reactor 响应式编程 快速集成 
		├── sa-token-reactor-spring-boot4-starter // [整合] Sa-Token 整合 SpringBoot4 Reactor 响应式编程 快速集成 
		├── sa-token-solon-plugin                 // [整合] Sa-Token 整合 Solon 快速集成 
		├── sa-token-jfinal-plugin                // [整合] Sa-Token 整合 JFinal 快速集成 
		├── sa-token-jboot-plugin                 // [整合] Sa-Token 整合 jboot 快速集成 
		├── sa-token-loveqq-boot-starter          // [整合] Sa-Token 整合 LoveQQ-Boot 快速集成 
	├── sa-token-plugin                       // [插件] Sa-Token 插件合集
		├── sa-token-jackson                      // [插件] Sa-Token 整合 Jackson (json序列化插件) 
		├── sa-token-jackson3                     // [插件] Sa-Token 整合 Jackson3 (json序列化插件) 
		├── sa-token-fastjson                     // [插件] Sa-Token 整合 Fastjson (json序列化插件) 
		├── sa-token-fastjson2                    // [插件] Sa-Token 整合 Fastjson2 (json序列化插件) 
		├── sa-token-fory-json                    // [插件] Sa-Token 整合 Apache Fory JSON (json序列化插件) 
		├── sa-token-snack3                       // [插件] Sa-Token 整合 Snack3 (json序列化插件) 
		├── sa-token-snack4                       // [插件] Sa-Token 整合 Snack4 (json序列化插件) 
		├── sa-token-hutool-timed-cache           // [插件] Sa-Token 整合 Hutool 缓存组件 Timed-Cache(基于内存) (数据缓存插件) 
		├── sa-token-caffeine                     // [插件] Sa-Token 整合 Caffeine 缓存组件(基于内存) (数据缓存插件) 
		├── sa-token-thymeleaf                    // [插件] Sa-Token 整合 Thymeleaf (自定义标签方言) 
		├── sa-token-freemarker                   // [插件] Sa-Token 整合 Freemarker (自定义标签方言) 
		├── sa-token-dubbo                        // [插件] Sa-Token 整合 Dubbo (RPC 调用鉴权、状态传递) 
		├── sa-token-dubbo3                       // [插件] Sa-Token 整合 Dubbo3 (RPC 调用鉴权、状态传递) 
		├── sa-token-temp-jwt                     // [插件] Sa-Token 整合 jjwt (临时 Token) 
		├── sa-token-jwt                          // [插件] Sa-Token 整合 jjwt (JWT 登录认证) 
		├── sa-token-sso                          // [插件] Sa-Token 实现 SSO 单点登录
		├── sa-token-oauth2                       // [插件] Sa-Token 实现 OAuth2.0 认证
		├── sa-token-apikey                       // [插件] Sa-Token 实现 API Key 认证
		├── sa-token-sign                         // [插件] Sa-Token 实现 API 参数签名  
		├── sa-token-redisson                     // [插件] Sa-Token 整合 Redisson (数据缓存插件) 
		├── sa-token-redisx                       // [插件] Sa-Token 整合 Redisx (数据缓存插件) 
		├── sa-token-serializer-features          // [插件] Sa-Token 序列化实现扩展 
		├── sa-token-redis-template               // [插件] Sa-Token 整合 RedisTemplate (数据缓存插件) 
		├── sa-token-redis-template-jdk-serializer // [插件] Sa-Token 整合 RedisTemplate - 使用 jdk 序列化算法 (数据缓存插件) 
		├── sa-token-redis-jackson                // [插件] Sa-Token 整合 RedisTemplate - 使用 Jackson 序列化算法 (数据缓存插件) 
		├── sa-token-alone-redis                  // [插件] Sa-Token 独立 Redis 插件,实现 [ 权限缓存与业务缓存分离 ]
		├── sa-token-alone-redisson               // [插件] Sa-Token 独立 Redisson 插件,实现 [ 权限缓存与业务缓存分离 ]
		├── sa-token-spring-aop                   // [插件] Sa-Token 整合 SpringAOP 注解鉴权
		├── sa-token-spring-el                    // [插件] Sa-Token 实现 SpringEL 表达式注解鉴权
		├── sa-token-grpc                         // [插件] Sa-Token 整合 gRPC (RPC 调用鉴权、状态传递) 
		├── sa-token-quick-login                  // [插件] Sa-Token 快速注入登录页插件 
		├── sa-token-redisson-spring-boot-starter // [插件] Sa-Token 整合 Redisson - SpringBoot 自动配置包 (数据缓存插件) 
		├── sa-token-forest                       // [插件] Sa-Token 整合 Forest,http 请求处理器 
		├── sa-token-okhttps                      // [插件] Sa-Token 整合 OkHttps,http 请求处理器 
		├── sa-token-rest-template                // [插件] Sa-Token 整合 RestTemplate,http 请求处理器 
		├── sa-token-rest-client                  // [插件] Sa-Token 整合 RestClient,http 请求处理器 
	├── sa-token-demo                         // [示例] Sa-Token 示例合集
		├── sa-token-demo-alone-redis             // [示例] Sa-Token 集成 alone-redis 模块
		├── sa-token-demo-alone-redis-cluster     // [示例] Sa-Token 集成 alone-redis 模块、集群模式
		├── sa-token-demo-alone-redisson          // [示例] Sa-Token 集成 alone-redisson 模块
		├── sa-token-demo-alone-redisson-cluster  // [示例] Sa-Token 集成 alone-redisson 模块、集群模式
		├── sa-token-demo-alone-redisson-sb3      // [示例] Sa-Token 集成 alone-redisson 模块(Spring Boot 3)
		├── sa-token-demo-alone-redisson-sb4      // [示例] Sa-Token 集成 alone-redisson 模块(Spring Boot 4)
		├── sa-token-demo-apikey                  // [示例] Sa-Token API Key 模块示例
		├── sa-token-demo-async                   // [示例] Sa-Token 异步场景示例
		├── sa-token-demo-beetl                   // [示例] Sa-Token 集成 beetl 示例
		├── sa-token-demo-bom-import              // [示例] Sa-Token bom 包导入示例 
		├── sa-token-demo-case                    // [示例] Sa-Token 各模块示例
		├── sa-token-demo-device-lock             // [示例] Sa-Token 设备锁登录示例 - 后端
		├── sa-token-demo-device-lock-h5          // [示例] Sa-Token 设备锁登录示例 - 前端 
		├── sa-token-demo-dubbo                   // [示例] Sa-Token 集成 dubbo
			├── sa-token-demo-dubbo-consumer          // [示例] Sa-Token 集成 dubbo 鉴权,消费端(调用端)
			├── sa-token-demo-dubbo-provider          // [示例] Sa-Token 集成 dubbo 鉴权,生产端(被调用端)
			├── sa-token-demo-dubbo3-consumer         // [示例] Sa-Token 集成 dubbo3 鉴权,消费端(调用端)
			├── sa-token-demo-dubbo3-provider         // [示例] Sa-Token 集成 dubbo3 鉴权,生产端(被调用端)
		├── sa-token-demo-freemarker              // [示例] Sa-Token 集成 Freemarker 标签方言
		├── sa-token-demo-first-run               // [示例] 源码首次运行引导,强制引入 apikey、sso、oauth2、sign、jwt 等可选模块
		├── sa-token-demo-bug-reproduce           // [示例] Issue/Bug 复现沙盒(按需改代码验证)
		├── sa-token-demo-json-typing-security    // [示例] JSON 多态反序列化白名单回归(Spring Boot + Jackson)
		├── sa-token-demo-json-typing-security-for-solon // [示例] JSON 多态反序列化白名单回归(Solon + Snack4)
		├── sa-token-demo-grpc                    // [示例] Sa-Token 集成 grpc 鉴权
			├── client                                // [示例] Sa-Token 集成 grpc 鉴权,client 端
			├── server                                // [示例] Sa-Token 集成 grpc 鉴权,server 端
		├── sa-token-demo-hutool-timed-cache      // [示例] Sa-Token 集成 hutool timed-cache
		├── sa-token-demo-caffeine                // [示例] Sa-Token 集成 Caffeine
		├── sa-token-demo-jfinal                  // [示例] Sa-Token 整合 JFinal
		├── sa-token-demo-jwt                     // [示例] Sa-Token 集成 jwt 登录认证 
		├── sa-token-demo-oauth2                  // [示例] Sa-Token 集成 OAuth2.0
			├── sa-token-demo-oauth2-client           // [示例] Sa-Token 集成 OAuth2.0 (客户端)
			├── sa-token-demo-oauth2-client-h5        // [示例] Sa-Token OAuth2 前端测试页 
			├── sa-token-demo-oauth2-server           // [示例] Sa-Token 集成 OAuth2.0 (服务端)
			├── sa-token-demo-oauth2-server-h5        // [示例] Sa-Token 集成 OAuth2.0 (服务端 - 前后台分离示例)
		├── sa-token-demo-quick-login             // [示例] Sa-Token 集成 quick-login 模块 
		├── sa-token-demo-quick-login-sb3         // [示例] Sa-Token 集成 quick-login 模块 (SpringBoot3)
		├── sa-token-demo-remember-me             // [示例] Sa-Token 实现 [ 记住我 ] 模式
			├── page_project                          // [示例] Sa-Token 实现 [ 记住我 ] 模式、前端页面
			├── sa-token-demo-remember-me-server      // [示例] Sa-Token 实现 [ 记住我 ] 模式、后端接口
		├── sa-token-demo-solon                   // [示例] Sa-Token 集成 Solon 
		├── sa-token-demo-solon-reisson           // [示例] Sa-Token 集成 Solon、Reisson
		├── sa-token-demo-springboot              // [示例] Sa-Token 整合 SpringBoot 
		├── sa-token-demo-springboot3-redis       // [示例] Sa-Token 整合 SpringBoot3 整合 Redis 
		├── sa-token-demo-springboot4-redis       // [示例] Sa-Token 整合 SpringBoot4 整合 Redis 
		├── sa-token-demo-springboot-low-version  // [示例] Sa-Token 整合 SpringBoot2 低版本
		├── sa-token-demo-springboot-redis        // [示例] Sa-Token 整合 SpringBoot 整合 Redis 
		├── sa-token-demo-springboot-redisson     // [示例] Sa-Token 整合 SpringBoot 整合 redisson 
		├── sa-token-demo-sse                     // [示例] 在 SSE 中使用 Sa-Token 
		├── sa-token-demo-ssm                     // [示例] 在 SSM 中使用 Sa-Token 
		├── sa-token-demo-sso                     // [示例] Sa-Token 集成 SSO 单点登录
			├── sa-token-demo-sso-server              // [示例] Sa-Token 集成 SSO单点登录-Server认证中心
			├── sa-token-demo-sso1-client             // [示例] Sa-Token 集成 SSO单点登录-模式一 应用端 (同域、同Redis)
			├── sa-token-demo-sso2-client             // [示例] Sa-Token 集成 SSO单点登录-模式二 应用端 (跨域、同Redis)
			├── sa-token-demo-sso3-client             // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端 (跨域、跨Redis)
			├── sa-token-demo-sso3-client--sb4        // [示例] SSO 模式三 应用端 (Spring Boot 4 + RestClient)
			├── sa-token-demo-sso3-client-nosdk       // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端 (不使用sdk,纯手动对接)
			├── sa-token-demo-sso3-client-resdk       // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端 (ReSdk 模式,重写部分方法对接任意技术栈)
			├── sa-token-demo-sso3-client-anon        // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端 (匿名应用接入示例)
			├── sa-token-demo-sso-server-h5           // [示例] Sa-Token 集成 SSO单点登录-Server认证中心 (前后端分离)
			├── sa-token-demo-sso-client-h5           // [示例] Sa-Token 集成 SSO单点登录-client应用端 (前后端分离-原生h5 版本)
			├── sa-token-demo-sso-server-vue2         // [示例] Sa-Token 集成 SSO单点登录-client应用端 (前后端分离-Vue2 版本)
			├── sa-token-demo-sso-client-vue3         // [示例] Sa-Token 集成 SSO单点登录-client应用端 (前后端分离-Vue3 版本)
		├── sa-token-demo-sso-for-solon           // [示例] Sa-Token 集成 SSO 单点登录(Solon 版)
			├── sa-token-demo-sso-server-solon        // [示例] Sa-Token 集成 SSO单点登录-Server认证中心
			├── sa-token-demo-sso1-client-solon       // [示例] Sa-Token 集成 SSO单点登录-模式一 应用端 (同域、同Redis)
			├── sa-token-demo-sso2-client-solon       // [示例] Sa-Token 集成 SSO单点登录-模式二 应用端 (跨域、同Redis)
			├── sa-token-demo-sso3-client-solon       // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端 (跨域、跨Redis)
		├── sa-token-demo-test                    // [示例] Sa-Token 整合测试项目
		├── sa-token-demo-thymeleaf               // [示例] Sa-Token 集成 Thymeleaf 标签方言
		├── sa-token-demo-webflux                 // [示例] Sa-Token 整合 WebFlux 
		├── sa-token-demo-webflux-springboot3    // [示例] Sa-Token 整合 WebFlux (SpringBoot3)
		├── sa-token-demo-webflux-springboot4    // [示例] Sa-Token 整合 WebFlux (SpringBoot4)
		├── sa-token-demo-websocket               // [示例] Sa-Token 集成 Web-Socket 鉴权示例
		├── sa-token-demo-websocket-spring        // [示例] Sa-Token 集成 Web-Socket(Spring封装版) 鉴权示例
		├── sa-token-demo-loveqq-boot             // [示例] Sa-Token 集成 LoveQQ-Boot
		├── pom.xml                               // 示例 pom 文件,用于帮助在 idea 中一键导入所有 demo 
	├── sa-token-test                         // [测试] Sa-Token 单元测试合集
		├── sa-token-easy-test                  // [测试] Sa-Token 简易测试
		├── sa-token-springboot-test            // [测试] Sa-Token SpringBoot 整合测试
		├── sa-token-jwt-test                   // [测试] Sa-Token jwt 整合测试
		├── sa-token-temp-jwt-test              // [测试] Sa-Token temp-jwt 整合测试
		├── sa-token-json-test                  // [测试] Sa-Token json 序列化测试(Jackson / Snack4 白名单等)
		├── sa-token-jackson3-test              // [测试] Sa-Token Jackson3 整合测试(含 DefaultTyping 白名单)
		├── sa-token-serializer-test            // [测试] Sa-Token 序列化测试
	├── sa-token-doc                          // [文档] Sa-Token 开发文档 
	├── MEMO                                  // [备忘] 内部备忘录、开发记录
	├── pom.xml                               // [依赖] 顶级pom文件 
	├── LICENSE                               // 开源协议
	├── mvn clean.bat                         // 一键 mvn clean 核心包+所有示例包
	├── mvn test.bat                          // 一键单元测试 
	├── preview-doc.bat                       // 一键预览开发文档
	├── README.md                             // 仓库自述文件

其它(sa-tokens 组织下相关仓库):


File: sa-token-doc-new/docs/fun/async--mock.md


title: "Sa-Token 异步 & Mock 上下文" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,异步 & Mock 上下文,附录"

异步 & Mock 上下文

异步上下文

有一些方法(例如StpUtil.isLogin())只可以在同步的 Web 上下文中才可以调用,如果在异步上下文中调用则会抛出异常:

text
cn.dev33.satoken.exception.SaTokenContextException: SaTokenContext 上下文尚未初始化

这是因为这些方法需要从前端的 HttpServletRequest 中读取 Token 参数,而异步上下文通常不是一次“请求”,不具有 HttpServletRequest 的概念,所以无法成功调用。

一般哪些场景属于异步上下文?

  • 通过 new Thread(() -> { ... }).start() 启动子线程。
  • 通过 taskExecutor.execute(() -> { ... }) 线程池启动异步任务。
  • 通过 @Async 注解标注的方法。
  • 通过 @Scheduled(cron = "") 启动的定时任务。
  • 消息队列中消费消息的函数。
  • ...

凡是不通过 web 请求调用触发的线程,在 Sa-Token 中均属于异步上下文,也可以称作 “非 Web 上下文”。

此时调用 StpUtil.isLogin()、StpUtil.getLoginId() 等需要 Web 上下文的 API,就会抛出上述异常。

如果你需要在 非 Web 上下文 中调用上述 API,则需要手动 mock 一个上下文,才可以调用成功:

例如:

java
// 【异步】new Thread  
@RequestMapping("isLogin2")
public SaResult isLogin2() {
	System.out.println("是否登录:" + StpUtil.isLogin());
	String tokenValue = StpUtil.getTokenValue();
	new Thread(() -> {
		SaTokenContextMockUtil.setMockContext(()->{
			StpUtil.setTokenValueToStorage(tokenValue);
			System.out.println("是否登录:" + StpUtil.isLogin());
		});
	}).start();
	return SaResult.data(StpUtil.getTokenValue());
}

参考上述方法,你需要先调用 SaTokenContextMockUtil.setMockContext(() -> { ... }) Mock 出一个 Web 上下文填充到上下文管理器中,
然后在 Mock 上下文范围内调用 StpUtil.setTokenValueToStorage(tokenValue) 指定当前上下文的 token 值,其效果等同于在 web 上下文中前端提交了此 token 值。

更多使用姿势请参考仓库示例:Async-TestController.java

响应式上下文

在 WebFlux / Spring Cloud 等响应式环境下调用 Sa-Token 的同步 API 也有可能发生上下文异常:

text
cn.dev33.satoken.exception.SaTokenContextException: SaTokenContext 上下文尚未初始化
	at cn.dev33.satoken.context.SaTokenContextForThreadLocalStaff.getModelBox(SaTokenContextForThreadLocalStaff.java:73) ~[classes/:na]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ cn.dev33.satoken.reactor.filter.SaReactorFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ cn.dev33.satoken.reactor.filter.SaFirewallCheckFilterForReactor [DefaultWebFilterChain]
	*__checkpoint ⇢ cn.dev33.satoken.reactor.filter.SaTokenCorsFilterForReactor [DefaultWebFilterChain]
	*__checkpoint ⇢ cn.dev33.satoken.reactor.filter.SaTokenContextFilterForReactor [DefaultWebFilterChain]
	*__checkpoint ⇢ HTTP GET "/test/isLogin" [ExceptionHandlingWebHandler]

如果是在自定义 Filter 中报的这个错,需要你在调用 Sa-Token 的同步 API 之前手动 set 一下上下文:

java
// 自定义过滤器
@Component
public class MyFilter implements WebFilter {
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
		try {
			// 先 set 上下文,再调用 Sa-Token 同步 API,并在 finally 里清除上下文
			SaReactorSyncHolder.setContext(exchange);
			System.out.println(StpUtil.isLogin());
		}
		finally {
			SaReactorSyncHolder.clearContext();
		}
		return chain.filter(exchange);
	}
}

参考:MyFilter.java

在 Controller 里同理:

java
@RequestMapping("isLogin2")
public SaResult isLogin2(ServerWebExchange exchange) {
	SaResult res = SaReactorSyncHolder.setContext(exchange, ()->{
		System.out.println("是否登录:" + StpUtil.isLogin());
		return SaResult.data(StpUtil.getTokenInfo());
	});
	return SaResult.data(res);
}

更多示例请参考:
TestController.java


File: sa-token-doc-new/docs/fun/auth-flow.md


title: "Sa-Token功能结构图" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,Sa-Token功能结构图,附录"

Sa-Token 功能结构图


Sa-Token 功能结构图:

Sa-Token 认证流程图:

PS:鼠标右键选择 [在新窗口打开图片] 即可高清模式查看图片。


File: sa-token-doc-new/docs/fun/auth-framework-function-test.md


title: "Sa-Token 从 Shiro、SpringSecurity、JWT 迁移" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,从 Shiro、SpringSecurity、JWT 迁移,附录"

Java 权限认证框架功能 测试 / 对比 / 迁移。

对比以下框架的常见功能,为项目技术栈迁移提供代码示例

  • Sa-Token
  • Apache Shiro
  • Spring Security
  • JWT

::: tip 注意事项

  • 因个人精力&能力有限,本篇只展示部分常见功能的对比,也欢迎大家一起贡献案例,提交pr。
  • 代码案例仓库:https://gitee.com/sa-tokens/auth-framework-function-test
  • 注:本篇主要展示一些常见功能不同框架的实现差异,而非每个框架的所含功能点对比。
    :::

依赖引入

:::tabs
== Sa-Token

xml
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.com/ -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-spring-boot3-starter</artifactId>
	<version>1.39.0</version>
</dependency>

== Shiro

xml
<!-- Shiro 安全控制 -->
<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring-boot-web-starter</artifactId>
	<version>1.13.0</version>
</dependency>

== SpringSecurity

xml
<!-- SpringSecurity -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
	<version>3.3.2</version>
</dependency>

SpringBoot 项目下一般不用特别指定 SpringSecurity 版本号

== JWT

xml
<!-- Hutool 工具类框架,其中包含 jwt 实现 -->
<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.8.29</version>
</dependency>

:::

会话登录 & 会话状态查询

:::tabs
== Sa-Token

测试 Controller

java
@RestController
@RequestMapping("/acc/")
public class LoginController {
	
	@Autowired
	SysUserDao sysUserDao;
		
	// 测试登录 
	@RequestMapping("doLogin")
	public AjaxJson doLogin(String username, String password) {
		// 校验
		SysUser user = sysUserDao.findByUsername(username);
		if(user == null) {
			return AjaxJson.getError("用户不存在");
		}
		if(!user.getPassword().equals(password)) {
			return AjaxJson.getError("密码错误");
		}
		// 登录
		StpUtil.login(user.getId());
		StpUtil.getSession().set("user", user);
		return AjaxJson.getSuccess("登录成功");
	}
	
	// 查询登录状态 
	@RequestMapping("isLogin")
	public AjaxJson isLogin() {
		if(StpUtil.isLogin()) {
			return AjaxJson.getSuccess("已登录,账号id:" + StpUtil.getLoginId());
		}
		return AjaxJson.getError("未登录");
	}

}

== Shiro

自定义 Realm

java
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private SysUserDao sysUserDao;

    // 加载用户信息
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        String username = (String)token.getPrincipal();
        SysUser sysUser = sysUserDao.findByUsername(username);
        if(sysUser == null){
            return null;
        }
        return new SimpleAuthenticationInfo(
                sysUser,
                sysUser.getPassword(),
                getName()
        );
    }
	
    // 加载权限信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

}

Shiro 配置类

java
@Configuration
public class ShiroConfigure {

    @Bean
    public MyRealm myRealm() {
        return new MyRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        return bean;
    }

}

测试 Controller

java
@RestController
@RequestMapping("/acc/")
public class LoginController {

	// 测试登录  ---- http://localhost:8082/acc/doLogin?username=zhang&password=123456
	@RequestMapping("doLogin")
	public AjaxJson doLogin(String username, String password) {
		Subject subject = SecurityUtils.getSubject();
		try {
			subject.login(new UsernamePasswordToken(username, password));
			return AjaxJson.getSuccess("登录成功!");
		} catch (AuthenticationException e) {
			e.printStackTrace();
			return AjaxJson.getError(e.getMessage());
		}
	}

	// 查询登录状态  ---- http://localhost:8082/acc/isLogin
	@RequestMapping("isLogin")
	public AjaxJson isLogin() {
		Subject subject = SecurityUtils.getSubject();
		if(subject.isAuthenticated()) {
			SysUser sysUser = (SysUser)subject.getPrincipal();
			return AjaxJson.getSuccess("已登录,账号id:" + sysUser.getId());
		}
		return AjaxJson.getError("未登录");
	}

}

== SpringSecurity

定义 SpringSecurity 配置类

java
@Configuration
public class SpringSecurityConfigure {

    /**
     * Spring Security的核心过滤器链配置
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        // 定义安全请求拦截规则
        httpSecurity.authorizeHttpRequests(router -> {
            router
                    // 放行接口
                    .requestMatchers("/acc/doLogin", "/acc/isLogin").permitAll()

					// 所有请求都需要认证
                    .anyRequest().authenticated(); 
                    ;
                });

        // 默认的表单登录
        httpSecurity.formLogin(withDefaults());

        // 是否启用 csrf 防御
        httpSecurity.csrf( csrf -> csrf.disable() );

        // 一些安全相关的全局响应头
        httpSecurity.headers(httpSecurityHeadersConfigurer -> {
            httpSecurityHeadersConfigurer.cacheControl(HeadersConfigurer.CacheControlConfig::disable);
            httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable);
        });
		
        return httpSecurity.build();
    }

    /**
     * Spring Security 认证管理器
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

}

定义 SpringSecurity UserDetails 管理器

java
/**
 * 自定义 SpringSecurity UserDetails 管理器
 *
 * @author click33
 * @since 2024/8/8
 */
@Component
public class CustomUserDetailsManager implements UserDetailsManager {

    @Autowired
    SysUserDao sysUserDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserDao.findByUsername(username);
        if(sysUser == null){
            throw new UsernameNotFoundException("用户不存在");
        }
        return User.withUsername(sysUser.getUsername())
                .password("{noop}" + sysUser.getPassword())
                .build();
    }

    @Override
    public void createUser(UserDetails user) {

    }

    @Override
    public void updateUser(UserDetails user) {

    }

    @Override
    public void deleteUser(String username) {

    }

    @Override
    public void changePassword(String oldPassword, String newPassword) {

    }

    @Override
    public boolean userExists(String username) {
        return false;
    }

}

测试 Controller

java
@RestController
@RequestMapping("/acc/")
public class LoginController {

	@Autowired
	AuthenticationManager authenticationManager;

	@Autowired
	SysUserDao sysUserDao;

	// 测试登录  ---- http://localhost:8083/acc/doLogin?username=zhang&password=123456
	@RequestMapping("doLogin")
	public AjaxJson doLogin(String username, String password, HttpServletRequest request) {
		try {
			// 验证账号密码
			UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
			usernamePasswordAuthenticationToken.setDetails(sysUserDao.findByUsername(username));
			Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
			// 存入上下文
			SecurityContextHolder.getContext().setAuthentication(authentication);
			request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
			// 返回
			return AjaxJson.getSuccess("登录成功!");
		} catch (Exception e) {
			e.printStackTrace();
			return AjaxJson.getError(e.getMessage());
		}
	}

	// 查询登录状态  ---- http://localhost:8083/acc/isLogin
	@RequestMapping("isLogin")
	public AjaxJson isLogin() {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		return AjaxJson.getSuccess("是否登录:" + !(authentication instanceof AnonymousAuthenticationToken))
				.set("principal", authentication.getPrincipal())
				.set("details", authentication.getDetails());
	}

}

== JWT

测试 Controller

java
@RestController
@RequestMapping("/acc/")
public class LoginController {

	@Autowired
	SysUserDao sysUserDao;

	// 测试登录 
	@RequestMapping("doLogin")
	public AjaxJson doLogin(String username, String password) {
		// 校验
		SysUser user = sysUserDao.findByUsername(username);
		if(user == null) {
			return AjaxJson.getError("用户不存在");
		}
		if(!user.getPassword().equals(password)) {
			return AjaxJson.getError("密码错误");
		}
		// 登录
		String token = JwtUtil.createToken(user.getId(), user, 60 * 60 * 2);
		return AjaxJson.getSuccess("登录成功").set("token", token);
	}

	// 查询登录状态 
	@RequestMapping("isLogin")
	public AjaxJson isLogin(HttpServletRequest request) {
		try{
			String token = request.getHeader("token");
			JWT jwt = JwtUtil.parseToken(token);
			return AjaxJson.getSuccess("已登录")
					.set("id", jwt.getPayload("userId"))
					.set("user", jwt.getPayload("user"));
		} catch (Exception e) {
			e.printStackTrace();
			return AjaxJson.getError("未登录");
		}
	}

}

:::

会话注销

:::tabs
== Sa-Token

java
@RequestMapping("logout")
public AjaxJson logout() {
	StpUtil.logout();
	return AjaxJson.getSuccess("注销成功");
}

== Shiro

java
@RequestMapping("logout")
public AjaxJson logout() {
	SecurityUtils.getSubject().logout();
	return AjaxJson.getSuccess("注销成功");
}

== SpringSecurity

java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
	
	// 其它配置 ...
	
	// 注销相关配置
	httpSecurity.logout(logout -> {
		logout.logoutUrl("/acc/logout");
		logout.logoutSuccessHandler((request, response, authentication) -> {
			response.setStatus(200);
			response.setCharacterEncoding("UTF-8");
			response.setContentType("application/json; charset=utf-8");
			String jsonStr = new ObjectMapper().writeValueAsString(AjaxJson.getSuccess("注销成功!"));
			response.getWriter().write(jsonStr);
		});
	});

	return httpSecurity.build();
}

== JWT

JWT 无法注销已经颁发的 token 。

:::

账号密码登录(MD5 加 salt)

:::tabs
== Sa-Token

测试 Controller

java
@RequestMapping("doLogin")
public AjaxJson doLogin(String username, String password) {
	// 校验
	SysUser user = sysUserDao.findByUsername(username);
	if(user == null) {
		return AjaxJson.getError("用户不存在");
	}
	String salt = "abc";
	if(!user.getPassword().equals(SaSecureUtil.md5(salt + password))) {
		return AjaxJson.getError("密码错误");
	}
	// 登录
	StpUtil.login(user.getId());
	StpUtil.getSession().set("user", user);
	return AjaxJson.getSuccess("登录成功");
}

== Shiro

自定义 Realm Bean 设定密码凭证器

java
@Bean
public MyRealm myRealm() {
	MyRealm realm = new MyRealm();
	// 设定凭证匹配器
	HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
	credentialsMatcher.setHashAlgorithmName("md5");
	realm.setCredentialsMatcher(credentialsMatcher);
	// 返回
	return realm;
}

自定义 Realm 实现类 doGetAuthenticationInfo 方法返回 slat 信息

java
// 加载用户信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
	String username = (String)token.getPrincipal();
	SysUser sysUser = sysUserDao.findByUsername(username);
	if(sysUser == null){
		return null;
	}

	return new SimpleAuthenticationInfo(
			sysUser,
			sysUser.getPassword(),
			ByteSource.Util.bytes("abc"), // 指定 slat 信息 
			getName()
	);
}

登录代码照旧

== SpringSecurity

CustomUserDetailsManager 的 loadUserByUsername 指定 MD5 算法

java
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
	SysUser sysUser = sysUserDao.findByUsername(username);
	if(sysUser == null){
		throw new UsernameNotFoundException("用户不存在");
	}
	return User.withUsername(sysUser.getUsername())
			.password("{MD5}" + sysUser.getPassword())
			.build();
}

登录时指定 salt

java
@RequestMapping("doLogin")
public AjaxJson doLogin(String username, String password, HttpServletRequest request) {
	try {
		// 验证账号密码
		String salt = "abc";
		UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, salt + password);
		// 其它代码照旧 ... 
		
		// 返回
		return AjaxJson.getSuccess("登录成功!");
	} catch (Exception e) {
		e.printStackTrace();
		return AjaxJson.getError(e.getMessage());
	}
}

== JWT

测试 Controller

java
@RequestMapping("doLogin")
public AjaxJson doLogin(String username, String password) {
	// 校验
	SysUser user = sysUserDao.findByUsername(username);
	if(user == null) {
		return AjaxJson.getError("用户不存在");
	}
	String salt = "abc";
	if(!user.getPassword().equals(SecureUtil.md5(salt + password))) {
		return AjaxJson.getError("密码错误");
	}
	// 登录
	String token = JwtUtil.createToken(user.getId(), user, 60 * 60 * 2);
	return AjaxJson.getSuccess("登录成功").set("token", token);
}

:::

从上下文获取当前登录 User 信息

:::tabs
== Sa-Token

java
// 从上下文获取当前登录 User 信息 
@RequestMapping("getCurrUser")
public AjaxJson getCurrUser() {
	return AjaxJson.getSuccess()
			.set("id", StpUtil.getLoginId())
			.set("user", StpUtil.getSession().get("user"));
}

== Shiro

java
// 从上下文获取当前登录 User 信息 
@RequestMapping("getCurrUser")
public AjaxJson getCurrUser() {
	Subject subject = SecurityUtils.getSubject();
	SysUser sysUser = (SysUser)subject.getPrincipal();
	return AjaxJson.getSuccess()
			.set("id", sysUser.getId())
			.set("user", sysUser);
}

== SpringSecurity

java
// 从上下文获取当前登录 User 信息 
@RequestMapping("getCurrUser")
public AjaxJson getCurrUser() {
	Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
	if(!(authentication instanceof AnonymousAuthenticationToken)) {
		SysUser sysUser = (SysUser)authentication.getDetails();
		return AjaxJson.getSuccess()
						.set("id", sysUser.getId())
						.set("user", sysUser);
	}
	return AjaxJson.getError("未登录");
}

== JWT

java
// 从上下文获取当前登录 User 信息 
@RequestMapping("getCurrUser")
public AjaxJson getCurrUser(HttpServletRequest request) {
	try{
		String token = request.getHeader("token");
		JWT jwt = JwtUtil.parseToken(token);
		SysUser sysUser = jwt.getPayloads().get("user", SysUser.class);
		return AjaxJson.getSuccessData(sysUser);
	} catch (Exception e) {
		e.printStackTrace();
		return AjaxJson.getError("未登录");
	}
}

:::

从会话上下文上存取值

:::tabs
== Sa-Token

java
// 测试从从会话上下文存取值 
@RequestMapping("testSession")
public AjaxJson test() {
	SaSession session = StpUtil.getSession();

	System.out.println("从 session 上取值:" + session.get("name"));
	session.set("name", "zhang");
	System.out.println("从 session 上取值:" + session.get("name"));

	return AjaxJson.getSuccess();
}

== Shiro

java
// 测试从从会话上下文存取值
@RequestMapping("testSession")
public AjaxJson test() {
	Subject subject = SecurityUtils.getSubject();
	Session session = subject.getSession();

	System.out.println("从 session 上取值:" + session.getAttribute("name"));
	session.setAttribute("name", "zhang");
	System.out.println("从 session 上取值:" + session.getAttribute("name"));

	return AjaxJson.getSuccess();
}

== SpringSecurity

java
// 测试从从会话上下文存取值
@RequestMapping("testSession")
public AjaxJson testSession(HttpServletRequest request) {
	HttpSession session = request.getSession();

	System.out.println("从 session 上取值:" + session.getAttribute("name"));
	session.setAttribute("name", "zhang");
	System.out.println("从 session 上取值:" + session.getAttribute("name"));
	return AjaxJson.getSuccess();
}

== JWT

无

:::

角色认证 & 权限认证

:::tabs
== Sa-Token

自定义 StpInterface 实现类

java
@Component
public class StpInterfaceImpl implements StpInterface {

	// 加载角色信息
	@Override
	public List<String> getRoleList(Object loginId, String loginType) {
		return Arrays.asList("admin", "super-admin", "ceo");
	}

	// 加载权限信息
	@Override
	public List<String> getPermissionList(Object loginId, String loginType) {
		return Arrays.asList("user:add", "user:delete", "user:update");
	}

}

测试 Controller

java
@RestController
@RequestMapping("/jur/")
public class JurController {

	// 角色判断  ---- http://localhost:8082/jur/assertRole
	@RequestMapping("assertRole")
	public AjaxJson assertRole() {
		// is 模式,返回 true 或 false
		System.out.println("单个角色判断:" + StpUtil.hasRole("admin"));
		System.out.println("多个角色判断(and):" + StpUtil.hasRoleAnd("admin", "dev-admin"));
		System.out.println("多个角色判断(or):" + StpUtil.hasRoleOr("admin", "dev-admin"));

		// check 模式,无角色时抛出异常
		StpUtil.checkRole("admin");  // 单个 check
		StpUtil.checkRoleAnd("admin", "dev-admin"); // 多个 check (and)
		StpUtil.checkRoleOr("admin", "dev-admin"); // 多个 check (or)

		return AjaxJson.getSuccess();
	}

	// 权限判断  ---- http://localhost:8082/jur/assertPermission
	@RequestMapping("assertPermission")
	public AjaxJson assertPermission() {
		// is 模式,返回 true 或 false
		System.out.println("单个权限判断:" + StpUtil.hasPermission("user:add"));
		System.out.println("多个权限判断(and):" + StpUtil.hasPermissionAnd("user:add", "user:delete22"));
		System.out.println("多个权限判断(or):" + StpUtil.hasPermissionOr("user:add", "user:delete22"));

		// check 模式,无权限时抛出异常
		StpUtil.checkPermission("user:add");  // 单个 check
		StpUtil.checkPermissionAnd("user:add", "user:delete22"); // 多个 check (and)
		StpUtil.checkPermissionOr("user:add", "user:delete22"); // 多个 check (or)

		return AjaxJson.getSuccess();
	}

}

== Shiro

自定义 Realm 里重写方法 doGetAuthorizationInfo

java
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
	SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
	// 加载角色信息
	authorizationInfo.addRoles(Arrays.asList("admin", "super-admin", "ceo"));
	// 加载权限信息
	authorizationInfo.addStringPermissions(Arrays.asList("user:add", "user:delete", "user:update"));
	return authorizationInfo;
}

测试 Controller

java
@RestController
@RequestMapping("/jur/")
public class JurController {

	// 角色判断 
	@RequestMapping("assertRole")
	public AjaxJson assertRole() {
		Subject subject = SecurityUtils.getSubject();

		// is 模式,返回 true 或 false
		System.out.println("单个角色判断:" + subject.hasRole("admin"));
		System.out.println("多个角色判断(and):" + subject.hasAllRoles(Arrays.asList("admin", "dev-admin")));
		System.out.println("多个角色判断(or):" + (subject.hasRole("admin") || subject.hasRole("dev-admin")));

		// check 模式,无角色时抛出异常
		subject.checkRole("admin");  // 单个 check
		subject.checkRoles("admin", "dev-admin"); // 多个 check (and)

		return AjaxJson.getSuccess();
	}

	// 权限判断 
	@RequestMapping("assertPermission")
	public AjaxJson assertPermission() {
		Subject subject = SecurityUtils.getSubject();

		// is 模式,返回 true 或 false
		System.out.println("单个权限判断:" + subject.isPermitted("user:add"));
		System.out.println("多个权限判断(and):" + subject.isPermittedAll("user:add", "user:delete22"));
		System.out.println("多个权限判断(or):" + (subject.isPermitted("user:add") || subject.isPermitted("user:delete22")));

		// check 模式,无权限时抛出异常
		subject.checkPermission("user:add");  // 单个 check
		subject.checkPermissions("user:add", "user:delete22"); // 多个 check (and)

		return AjaxJson.getSuccess();
	}

}

== SpringSecurity

CustomUserDetailsManager 的 loadUserByUsername 里返回用户的 角色 或 权限 信息

java
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
	SysUser sysUser = sysUserDao.findByUsername(username);
	if(sysUser == null){
		throw new UsernameNotFoundException("用户不存在");
	}

    // 不可以同时返回 roles 和 authorities,因为会相互覆盖,SpringSecurity 源码有bug
	return User.withUsername(sysUser.getUsername())
			.password("{noop}" + sysUser.getPassword())
			// .roles("admin", "super-admin", "ceo")
			.authorities("user:add", "user:delete", "user:update")
			.build();
}

测试 Controller

java
@RestController
@RequestMapping("/jur/")
public class JurController {

	// 角色判断  
	@RequestMapping("assertRole")
	public AjaxJson assertRole() {
		SecurityExpressionRoot securityExpressionRoot = new SecurityExpressionRoot(SecurityContextHolder.getContext().getAuthentication()) {};

		System.out.println("单个角色判断:" + securityExpressionRoot.hasRole("admin"));
		System.out.println("多个角色判断(and):" + (securityExpressionRoot.hasRole("admin") && securityExpressionRoot.hasRole("dev-admin")));
		System.out.println("多个角色判断(or):" + securityExpressionRoot.hasAnyRole("admin", "dev-admin"));

		return AjaxJson.getSuccess();
	}

	// 权限判断 
	@RequestMapping("assertPermission")
	public AjaxJson assertPermission() {
		SecurityExpressionRoot securityExpressionRoot = new SecurityExpressionRoot(SecurityContextHolder.getContext().getAuthentication()) {};

		System.out.println("单个权限判断:" + securityExpressionRoot.hasAuthority("user:add"));
		System.out.println("多个权限判断(and):" + (securityExpressionRoot.hasAuthority("user:add") && securityExpressionRoot.hasAuthority("user:delete2")));
		System.out.println("多个权限判断(or):" + securityExpressionRoot.hasAnyAuthority("user:add", "user:delete2"));

		return AjaxJson.getSuccess();
	}

}

== JWT

无

:::

注解鉴权

:::tabs
== Sa-Token

SaTokenConfigure 配置注解拦截器

java
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
	}
}

测试 Controller

java
@RestController
@RequestMapping("/at-check/")
public class AtCheckController {

    // 登录校验 
    @SaCheckLogin
    @RequestMapping("checkLogin")
    public AjaxJson checkLogin() {
        return AjaxJson.getSuccess();
    }

    // 角色校验 
    @SaCheckRole("admin")
    @RequestMapping("checkRole")
    public AjaxJson checkRole() {
        return AjaxJson.getSuccess();
    }

    // 权限校验 
    @SaCheckPermission("user:add")
    @RequestMapping("checkPermission")
    public AjaxJson checkPermission() {
        return AjaxJson.getSuccess();
    }

    // 忽略认证校验 
    @SaIgnore
    @SaCheckLogin
    @RequestMapping("ignoreCheck")
    public AjaxJson ignoreCheck() {
        return AjaxJson.getSuccess();
    }

}

== Shiro

测试 Controller

java
@RestController
@RequestMapping("/at-check/")
public class AtCheckController {

    // 登录校验 
    @RequiresAuthentication
    @RequestMapping("checkLogin")
    public AjaxJson checkLogin() {
        return AjaxJson.getSuccess();
    }

    // 角色校验  
    @RequiresRoles("admin")
    @RequestMapping("checkRole")
    public AjaxJson checkRole() {
        return AjaxJson.getSuccess();
    }

    // 权限校验 
    @RequiresPermissions("user:add")
    @RequestMapping("checkPermission")
    public AjaxJson checkPermission() {
        return AjaxJson.getSuccess();
    }

}

== SpringSecurity

SpringSecurityConfigure 配置类加上 @EnableMethodSecurity 注解

java
@Configuration
@EnableMethodSecurity
public class SpringSecurityConfigure {
	// ...
}

测试 Controller

java
@RestController
@RequestMapping("/at-check/")
public class AtCheckController {

    // 登录校验  
    @PreAuthorize("isAuthenticated()")
    @RequestMapping("checkLogin")
    public AjaxJson checkLogin() {
        return AjaxJson.getSuccess();
    }

    // 角色校验  
    @PreAuthorize("hasRole('admin')")
    @RequestMapping("checkRole")
    public AjaxJson checkRole() {
        return AjaxJson.getSuccess();
    }

    // 权限校验 
    @PreAuthorize("hasAuthority('user:add')")
    @RequestMapping("checkPermission")
    public AjaxJson checkPermission() {
        return AjaxJson.getSuccess();
    }

}

== JWT

无

:::

路由拦截鉴权

:::tabs
== Sa-Token

SaTokenConfigure 配置

java
@Override
public void addInterceptors(InterceptorRegistry registry) {
	// 注册 Sa-Token 拦截器打开注解鉴权功能 
	registry.addInterceptor(new SaInterceptor(handle -> {
		SaRouter.match("/route-check/getInfo1").stop(); // 不拦截
		SaRouter.match("/route-check/getInfo2").check(r -> StpUtil.checkLogin()); // 需要登录
		SaRouter.match("/route-check/getInfo3").check(r -> StpUtil.checkRole("admin2")); // 需要角色
		SaRouter.match("/route-check/getInfo4").check(r -> StpUtil.checkPermission("user:add3")); // 需要权限
	})).addPathPatterns("/**");
}

== Shiro

过滤器配置

java
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
	ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
	bean.setSecurityManager(securityManager());

	// 路由拦截鉴权
	Map<String,String> filterMap = new LinkedHashMap<>();
	filterMap.put("/route-check/getInfo", "anon"); // 不拦截
	filterMap.put("/route-check/getInfo2", "authc"); // 需要登录
	filterMap.put("/route-check/getInfo3", "perms[admin2]"); // 需要角色
	filterMap.put("/route-check/getInfo4", "perms[user:add3]"); // 需要权限
	bean.setFilterChainDefinitionMap(filterMap);
	bean.setLoginUrl("/401");  // 未登录时跳转的 url
	bean.setUnauthorizedUrl("/403");  // 未授权时跳转的 url

	return bean;
}

== SpringSecurity

SpringSecurityConfigure 配置

java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
	// 定义安全请求拦截规则
	httpSecurity.authorizeHttpRequests(router -> {
		router
			.requestMatchers("/route-check/getInfo1").permitAll()    // 不拦截
			.requestMatchers("/route-check/getInfo2").authenticated()    // 需要登录
			.requestMatchers("/route-check/getInfo3").hasRole("admin")    // 需要 admin 角色
			.requestMatchers("/route-check/getInfo4").hasAuthority("user:add")    // 需要 user:add 权限
			.anyRequest().permitAll(); // 所有请求都放行
		});

	return httpSecurity.build();
}

== JWT

无

:::

鉴权未通过的处理方案

:::tabs
== Sa-Token

定义全局异常处理类

java
@RestControllerAdvice
public class GlobalException {

	@ExceptionHandler(NotLoginException.class)
	public AjaxJson handlerException(NotLoginException e) {
		return AjaxJson.get(401, "未登录");
	}

	@ExceptionHandler(NotRoleException.class)
	public AjaxJson handlerException(NotRoleException e) {
		return AjaxJson.get(403, "缺少角色:" + e.getRole());
	}

	@ExceptionHandler(NotPermissionException.class)
	public AjaxJson handlerException(NotPermissionException e) {
		return AjaxJson.get(403, "缺少权限:" + e.getPermission());
	}

}

== Shiro

过滤器配置

java
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
	ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
	
	// ... 
	
	bean.setLoginUrl("/401");  // 未登录时跳转的 url
	bean.setUnauthorizedUrl("/403");  // 未授权时跳转的 url

	return bean;
}

定义路由

java
@RestController
public class ShiroErrorController {

    @RequestMapping("/401")
    public Object error401(HttpServletRequest request, HttpServletResponse response) {
        response.setStatus(200);
        return AjaxJson.get(401, "not login");
    }

    @RequestMapping("/403")
    public Object error403(HttpServletRequest request, HttpServletResponse response) {
        response.setStatus(200);
        return AjaxJson.get(403, "鉴权未通过");
    }

}

== SpringSecurity

实现 AccessDeniedHandler, AuthenticationEntryPoint 接口

java
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler, AuthenticationEntryPoint, Serializable {

    // 未登录异常
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        //验证为未登陆状态会进入此方法,认证错误
        response.setStatus(401);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter printWriter = response.getWriter();
        String body = "请先进行登录";
        printWriter.write(body);
        printWriter.flush();
    }

    // 权限不足
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        // 登陆状态下,权限不足执行该方法
        response.setStatus(200);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter printWriter = response.getWriter();
        String body = "权限不足";
        printWriter.write(body);
        printWriter.flush();
    }
}

注入 SecurityFilterChain

java
// 未登录处理逻辑、权限不足处理逻辑
@Autowired
private CustomAccessDeniedHandler accessDeniedHandler;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
	
	// 异常处理
	httpSecurity.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {
		// 权限不足处理方案
		httpSecurityExceptionHandlingConfigurer.accessDeniedHandler(accessDeniedHandler);
		// 未登录 处理逻辑
		httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint(accessDeniedHandler);
	});

	return httpSecurity.build();
}

== JWT

使用 try-catch 捕获,或定义全局异常处理

java
@RestControllerAdvice
public class GlobalException {
	// 全局异常拦截(拦截项目中的所有异常)
	@ExceptionHandler
	public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) {

		// 打印堆栈,以供调试
		System.out.println("全局异常---------------");
		e.printStackTrace();

		// 返回给前端
		return AjaxJson.getError(e.getMessage());
	}
}

:::

和 Thymeleaf 集成

:::tabs
== Sa-Token

pom.xml 依赖

xml
<!-- 在 thymeleaf 标签中使用 Sa-Token -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-dialect-thymeleaf</artifactId>
	<version>1.39.0</version>
</dependency>

SaTokenConfigure 增加配置 Sa-Token 标签方言对象

java
// Sa-Token 标签方言 (Thymeleaf版)
@Bean
public SaTokenDialect getSaTokenDialect() {
	return new SaTokenDialect();
}

新建 ThymeleafConfigure 注入全局变量

java
@Configuration
public class ThymeleafConfigure {
    // 为 Thymeleaf 注入全局变量,以便在页面中调用 Sa-Token 的方法
    @Autowired
    public void configureThymeleafStaticVars(ThymeleafViewResolver viewResolver) {
        viewResolver.addStaticVariable("stp", StpUtil.stpLogic);
    }
}

新建 Controller

java
@Controller
public class HomeController {
    @RequestMapping("/")
    public Object index(HttpServletRequest request) {
        request.setAttribute("isLogin", StpUtil.isLogin());
        return new ModelAndView("index.html");
    }
}

新建 templates/index.html

html
<!DOCTYPE html>
<html lang="zh" xmlns:sa="http://www.thymeleaf.org/extras/sa-token">
<head>
    <title>Sa-Token 集成 Thymeleaf 标签方言</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
</head>
<body>
<div class="view-box" style="padding: 30px;">
    <h2>Sa-Token 集成 Thymeleaf 标签方言 —— 测试页面</h2>
    <p>当前是否登录:<span th:text="${stp.isLogin()}"></span></p>
    <p>
        <a href="login" target="_blank">登录</a>
        <a href="logout" target="_blank">注销</a>
    </p>

    <p>登录之后才能显示:<span sa:login>value</span></p>
    <p>不登录才能显示:<span sa:notLogin>value</span></p>

    <p>具有角色 admin 才能显示:<span sa:hasRole="admin">value</span></p>
    <p>同时具备多个角色才能显示:<span sa:hasRoleAnd="admin, ceo, cto">value</span></p>
    <p>只要具有其中一个角色就能显示:<span sa:hasRoleOr="admin, ceo, cto">value</span></p>
    <p>不具有角色 admin 才能显示:<span sa:notRole="admin">value</span></p>

    <p>具有权限 user-add 才能显示:<span sa:hasPermission="user-add">value</span></p>
    <p>同时具备多个权限才能显示:<span sa:hasPermissionAnd="user-add, user-delete, user-get">value</span></p>
    <p>只要具有其中一个权限就能显示:<span sa:hasPermissionOr="user-add, user-delete, user-get">value</span></p>
    <p>不具有权限 user-add 才能显示:<span sa:notPermission="user-add">value</span></p>

    <p th:if="${stp.isLogin()}">
        从SaSession中取值:
        <span th:text="${stp.getSession().get('name', '')}"></span>
    </p>

</div>
</body>
</html>

== Shiro

pom.xml 依赖

xml
<!-- Shiro 整合 Thymeleaf 依赖 -->
<dependency>
	<groupId>com.github.theborakompanioni</groupId>
	<artifactId>thymeleaf-extras-shiro</artifactId>
	<version>2.1.0</version>
</dependency>

ShiroConfigure 增加配置 Shiro 方言对象

java
@Bean
public ShiroDialect shiroDialect() {
	return new ShiroDialect();
}

新建 Controller

java
@Controller
public class HomeController {
    @RequestMapping("/")
    public Object index(HttpServletRequest request) {
        Subject subject = SecurityUtils.getSubject();
        request.setAttribute("isLogin", subject.isAuthenticated());
        return new ModelAndView("index.html");
    }
}

新建 templates/index.html

html
<!DOCTYPE html>
<html lang="zh" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <title>Shiro 集成 Thymeleaf 标签方言</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
</head>
<body>
<div class="view-box" style="padding: 30px;">
    <h2>Shiro 集成 Thymeleaf 标签方言 —— 测试页面</h2>
    <p>当前是否登录:<span th:text="${isLogin}"></span></p>
    <p>
        <a href="/acc/doLogin?username=zhang&password=123456" target="_blank">登录</a>
        <a href="/acc/logout" target="_blank">注销</a>
    </p>
    <p>登录之后才能显示:<span shiro:authenticated>value</span></p>
    <p>不登录才能显示:<span shiro:guest >value</span></p>

    <p>具有角色 admin 才能显示:<span shiro:hasRole="admin">value</span></p>
    <p>同时具备多个角色才能显示:<span shiro:hasAllRoles="admin, ceo, cto">value</span></p>
    <p>只要具有其中一个角色就能显示:<span shiro:hasAnyRoles="admin, ceo, cto">value</span></p>
    <p>不具有角色 admin 才能显示:<span shiro:lacksRole="admin">value</span></p>

    <p>具有权限 user-add 才能显示:<span shiro:hasPermission="user-add">value</span></p>
    <p>同时具备多个权限才能显示:<span shiro:hasAllPermissions="user-add, user-delete, user-get">value</span></p>
    <p>只要具有其中一个权限就能显示:<span shiro:hasAnyPermissions="user-add, user-delete, user-get">value</span></p>
    <p>不具有权限 user-add 才能显示:<span shiro:lacksPermission="user-add">value</span></p>

    <p shiro:authenticated>
      当前登录账号:<span shiro:principal></span>
    </p>

</div>
</body>
</html>

== SpringSecurity

pom.xml 引入依赖

xml
<!-- SpringSecurity 整合 Thymeleaf 依赖 -->
<dependency>
	<groupId>org.thymeleaf.extras</groupId>
	<artifactId>thymeleaf-extras-springsecurity6</artifactId>
	<version>3.1.2.RELEASE</version>
</dependency>

新建 Controller

java
@RestController
public class HomeController {
    // 首页  
    @RequestMapping("/")
    public Object index(HttpServletRequest request) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        request.setAttribute("isLogin", !(authentication instanceof AnonymousAuthenticationToken));
        return new ModelAndView("index.html");
    }
}

新建 templates/index.html

html
<!DOCTYPE html>
<html lang="zh" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
    <title>Shiro 集成 Thymeleaf 标签方言</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
</head>
<body>
<div class="view-box" style="padding: 30px;">
    <h2>Shiro 集成 Thymeleaf 标签方言 —— 测试页面</h2>
    <p>当前是否登录:<span th:text="${isLogin}"></span></p>

    <p>
        <a href="/acc/doLogin?username=zhang&password=123456" target="_blank">登录</a>
        <a href="/acc/logout" target="_blank">注销</a>
    </p>
    <p>登录之后才能显示:<span sec:authorize="isAuthenticated()">value</span></p>
    <p>不登录才能显示:<span sec:authorize="!isAuthenticated()" >value</span></p>

    <p>具有角色 admin 才能显示:<span sec:authorize="hasRole('admin')">value</span></p>
    <p>同时具备多个角色才能显示:<span sec:authorize="hasRole('admin') && hasRole('ceo') && hasRole('cto')">value</span></p>
    <p>只要具有其中一个角色就能显示:<span sec:authorize="hasAnyRole('admin', 'ceo', 'cto')">value</span></p>
    <p>不具有角色 admin 才能显示:<span sec:authorize="!hasRole('admin')">value</span></p>

    <p>具有权限 user-add 才能显示:<span sec:authorize="hasAuthority('user-add')">value</span></p>
    <p>同时具备多个权限才能显示:<span sec:authorize="hasAuthority('user-add') && hasAuthority('user-delete') && hasAuthority('user-get')">value</span></p>
    <p>只要具有其中一个权限就能显示:<span sec:authorize="hasAnyAuthority('user-add', 'user-delete', 'user-get')">value</span></p>
    <p>不具有权限 user-add 才能显示:<span sec:authorize="!hasAuthority('user-add')">value</span></p>

    <p sec:authorize="isAuthenticated()">
        当前登录账号:<span sec:authentication="details"></span>
    </p>

</div>
</body>
</html>

== JWT

无

:::

前后端分离

:::tabs
== Sa-Token

1、在登录时,将 token 信息返回到前端

java
// 测试登录 
@RequestMapping("doLogin")
public AjaxJson doLogin(String username, String password) {
	// 校验
	SysUser user = sysUserDao.findByUsername(username);
	// user 信息校验代码不再赘述 ... 
	
	// 登录
	StpUtil.login(user.getId());
	StpUtil.getSession().set("user", user);
	return AjaxJson.getSuccess("登录成功").set("satoken", StpUtil.getTokenValue());  // ⚠️ 关键代码 
}

2、前端改造

  • 1、在登录请求时,将返回的 token 保存到本地 localStorage.setItem('satoken', res.satoken)。
  • 2、在后续每次请求中,读取本地保存的 satoken 塞到请求 header 中
js
const header = {};
if(localStorage.satoken) {
	header.satoken = localStorage.satoken;
}
// 后续提交请求...

== Shiro

1、自定义 SessionManager,从请求 header 里读取前端提交的 token

java
public class MySessionManager extends DefaultWebSessionManager {

    private static final String TOKEN = "token";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    public MySessionManager() {
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader(TOKEN);
        // 如果请求头中有 token 则其值为sessionId
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }
}

2、注入到 SecurityManager 中

java
@Configuration
public class ShiroConfigure {

	// 省略其它次要代码 ... 

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        manager.setSessionManager(sessionManager());
        return manager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        return bean;
    }

    // 自定义sessionManager
    @Bean
    public SessionManager sessionManager() {
        MySessionManager mySessionManager = new MySessionManager();
        return mySessionManager;
    }
}

3、测试 Controller,登录时将 token 信息返回到前端

java
// 测试登录 
@RequestMapping("doLogin")
public AjaxJson doLogin(String username, String password) {
	Subject subject = SecurityUtils.getSubject();
	try {
		subject.login(new UsernamePasswordToken(username, password));
		String token = subject.getSession().getId().toString();    // ⚠️ 关键代码
		return AjaxJson.getSuccess("登录成功!").set("token", token);    // ⚠️ 关键代码
	} catch (AuthenticationException e) {
		e.printStackTrace();
		return AjaxJson.getError(e.getMessage());
	}
}

4、前端改造

  • 1、在登录请求时,将返回的 token 保存到本地 localStorage.setItem('token', res.token)。
  • 2、在后续每次请求中,读取本地保存的 token 塞到请求 header 中
js
const header = {};
if(localStorage.token) {
	header.token = localStorage.token;
}
// 后续提交请求...

== SpringSecurity

见下方 “集成 Redis” 部分,同时做到:集成 Redis + 前后端分离。

== JWT

JWT 不依赖 Cookie 保存/传输 token,因此无需特殊定制即可原生支持前后端分离模式。

:::

集成 Redis

:::tabs
== Sa-Token

pom.xml 引入依赖

xml
<!-- Sa-Token 整合 RedisTemplate -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-redis-template</artifactId>
	<version>1.39.0</version>
</dependency>

<!-- 提供Redis连接池 -->
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-pool2</artifactId>
</dependency>

application.yml 新增连接配置

yaml
spring:
    data:
        # redis配置
        redis:
            # Redis数据库索引(默认为0)
            database: 1
            # Redis服务器地址
            host: 127.0.0.1
            # Redis服务器连接端口
            port: 6379
            # Redis服务器连接密码(默认为空)
            password:
            # 连接超时时间
            timeout: 10s
            lettuce:
                pool:
                    # 连接池最大连接数
                    max-active: 200
                    # 连接池最大阻塞等待时间(使用负值表示没有限制)
                    max-wait: -1ms
                    # 连接池中的最大空闲连接
                    max-idle: 10
                    # 连接池中的最小空闲连接
                    min-idle: 0

其它代码照旧

== Shiro

pom.xml 引入依赖

xml
<!-- Shiro 集成 Redis -->
<dependency>
	<groupId>org.crazycake</groupId>
	<artifactId>shiro-redis</artifactId>
	<version>3.3.1</version>
</dependency>

application.yml 新增连接配置

yaml
spring:
    redis:
        shiro:
            # Redis服务器地址
            host: 127.0.0.1:6379
            # Redis服务器连接密码(默认为空)
            password:
            # Redis数据库索引(默认为0)
            database: 2
            # 连接超时时间
            timeout: 1800

ShiroConfigure 注入相关 Bean

java
@Configuration
public class ShiroConfigure {

    // 自定义 securityManager
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // manager.setRealm(myRealm());

        // 自定义session管理 使用redis
        manager.setSessionManager(sessionManager());
        // 自定义缓存实现 使用redis
        manager.setCacheManager(cacheManager());

        return manager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        return bean;
    }

    // -------- 以下为 shiro redis 相关 --------

    // Shiro redis 连接信息
    @Value("${spring.redis.shiro.host}")
    private String host;
    @Value("${spring.redis.shiro.database}")
    private int database;
    @Value("${spring.redis.shiro.timeout}")
    private int timeout;
    @Value("${spring.redis.shiro.password}")
    private String password;

    /**
     * 配置shiro redisManager
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        if(StringUtils.hasText(password)){
            redisManager.setPassword(password);
        }
        redisManager.setDatabase(database);
        redisManager.setTimeout(timeout);
        return redisManager;
    }

    /**
     * cacheManager 缓存 redis 实现
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    /**
     * RedisSessionDAO redis 实现
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    // 自定义sessionManager
    @Bean
    public SessionManager sessionManager() {
        MySessionManager mySessionManager = new MySessionManager();
        mySessionManager.setSessionDAO(redisSessionDAO());
        return mySessionManager;
    }

}

SysUser 实体类要实现 Serializable 接口

java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysUser implements Serializable {
	// ... 
}

其它代码照旧

== SpringSecurity

(结合上部分,同时做到集成 Redis + 前后端分离)

1、pom.xml 引入依赖

xml
<!-- HttpSession 存储到 Redis -->
<dependency>
	<groupId>org.springframework.session</groupId>
	<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、yml 增加配置

yml
spring:
    session:
        store-type: redis
        timeout: 8H
        redis:
            namespace: spring:session

    data:
        # redis配置
        redis:
            # Redis数据库索引(默认为0)
            database: 3
            # Redis服务器地址
            host: 127.0.0.1
            # Redis服务器连接端口
            port: 6379
            # Redis服务器连接密码(默认为空)
            password:
            # 连接超时时间
            timeout: 10s
            lettuce:
                pool:
                    # 连接池最大连接数
                    max-active: 200
                    # 连接池最大阻塞等待时间(使用负值表示没有限制)
                    max-wait: -1ms
                    # 连接池中的最大空闲连接
                    max-idle: 10
                    # 连接池中的最小空闲连接
                    min-idle: 0

3、在 CustomAccessDeniedHandler 自定义认证异常处理类中,返回 json 格式数据

java
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler, AuthenticationEntryPoint, Serializable {

    // 未登录异常
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        //验证为未登陆状态会进入此方法,认证错误
        response.setStatus(401);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter printWriter = response.getWriter();
        String body = new ObjectMapper().writeValueAsString(AjaxJson.get(401, "请先进行登录"));
        printWriter.write(body);
        printWriter.flush();
    }

    // 权限不足
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        // 登陆状态下,权限不足执行该方法
        response.setStatus(200);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter printWriter = response.getWriter();
        String body = new ObjectMapper().writeValueAsString(AjaxJson.get(403, "权限不足"));
        printWriter.write(body);
        printWriter.flush();
    }
    
}

4、别忘了注入到 SecurityFilterChain 过滤器链

java
// 异常处理
httpSecurity.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {
	// 权限不足处理方案
	httpSecurityExceptionHandlingConfigurer.accessDeniedHandler(accessDeniedHandler);
	// 未登录 处理逻辑
	httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint(accessDeniedHandler);
});

5、在登录时,返回对应 token 信息

java
// 测试登录 
@RequestMapping("doLogin")
public AjaxJson doLogin(String username, String password, HttpServletRequest request) {
	try {
		// 验证账号密码
		UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
		usernamePasswordAuthenticationToken.setDetails(sysUserDao.findByUsername(username));
		Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
		// 存入上下文
		SecurityContextHolder.getContext().setAuthentication(authentication);
		request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
		// 返回
		String token = request.getSession().getId();
		return AjaxJson.getSuccess("登录成功!").set("token", token);
	} catch (Exception e) {
		e.printStackTrace();
		return AjaxJson.getError(e.getMessage());
	}
}

6、前端改造

  • 1、在登录请求时,将返回的 token 保存到本地 localStorage.setItem('token', res.token)。
  • 2、在后续每次请求中,读取本地保存的 token 塞到请求 header 中
js
const header = {};
if(localStorage.token) {
	header.token = localStorage.token;
}
// 后续提交请求...

7、新建 HttpSessionConfigure 配置重写 HttpSessionId 读取策略,改为从 header 头读取 token 参数作为 SessionId

java
@Configuration
public class HttpSessionConfigure {
    // HttpSession 读取策略,从 header 头读取 token 参数作为 session id
    @Bean
    public HeaderHttpSessionIdResolver httpSessionStrategy() {
        System.out.println("----------------- 自定义 HttpSession Id 读取方式");
        return new HeaderHttpSessionIdResolver("token");
    }
}

== JWT

无

:::


File: sa-token-doc-new/docs/fun/cors-filter.md


title: "Sa-Token 解决跨域问题" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,解决跨域问题,附录"

解决跨域问题

参考1: https://juejin.cn/post/7491603065944129590

参考2: https://mp.weixin.qq.com/s/tbqjCKrTMj-l1lZbeyu81g

参考3: https://mp.weixin.qq.com/s/8aziIhqGCb_qsr8kLiqzmg


File: sa-token-doc-new/docs/fun/curr-domain.md


title: "Sa-Token 解决反向代理 uri 丢失的问题" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,解决反向代理 uri 丢失的问题,附录"

解决反向代理 uri 丢失的问题


使用 request.getRequestURL() 可获取当前程序所在外网的访问地址,在 Sa-Token 中,其 SaHolder.getRequest().getUrl() 也正是借助此API完成,
有很多模块都用到了这个能力,比如SSO单点登录。

我们可以使用如下代码测试此API

java
// 显示当前程序所在外网的都访问地址
@RequestMapping("test")
public String test() {
	return "您访问的是:" + SaHolder.getRequest().getUrl();
}

从浏览器访问此接口,我们可以看到:

此 API 在本地开发时一般可以正常工作,然而如果我们在部署时使用 Nginx 做了一层反向代理后,其最终结果可能会和我们预想的有一点偏差:

不仅是 Nginx,所有包含路由转发的地方都有可能导致上述丢失 uri 的现象,解决方案也很简单,既然程序无法自动识别,我们改成手动获取即可,Sa-Token 提供两个方案:

方案一:Nginx转发时追加 header 参数

1、首先在 Nginx 代理转发的地方增加参数

重点是这一句:proxy_set_header Public-Network-URL http://$http_host$request_uri;

2、在程序中重写 SaRequest 创建策略,自定义获取 uri 的逻辑

java
@Configuration
public class SaTokenConfigure {

	@PostConstruct
	public void configSaStrategy() {
		SaStrategy.instance.setCreateSaRequest(source -> {
			HttpServletRequest request = (HttpServletRequest) source;
			return new SaRequestForServlet(request) {
				@Override
				public String getUrl() {
					String publicUrl = request.getHeader("Public-Network-URL");
					if (publicUrl != null) {
						return publicUrl;
					}
					return super.getUrl();
				}
			};
		});
	}

}

其它逻辑保持不变,框架即可正确获取 uri 地址

::: danger 风险警告
注意:步骤一与步骤二需要同步存在,否则可能有前端假传 header 参数造成安全问题
:::

方案二:直接在yml中配置当前项目的网络访问地址

在 application.yml 中增加配置:

:::tabs
== yaml 风格

yaml
sa-token: 
    # 配置当前项目的网络访问地址
    curr-domain: http://local.dev33.cn:8902/api

== properties 风格

properties
# 配置当前项目的网络访问地址
sa-token.curr-domain=http://local.dev33.cn:8902/api

:::

即可避免路由转发过程中丢失 uri 的问题


File: sa-token-doc-new/docs/fun/custom-annotations.md


title: "Sa-Token 自定义注解" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,自定义注解,附录"

自定义注解

如果框架内置的注解无法满足你的业务需求,你还可以自定义注解注入到框架中。


1、自定义注解

假设有以下业务需求

::: info 需求场景
自定义一个注解 @CheckAccount,具有 name、pwd 两个字段,在标注一个方法上时,要求前端必须提交相应的账号密码参数才能访问方法。
:::

1.1、第一步,创建一个注解

java
/**
 * 账号校验:在标注一个方法上时,要求前端必须提交相应的账号密码参数才能访问方法。
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface CheckAccount {

    /**
     * 需要校验的账号
     */
    String name();

    /**
     * 需要校验的密码
     */
    String pwd();

}

1.2、第二步,创建注解处理器

实现 SaAnnotationHandlerInterface 接口,指定泛型为刚才自定义的注解

java
/**
 * 注解 CheckAccount 的处理器
 */
@Component
public class CheckAccountHandler implements SaAnnotationHandlerInterface<CheckAccount> {

    // 指定这个处理器要处理哪个注解
    @Override
    public Class<CheckAccount> getHandlerAnnotationClass() {
        return CheckAccount.class;
    }

    // 每次请求校验注解时,会执行的方法
    @Override
    public void checkMethod(CheckAccount at, AnnotatedElement element) {
        // 获取前端请求提交的参数
        String name = SaHolder.getRequest().getParamNotNull("name");
        String pwd = SaHolder.getRequest().getParamNotNull("pwd");

        // 与注解中指定的值相比较
        if(name.equals(at.name()) && pwd.equals(at.pwd()) ) {
            // 校验通过,什么也不做
        } else {
            // 校验不通过,则抛出异常
            throw new SaTokenException("账号或密码错误,未通过校验");
        }
    }

}

参考上述代码,实现类上指定了 @Component 注解,使其可以在 ioc 环境下(如 Spring)被自动扫描注册 Sa-Token 中,
如果你的项目属于非 ioc 环境,则需要手动将其注册到 Sa-Token 框架中:

java
SaAnnotationStrategy.instance.registerAnnotationHandler(new CheckAccountHandler());

1.3、测试自定义的注解

我们在一个请求接口上指定这个注解,来测试一下效果

java
@RestController
@RequestMapping("/test/")
public class TestController {

	@RequestMapping("test")
	@CheckAccount(name = "sa", pwd = "123456")
	public SaResult test() {
		System.out.println("------------进来了");
		return SaResult.ok(); 
	}
	
}

启动项目,使用浏览器访问此接口。

先来个错误的账号密码访问测试一下:http://localhost:8081/test/test?name=sa&pwd=123

返回结果:

js
{
  "code": 500,
  "msg": "账号或密码错误,未通过校验",
  "data": null
}

使用正确账号密码测试访问:http://localhost:8081/test/test?name=sa&pwd=123456

返回结果:

js
{
  "code": 200,
  "msg": "ok",
  "data": null
}

2、使用自定义注解优化多账号鉴权

在之前的 [ 多账号鉴权 ] 章节,我们介绍了利用 “spring 注解处理器” 达到注解合并的目的,从而简化多账号体系下的注解鉴权写法。

此种方案比较简单,但是也有一些缺点。

  • 1、强依赖 Spring,无法在非 Spring 环境中使用。
  • 2、注解递归检查可能会造成一些性能下降。
  • 3、扩展性较低,只能略微简化框架内置好的注解写法,无法灵活扩展功能。

此处我们再演示一种方案,使用自定义注解的方式达到相同的目的。

2.1、首先定义注解

java
/**
 * 登录认证(User版):只有登录之后才能进入该方法 
 * <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上) 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckLogin {
	
}

2.2、定义注解处理器

java
/**
 * 注解 SaUserCheckLogin 的处理器
 */
@Component
public class SaUserCheckLoginHandler implements SaAnnotationHandlerInterface<SaUserCheckLogin> {

    @Override
    public Class<SaUserCheckLogin> getHandlerAnnotationClass() {
        return SaUserCheckLogin.class;
    }

    @Override
    public void checkMethod(SaUserCheckLogin at, AnnotatedElement element) {
        SaCheckLoginHandler._checkMethod(StpUserUtil.TYPE);
    }

}

2.3、使用新注解

接下来就可以使用我们的自定义注解了:

java
// 使用 @SaUserCheckLogin 的效果等同于使用:@SaCheckLogin(type = "user")
@SaUserCheckLogin
@RequestMapping("info")
public String info() {
    return "查询用户信息";
}

注:其它注解 @SaCheckRole("xxx")、@SaCheckPermission("xxx") 同理, 完整示例参考 Gitee 代码:
自定义注解。


File: sa-token-doc-new/docs/fun/dynamic-router-check.md


title: "Sa-Token 参考:把路由拦截鉴权动态化" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,参考:把路由拦截鉴权动态化,附录"

参考:把路由拦截鉴权动态化

框架提供的示例是硬代码写死的,不过稍微做一下更改,你就可以让他动态化


参考如下:

java
@Override
public void addInterceptors(InterceptorRegistry registry) {
	registry.addInterceptor(new SaInterceptor(handle -> {
		SaRouter
			.match("/**")
			.notMatch(excludePaths())
			.check(r -> StpUtil.checkLogin());
	})).addPathPatterns("/**");
}

// 动态获取哪些 path 可以忽略鉴权 
public List<String> excludePaths() {
	// 此处仅为示例,实际项目你可以写任意代码来查询这些path
	return Arrays.asList("/path1", "/path2", "/path3");
}

如果不仅仅是登录校验,还需要鉴权,那也很简单:

java
@Override
public void addInterceptors(InterceptorRegistry registry) {
	registry.addInterceptor(new SaInterceptor(handle -> {
		// 遍历校验规则,依次鉴权 
		Map<String, String> rules = getAuthRules();
		for (String path : rules.keySet()) {
			SaRouter.match(path, () -> StpUtil.checkPermission(rules.get(path)));
		}
	})).addPathPatterns("/**");
}

// 动态获取鉴权规则 
public Map<String, String> getAuthRules() {
	// key 代表要拦截的 path,value 代表需要校验的权限 
	Map<String, String> authMap = new LinkedHashMap<>();
	authMap.put("/user/**", "user");
	authMap.put("/admin/**", "admin");
	authMap.put("/article/**", "article");
	// 更多规则 ... 
	return authMap;
}

错误的写法:

java
@Override
public void addInterceptors(InterceptorRegistry registry) {
	registry.addInterceptor(new SaInterceptor(handle -> {
		StpUtil.checkLogin();
	}))
	.addPathPatterns("/**")
	.excludePathPatterns(excludePaths());
}

// 动态获取哪些 path 可以忽略鉴权 
public List<String> excludePaths() {
	// 此处仅为示例,实际项目你可以写任意代码来查询这些path
	return Arrays.asList("/path1", "/path2", "/path3");
}

错误点:因为 lambda 表达式之外的代码只会在启动时执行一次,所以 excludePaths() 方法是无法做到动态化读取的,
若要在项目运行时动态读写,必须把调用 excludePaths() 的时机放在 lambda 里。


File: sa-token-doc-new/docs/fun/exception-code.md


title: "Sa-Token 异常细分状态码" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,异常细分状态码,附录"

异常细分状态码


获取异常细分状态码

Sa-Token 中的基础异常类是 SaTokenException,在此基础上,又针对一些特定场景划分出诸如 NotLoginException、NotPermissionException 等。

但是框架中异常抛出点远远多于异常种类的划分,比如在 SSO 插件中,[ redirect 重定向地址无效 ] 和 [ ticket 参数值无效 ] 都会导致 SSO 授权的失败,
但是它们抛出的异常都是 SaSsoException,如果你需要对这两种异常情形做出不同的处理,仅仅判断异常的 ClassType 显然不够。

为了解决上述需求,Sa-Token 对每个异常抛出点都会指定一个特定的 code 值,就像这样:

java
if(SaFoxUtil.isUrl(url) == false) {
	throw new SaSsoException("无效redirect:" + url).setCode(SaSsoErrorCode.CODE_30001);	
}

就像是打上一个特定的标记,不同异常情形标记的 code 码值也会不同,这就为你精细化异常处理提供了前提。

要在捕获异常时获取这个 code 码也非常简单:

java
@RestControllerAdvice
public class GlobalExceptionHandler {
	@ExceptionHandler(SaTokenException.class)
	public SaResult handlerSaTokenException(SaTokenException e) {
		
		// 根据不同异常细分状态码返回不同的提示 
		if(e.getCode() == 30001) {
			return SaResult.error("redirect 重定向 url 是一个无效地址");
		}
		if(e.getCode() == 30002) {
			return SaResult.error("redirect 重定向 url 不在 allowUrl 允许的范围内");
		}
		if(e.getCode() == 30004) {
			return SaResult.error("提供的 ticket 是无效的");
		}
		// 更多 code 码判断 ... 
		
		// 默认的提示 
		return SaResult.error("服务器繁忙,请稍后重试...");
	}
}

SaToken 中的所有异常都是继承于 SaTokenException 的,也就是说,所有异常你都可以通过 e.getCode() 的方式获取对应的异常细分状态码。

异常细分状态码-参照表

sa-token-code 核心包

| code码值 | 含义 |
| :-------- | :-------- |
| -1 | 代表这个异常在抛出时未指定异常细分状态码 |
| 10001 | 未能获取有效的上下文处理器 |
| 10002 | 未能获取有效的上下文 |
| 10003 | JSON 转换器未实现 |
| 10011 | 未能从全局 StpLogic 集合中找到对应 type 的 StpLogic |
| 10021 | 指定的配置文件加载失败 |
| 10022 | 配置文件属性无法正常读取 |
| 10031 | 重置的侦听器集合不可以为空 |
| 10032 | 注册的侦听器不可以为空 |
| 10301 | 提供的 Same-Token 是无效的 |
| 10311 | 表示未能通过 Http Basic 认证校验 |
| 10321 | 提供的 HttpMethod 是无效的 |
| 11001 | 未能读取到有效Token |
| 11002 | 登录时的账号id值为空 |
| 11003 | 更改 Token 指向的 账号Id 时,账号Id值为空 |
| 11011 | 未能读取到有效Token |
| 11012 | Token无效 |
| 11013 | Token已过期 |
| 11014 | Token已被顶下线 |
| 11015 | Token已被踢下线 |
| 11016 | Token已被冻结 |
| 11031 | 在未集成 sa-token-jwt 插件时调用 getExtra() 抛出异常 |
| 11041 | 缺少指定的角色 |
| 11051 | 缺少指定的权限 |
| 11061 | 当前账号未通过服务封禁校验 |
| 11062 | 提供要解禁的账号无效 |
| 11063 | 提供要解禁的服务无效 |
| 11064 | 提供要解禁的等级无效 |
| 11071 | 二级认证校验未通过 |
| 12001 | 请求中缺少指定的参数 |
| 12002 | 构建 Cookie 时缺少 name 参数 |
| 12003 | 构建 Cookie 时缺少 value 参数 |
| 12101 | Base64 编码异常 |
| 12102 | Base64 解码异常 |
| 12103 | URL 编码异常 |
| 12104 | URL 解码异常 |
| 12111 | md5 加密异常 |
| 12112 | sha1 加密异常 |
| 12113 | sha256 加密异常 |
| 12114 | AES 加密异常 |
| 12115 | AES 解密异常 |
| 12116 | RSA 公钥加密异常 |
| 12117 | RSA 私钥加密异常 |
| 12118 | RSA 公钥解密异常 |
| 12119 | RSA 私钥解密异常 |
| 12201 | 参与参数签名的秘钥不可为空 |
| 12202 | 给定的签名无效 |
| 12203 | timestamp 超出允许的范围 |

sa-token-servlet

| code码值 | 含义 |
| :-------- | :-------- |
| 20001 | 转发失败 |
| 20002 | 重定向失败 |

sa-token-spring-boot-starter

| code码值 | 含义 |
| :-------- | :-------- |
| 20101 | 企图在非 Web 上下文获取 Request、Response 等对象 |
| 20103 | 对象转 JSON 字符串失败 |
| 20104 | JSON 字符串转 Map 失败 |
| 20105 | 默认的 Filter 异常处理函数 |

sa-token-reactor-spring-boot-starter

| code码值 | 含义 |
| :-------- | :-------- |
| 20203 | 对象转 JSON 字符串失败 |
| 20204 | JSON 字符串转 Map 失败 |
| 20205 | 默认的 Filter 异常处理函数 |

sa-token-solon-plugin

| code码值 | 含义 |
| :-------- | :-------- |
| 20301 | 默认的拦截器异常处理函数 |
| 20302 | 默认的 Filter 异常处理函数 |

sa-token-sso 单点登录相关:

| code码值 | 含义 |
| :-------- | :-------- |
| 30001 | redirect 重定向 url 是一个无效地址 |
| 30002 | redirect 重定向 url 不在 allowUrl 允许的范围内 |
| 30003 | 接口调用方提供的 secretkey 秘钥无效 |
| 30004 | 提供的 ticket 是无效的 |
| 30005 | 在模式三下,sso-client 调用 sso-server 端 校验ticket接口 时,得到的响应是校验失败 |
| 30006 | 在模式三下,sso-client 调用 sso-server 端 单点注销接口 时,得到的响应是注销失败 |
| 30007 | http 请求调用 提供的 timestamp 与当前时间的差距超出允许的范围 |
| 30008 | http 请求调用 提供的 sign 无效 |
| 30009 | 本地系统没有配置 secretkey 字段 |
| 30010 | 本地系统没有配置 http 请求处理器(参考 HTTP 请求扩展) |
| 30011 | 该 ticket 不属于当前 client |

sa-token-oauth2 相关:

| code码值 | 含义 |
| :-------- | :-------- |
| 30101 | client_id 不可为空 |
| 30102 | scope 不可为空 |
| 30103 | redirect_uri 不可为空 |
| 30104 | LoginId 不可为空 |
| 30105 | 无效 client_id |
| 30106 | 无效 access_token |
| 30107 | 无效 client_token |
| 30108 | Access-Token 不具备指定的 Scope |
| 30109 | Client-Token 不具备指定的 Scope |
| 30110 | 无效 code 码 |
| 30111 | 无效 Refresh-Token |
| 30112 | 请求的 Scope 暂未签约 |
| 30113 | 无效 redirect_url |
| 30114 | 非法 redirect_url |
| 30115 | 无效 client_secret |
| 30120 | redirect_uri 不一致 |
| 30122 | client_id 不一致 |
| 30125 | 无效 response_type |
| 30126 | 无效 grant_type |
| 30127 | 无效 state |
| 30141 | 系统暂未开放的授权模式 |
| 30142 | 应用暂未开放的授权模式 |
| 30151 | 无效的请求 Method |
| 30191 | 其它异常 |

sa-token-jwt 插件相关:

| code码值 | 含义 |
| :-------- | :-------- |
| 30201 | 对 jwt 字符串解析失败 |
| 30202 | 此 jwt 的签名无效 |
| 30203 | 此 jwt 的 loginType 字段不符合预期 |
| 30204 | 此 jwt 已超时 |
| 30205 | 没有配置jwt秘钥 |
| 30206 | 登录时提供的账号id为空 |
| 30207 | extraData 包含 jwt 保留字段 |

sa-token-temp-jwt 插件相关:

| code码值 | 含义 |
| :-------- | :-------- |
| 30301 | jwt 模式没有提供秘钥 |
| 30302 | jwt 模式不可以删除 Token |
| 30303 | Token已超时 |

::: warning 注意
部分插件因异常抛出点较少,暂未做状态码细分处理
:::


File: sa-token-doc-new/docs/fun/firewall.md


title: "Sa-Token 防火墙" keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,防火墙,附录"

防火墙

Sa-Token 内置防火墙组件 SaFirewallStrategy,用于拦截一些可能造成攻击的危险请求。
例如当前端提交的 path 为 /test//login 时,框架将会强制截断请求,响应以下内容:

txt
非法请求:/test//login

因为包含双斜杠的 path 请求通常被用于鉴权绕行攻击。类似的拦截规则还有很多, SaFirewallStrategy 采用 hooks 机制,允许开发者自由扩展拦截规则,
框架默认具有以下 hook 拦截规则:

  • SaFirewallCheckHookForWhitePath:请求 path 白名单放行。
  • SaFirewallCheckHookForBlackPath:请求 path 黑名单校验。
  • SaFirewallCheckHookForPathDangerCharacter:请求 path 危险字符校。
  • SaFirewallCheckHookForPathBannedCharacter:请求 path 禁止字符校验。
  • SaFirewallCheckHookForDirectoryTraversal:请求 path 目录遍历符检测。
  • SaFirewallCheckHookForHost:Host 检测。
  • SaFirewallCheckHookForHttpMethod:请求 Method 检测。
  • SaFirewallCheckHookForHeader:请求头检测。
  • SaFirewallCheckHookForParameter:请求参数检测。

1、默认 hook 配置:

假设我们想要增加请求 path 黑名单,可以使用如下代码:

java
@Configuration
public class SaTokenConfigure {
	@PostConstruct
	public void saTokenPostConstruct() {
		SaFirewallCheckHookForBlackPath.instance.resetConfig("/abc");
	}
}

现在从浏览器访问 /abc,将会被防火墙组件直接拦截:

txt
非法请求:/abc

除了 SaFirewallCheckHookForBlackPath 以外,其它所有 hook 均可通过此方式重载配置,在此暂不冗余演示。

2、注册新的 hook 规则:

你可以使用如下代码注册新的 hook 规则:

java
@PostConstruct
public void saTokenPostConstruct() {
	// 注册新 hook 演示,拦截所有带有 pwd 参数的请求,拒绝响应 
	SaFirewallStrategy.instance.registerHook((req, res, extArg)->{
		if(req.getParam("pwd") != null) {
			throw new FirewallCheckException("请求中不可包含 pwd 参数");
		}
	});
}

除了注册新 hook 规则,你还可以移除默认 hook ,来删减你认为不必要存在的校验规则:

java
// 移除指定类型的 hook 验证
SaFirewallStrategy.instance.removeHook(SaFirewallCheckHookForHost.class);

3、利用自动注入特性注册 hook

如果你的项目属于 IOC 环境(例如 SpringBoot 项目),还可以这样注册 hook:

java
// 自定义防火墙校验 hook 
@Component
public class SaFirewallCheckHookForXxx implements SaFirewallCheckHook {
    @Override
    public void execute(SaRequest req, SaResponse res, Object extArg) {
        System.out.println("----------- 自定义防火墙校验 hook ");
    }
}

4、指定异常处理:

被防火墙拦截的请求不会做出格式化响应,因为通常这些请求为非正常业务请求,只需阻断即可,无需前端依照响应做出页面提示。

如果你的业务切实需要对防火墙拦截做出格式化响应,可以通过以下代码完成:

java
@PostConstruct
public void saTokenPostConstruct() {
	// 指定防火墙校验不通过时的处理方案
	SaFirewallStrategy.instance.checkFailHandle = (e, req, res, extArg) -> {
		System.out.println("防火墙校验不通过:" + e.getMessage());
		try {
			HttpServletResponse response = (HttpServletResponse)res.getSource();
			response.setContentType("application/json;charset=UTF-8");
			String resJson = SaResult.error(e.getMessage()).toString();
			response.getWriter().print(resJson);
			response.getWriter().flush();
		} catch (IOException ex) {
			throw new RuntimeException(ex);
		}
	};
}

浏览器将得到以下 json 格式响应:

js
{
  "code": 500,
  "msg": "非法请求:/abc",
  "data": null
}

---

## File: sa-token-doc-new/docs/fun/git-pr.md

[Binary file]

---

## File: sa-token-doc-new/docs/fun/issue-template.md

[Binary file]

---

## File: sa-token-doc-new/docs/fun/jur-cache.md

---
title: "Sa-Token 参考:把权限放在缓存里"
keywords: "Sa-Token,sa-token,satoken,Sa-Token文档,参考:把权限放在缓存里,附录"
---

# 参考:将权限数据放在缓存里
前面我们讲解了如何通过`StpInterface`接口注入权限数据,框架默认是不提供缓存能力的,如果你想减小数据库的访问压力,则需要将权限数据放到缓存中

--- 

参考如下:
``` java
/**
 * 自定义权限验证接口扩展 
 */
@Component  
public class StpInterfaceImpl implements StpInterface {
    
	// 返回一个账号所拥有的权限码集合
	@Override
	@SuppressWarnings("unchecked")
	public List<String> getPermissionList(Object loginId, String loginType) {

		// 1. 声明权限码集合
		List<String> list = new ArrayList<>();

		// 2. 遍历角色列表,查询拥有的权限码
		for (String roleId : getRoleList(loginId, loginType)) {
			List<String> permissionList = (List<String>)SaManager.getSaTokenDao().getObject("satoken:role-find-permission:" + roleId);
			if(permissionList == null) {
				// 从数据库查询这个角色 id 所拥有的权限列表
				permissionList = ...
				// 查好后,set 到缓存中
				SaManager.getSaTokenDao().setObject("satoken:role-find-permission:" + roleId, permissionList, 60 * 60 * 24 * 30);
			}
			list.addAll(permissionList);
		}

		// 3. 返回权限码集合
		return list;
	}

	// 返回一个账号所拥有的角色标识集合
	@Override
	@SuppressWarnings("unchecked")
	public List<String> getRoleList(Object loginId, String loginType) {
		List<String> roleList = (List<String>)SaManager.getSaTokenDao().getObject("satoken:loginId-find-role:" + loginId);
		if(roleList == null) {
			// 从数据库查询这个账号id拥有的角色列表,
			roleList = ... 
			// 查好后,set 到缓存中
			SaManager.getSaTokenDao().setObject("satoken:loginId-find-role:" + loginId, roleList, 60 * 60 * 24 * 30);
		}
		return roleList;
	}
	
}

疑问:为什么不直接缓存 `[账号id->权限列表]`的关系,而是 `[账号id -> 角色id -> 权限列表]`?

答:[账号id->权限列表]的缓存方式虽然更加直接粗暴,却有一个严重的问题:

  • 通常我们系统的权限架构是RBAC模型:权限与用户没有直接的关系,而是:用户拥有指定的角色,角色再拥有指定的权限
  • 而这种'拥有关系'是动态的,是可以随时修改的,一旦我们修改了它们的对应关系,便要同步修改或清除对应的缓存数据

现在假设如下业务场景:我们系统中有十万个账号属于同一个角色,当我们变动这个角色的权限时,难道我们要同时清除这十万个账号的缓存信息吗?
这显然是一个不合理的操作,同一时间缓存大量清除容易引起Redis的缓存雪崩

而当我们采用 [账号id -> 角色id -> 权限列表] 的缓存模型时,则只需要清除或修改 [角色id -> 权限列表] 一条缓存即可

一言以蔽之:权限的缓存模型需要跟着权限模型走,角色缓存亦然


File: sa-token-doc-new/docs/fun/log.md

[Binary file]