新聞中心
Sa-Token 介紹
Sa-Token 是一個輕量級 Java 權(quán)限認證框架,主要解決:登錄認證、權(quán)限認證、單點登錄、OAuth2.0、分布式Session會話、微服務網(wǎng)關(guān)鑒權(quán) 等一系列權(quán)限相關(guān)問題。

堅守“ 做人真誠 · 做事靠譜 · 口碑至上 · 高效敬業(yè) ”的價值觀,專業(yè)網(wǎng)站建設服務10余年為成都成都生料攪拌車小微創(chuàng)業(yè)公司專業(yè)提供企業(yè)網(wǎng)站建設營銷網(wǎng)站建設商城網(wǎng)站建設手機網(wǎng)站建設小程序網(wǎng)站建設網(wǎng)站改版,從內(nèi)容策劃、視覺設計、底層架構(gòu)、網(wǎng)頁布局、功能開發(fā)迭代于一體的高端網(wǎng)站建設服務。
Sa-Token 旨在以簡單、優(yōu)雅的方式完成系統(tǒng)的權(quán)限認證部分,以登錄認證為例,你只需要:
// 會話登錄,參數(shù)填登錄人的賬號id
StpUtil.login(10001);無需實現(xiàn)任何接口,無需創(chuàng)建任何配置文件,只需要這一句靜態(tài)代碼的調(diào)用,便可以完成會話登錄認證。
如果一個接口需要登錄后才能訪問,我們只需調(diào)用以下代碼:
// 校驗當前客戶端是否已經(jīng)登錄,如果未登錄則拋出 `NotLoginException` 異常
StpUtil.checkLogin();在 Sa-Token 中,大多數(shù)功能都可以一行代碼解決:
踢人下線:
// 將賬號id為 10077 的會話踢下線
StpUtil.kickout(10077);權(quán)限認證:
// 注解鑒權(quán):只有具備 `user:add` 權(quán)限的會話才可以進入方法
@SaCheckPermission("user:add")
public String insert(SysUser user) {
// ...
return "用戶增加";
}路由攔截鑒權(quán):
// 根據(jù)路由劃分模塊,不同模塊不同鑒權(quán)
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 等框架的三拜九叩之后,你就會明白,相對于這些傳統(tǒng)老牌框架,Sa-Token 的 API 設計是多么的簡單、優(yōu)雅!
Sa-Token 功能一覽
Sa-Token 目前主要五大功能模塊:登錄認證、權(quán)限認證、單點登錄、OAuth2.0、微服務鑒權(quán)。
- 登錄認證 —— 單端登錄、多端登錄、同端互斥登錄、七天內(nèi)免登錄
- 權(quán)限認證 —— 權(quán)限認證、角色認證、會話二級認證
- Session會話 —— 全端共享Session、單端獨享Session、自定義Session
- 踢人下線 —— 根據(jù)賬號id踢人下線、根據(jù)Token值踢人下線
- 賬號封禁 —— 登錄封禁、按照業(yè)務分類封禁、按照處罰階梯封禁
- 持久層擴展 —— 可集成Redis、Memcached等專業(yè)緩存中間件,重啟數(shù)據(jù)不丟失
- 分布式會話 —— 提供jwt集成、共享數(shù)據(jù)中心兩種分布式會話方案
- 微服務網(wǎng)關(guān)鑒權(quán) —— 適配Gateway、ShenYu、Zuul等常見網(wǎng)關(guān)的路由攔截認證
- 單點登錄 —— 內(nèi)置三種單點登錄模式:無論是否跨域、是否共享Redis,都可以搞定
- OAuth2.0認證 —— 輕松搭建 OAuth2.0 服務,支持openid模式
- 二級認證 —— 在已登錄的基礎(chǔ)上再次認證,保證安全性
- Basic認證 —— 一行代碼接入 Http Basic 認證
- 獨立Redis —— 將權(quán)限緩存與業(yè)務緩存分離
- 臨時Token認證 —— 解決短時間的Token授權(quán)問題
- 模擬他人賬號 —— 實時操作任意用戶狀態(tài)數(shù)據(jù)
- 臨時身份切換 —— 將會話身份臨時切換為其它賬號
- 前后臺分離 —— APP、小程序等不支持Cookie的終端
- 同端互斥登錄 —— 像QQ一樣手機電腦同時在線,但是兩個手機上互斥登錄
- 多賬號認證體系 —— 比如一個商城項目的user表和admin表分開鑒權(quán)
- Token風格定制 —— 內(nèi)置六種Token風格,還可:自定義Token生成策略、自定義Token前綴
- 注解式鑒權(quán) —— 優(yōu)雅的將鑒權(quán)與業(yè)務代碼分離
- 路由攔截式鑒權(quán) —— 根據(jù)路由攔截鑒權(quán),可適配restful模式
- 自動續(xù)簽 —— 提供兩種Token過期策略,靈活搭配使用,還可自動續(xù)簽
- 會話治理 —— 提供方便靈活的會話查詢接口
- 記住我模式 —— 適配[記住我]模式,重啟瀏覽器免驗證
- 密碼加密 —— 提供密碼加密模塊,可快速MD5、SHA1、SHA256、AES、RSA加密
- 全局偵聽器 —— 在用戶登陸、注銷、被踢下線等關(guān)鍵性操作時進行一些AOP操作
- 開箱即用 —— 提供SpringMVC、WebFlux等常見web框架starter集成包,真正的開箱即用
SpringBoot 集成 Sa-Token
一、添加maven依賴
cn.dev33
sa-token-spring-boot-starter
1.34.0
注:如果你使用的 SpringBoot 3.x,只需要將 sa-token-spring-boot-starter 修改為 sa-token-spring-boot3-starter 即可。
二、設置配置文件
你可以零配置啟動項目 ,但同時你也可以在 application.yml 中增加如下配置,定制性使用框架:
server:
# 端口
port: 8081
############## Sa-Token 配置 (文檔: https://sa-token.cc) ##############
sa-token:
# token名稱 (同時也是cookie名稱)
token-name: satoken
# token有效期,單位s 默認30天, -1代表永不過期
timeout: 2592000
# token臨時有效期 (指定時間內(nèi)無操作就視為token過期) 單位: 秒
activity-timeout: -1
# 是否允許同一賬號并發(fā)登錄 (為true時允許一起登錄, 為false時新登錄擠掉舊登錄)
is-concurrent: true
# 在多人登錄同一賬號時,是否共用一個token (為true時所有登錄共用一個token, 為false時每次登錄新建一個token)
is-share: true
# token風格
token-style: uuid
# 是否輸出操作日志
is-log: false三、創(chuàng)建案例
@RestController
@RequestMapping("/user/")
public class UserController {
// 測試登錄,瀏覽器訪問:http://localhost:8081/user/doLogin?username=zhang&password=123456
@RequestMapping("doLogin")
public String doLogin(String username, String password) {
// 此處僅作模擬示例,真實項目需要從數(shù)據(jù)庫中查詢數(shù)據(jù)進行比對
if("iron".equals(username) && "123456".equals(password)) {
StpUtil.login(10001);
return "登錄成功";
}
return "登錄失敗";
}
// 查詢登錄狀態(tài),瀏覽器訪問:http://localhost:8081/user/isLogin
@RequestMapping("isLogin")
public String isLogin() {
return "當前會話是否登錄:" + StpUtil.isLogin();
}
}登錄認證
設計思路
對于一些登錄之后才能訪問的接口(例如:查詢我的賬號資料),我們通常的做法是增加一層接口校驗:
- 如果校驗通過,則:正常返回數(shù)據(jù)。
- 如果校驗未通過,則:拋出異常,告知其需要先進行登錄。
那么,判斷會話是否登錄的依據(jù)是什么?我們先來簡單分析一下登錄訪問流程:
- 用戶提交 name + password 參數(shù),調(diào)用登錄接口。
- 登錄成功,返回這個用戶的 Token 會話憑證。
- 用戶后續(xù)的每次請求,都攜帶上這個 Token。
- 服務器根據(jù) Token 判斷此會話是否登錄成功。
所謂登錄認證,指的就是服務器校驗賬號密碼,為用戶頒發(fā) Token 會話憑證的過程,這個 Token 也是我們后續(xù)判斷會話是否登錄的關(guān)鍵所在。
登錄與注銷
根據(jù)以上思路,我們需要一個會話登錄的函數(shù):
// 會話登錄:參數(shù)填寫要登錄的賬號id,建議的數(shù)據(jù)類型:long | int | String, 不可以傳入復雜類型,如:User、Admin 等等
StpUtil.login(Object id);只此一句代碼,便可以使會話登錄成功,實際上,Sa-Token 在背后做了大量的工作,包括但不限于:
- 檢查此賬號是否之前已有登錄
- 為賬號生成 Token 憑證與 Session 會話
- 通知全局偵聽器,xx 賬號登錄成功
- 將 Token 注入到請求上下文
- 等等其它工作……
你暫時不需要完整的了解整個登錄過程,你只需要記住關(guān)鍵一點:Sa-Token 為這個賬號創(chuàng)建了一個Token憑證,且通過 Cookie 上下文返回給了前端。
所以一般情況下,我們的登錄接口代碼,會大致類似如下:
// 會話登錄接口
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
// 第一步:比對前端提交的賬號名稱、密碼
if("iron".equals(name) && "123456".equals(pwd)) {
// 第二步:根據(jù)賬號id,進行登錄
StpUtil.login(10001);
return SaResult.ok("登錄成功");
}
return SaResult.error("登錄失敗");
}如果你對以上代碼閱讀沒有壓力,你可能會注意到略顯奇怪的一點:此處僅僅做了會話登錄,但并沒有主動向前端返回 Token 信息。是因為不需要嗎?嚴格來講是需要的,只不過 StpUtil.login(id) 方法利用了 Cookie 自動注入的特性,省略了你手寫返回 Token 的代碼。
如果你對 Cookie 功能還不太了解,也不用擔心,我們會在之后的 [ 前后端分離 ] 章節(jié)中詳細的闡述 Cookie 功能,現(xiàn)在你只需要了解最基本的兩點:
- Cookie 可以從后端控制往瀏覽器中寫入 Token 值。
- Cookie 會在前端每次發(fā)起請求時自動提交 Token 值。
因此,在 Cookie 功能的加持下,我們可以僅靠 StpUtil.login(id) 一句代碼就完成登錄認證。
除了登錄方法,我們還需要:
// 當前會話注銷登錄
StpUtil.logout();
// 獲取當前會話是否已經(jīng)登錄,返回true=已登錄,false=未登錄
StpUtil.isLogin();
// 檢驗當前會話是否已經(jīng)登錄, 如果未登錄,則拋出異常:`NotLoginException`
StpUtil.checkLogin();異常 NotLoginException 代表當前會話暫未登錄,可能的原因有很多:前端沒有提交 Token、前端提交的 Token 是無效的、前端提交的 Token 已經(jīng)過期 …… 等等,可參照此篇:未登錄場景值,了解如何獲取未登錄的場景值。
會話查詢
// 獲取當前會話賬號id, 如果未登錄,則拋出異常:`NotLoginException`
StpUtil.getLoginId();
// 類似查詢API還有:
StpUtil.getLoginIdAsString(); // 獲取當前會話賬號id, 并轉(zhuǎn)化為`String`類型
StpUtil.getLoginIdAsInt(); // 獲取當前會話賬號id, 并轉(zhuǎn)化為`int`類型
StpUtil.getLoginIdAsLong(); // 獲取當前會話賬號id, 并轉(zhuǎn)化為`long`類型
// ---------- 指定未登錄情形下返回的默認值 ----------
// 獲取當前會話賬號id, 如果未登錄,則返回null
StpUtil.getLoginIdDefaultNull();
// 獲取當前會話賬號id, 如果未登錄,則返回默認值 (`defaultValue`可以為任意類型)
StpUtil.getLoginId(T defaultValue);Token 查詢
// 獲取當前會話的token值
StpUtil.getTokenValue();
// 獲取當前`StpLogic`的token名稱
StpUtil.getTokenName();
// 獲取指定token對應的賬號id,如果未登錄,則返回 null
StpUtil.getLoginIdByToken(String tokenValue);
// 獲取當前會話剩余有效期(單位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();
// 獲取當前會話的token信息參數(shù)
StpUtil.getTokenInfo();有關(guān)TokenInfo參數(shù)詳解,請參考:TokenInfo參數(shù)詳解
來個小測試,加深一下理解
新建 LoginController,復制以下代碼
/**
* 登錄測試
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
// 測試登錄 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
// 此處僅作模擬示例,真實項目需要從數(shù)據(jù)庫中查詢數(shù)據(jù)進行比對
if("iron".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登錄成功");
}
return SaResult.error("登錄失敗");
}
// 查詢登錄狀態(tài) ---- http://localhost:8081/acc/isLogin
@RequestMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登錄:" + StpUtil.isLogin());
}
// 查詢 Token 信息 ---- http://localhost:8081/acc/tokenInfo
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
// 測試注銷 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}權(quán)限認證
設計思路
所謂權(quán)限認證,核心邏輯就是判斷一個賬號是否擁有指定權(quán)限:
- 有,就讓你通過。
- 沒有?那么禁止訪問!
深入到底層數(shù)據(jù)中,就是每個賬號都會擁有一個權(quán)限碼集合,框架來校驗這個集合中是否包含指定的權(quán)限碼。
例如:當前賬號擁有權(quán)限碼集合 ["user-add", "user-delete", "user-get"],這時候我來校驗權(quán)限 "user-update",則其結(jié)果就是:驗證失敗,禁止訪問。
所以現(xiàn)在問題的核心就是:
- 如何獲取一個賬號所擁有的的權(quán)限碼集合?
- 本次操作需要驗證的權(quán)限碼是哪個?
獲取當前賬號權(quán)限碼集合
因為每個項目的需求不同,其權(quán)限設計也千變?nèi)f化,因此 [ 獲取當前賬號權(quán)限碼集合 ] 這一操作不可能內(nèi)置到框架中, 所以 Sa-Token 將此操作以接口的方式暴露給你,以方便你根據(jù)自己的業(yè)務邏輯進行重寫。
你需要做的就是新建一個類,實現(xiàn) StpInterface接口,例如以下代碼:
/**
* 自定義權(quán)限驗證接口擴展
*/
@Component // 保證此類被SpringBoot掃描,完成Sa-Token的自定義權(quán)限驗證擴展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一個賬號所擁有的權(quán)限碼集合
*/
@Override
public List getPermissionList(Object loginId, String loginType) {
// 本list僅做模擬,實際項目中要根據(jù)具體業(yè)務邏輯來查詢權(quán)限
List list = new ArrayList();
list.add("101");
list.add("user.add");
list.add("user.update");
list.add("user.get");
// list.add("user.delete");
list.add("art.*");
return list;
}
/**
* 返回一個賬號所擁有的角色標識集合 (權(quán)限與角色可分開校驗)
*/
@Override
public List getRoleList(Object loginId, String loginType) {
// 本list僅做模擬,實際項目中要根據(jù)具體業(yè)務邏輯來查詢角色
List list = new ArrayList();
list.add("admin");
list.add("super-admin");
return list;
}
} 參數(shù)解釋:
- loginId:賬號id,即你在調(diào)用 StpUtil.login(id) 時寫入的標識值。
- loginType:賬號體系標識,此處可以暫時忽略,在 [ 多賬戶認證 ] 章節(jié)下會對這個概念做詳細的解釋。
可參考代碼:碼云:StpInterfaceImpl.java
注意: StpInterface 接口在需要鑒權(quán)時由框架自動調(diào)用,開發(fā)者只需要配置好就可以使用下面的鑒權(quán)方法或后面的注解鑒權(quán)
權(quán)限校驗
然后就可以用以下api來鑒權(quán)了
// 獲?。寒斍百~號所擁有的權(quán)限集合
StpUtil.getPermissionList();
// 判斷:當前賬號是否含有指定權(quán)限, 返回 true 或 false
StpUtil.hasPermission("user.add");
// 校驗:當前賬號是否含有指定權(quán)限, 如果驗證未通過,則拋出異常: NotPermissionException
StpUtil.checkPermission("user.add");
// 校驗:當前賬號是否含有指定權(quán)限 [指定多個,必須全部驗證通過]
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");
// 校驗:當前賬號是否含有指定權(quán)限 [指定多個,只要其一驗證通過即可]
StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");擴展:NotPermissionException 對象可通過 getLoginType() 方法獲取具體是哪個 StpLogic 拋出的異常
角色校驗
在Sa-Token中,角色和權(quán)限可以獨立驗證
// 獲?。寒斍百~號所擁有的角色集合
StpUtil.getRoleList();
// 判斷:當前賬號是否擁有指定角色, 返回 true 或 false
StpUtil.hasRole("super-admin");
// 校驗:當前賬號是否含有指定角色標識, 如果驗證未通過,則拋出異常: NotRoleException
StpUtil.checkRole("super-admin");
// 校驗:當前賬號是否含有指定角色標識 [指定多個,必須全部驗證通過]
StpUtil.checkRoleAnd("super-admin", "shop-admin");
// 校驗:當前賬號是否含有指定角色標識 [指定多個,只要其一驗證通過即可]
StpUtil.checkRoleOr("super-admin", "shop-admin");擴展:NotRoleException 對象可通過 getLoginType() 方法獲取具體是哪個 StpLogic 拋出的異常
權(quán)限通配符
Sa-Token允許你根據(jù)通配符指定泛權(quán)限,例如當一個賬號擁有art.*的權(quán)限時,art.add、art.delete、art.update都將匹配通過
// 當擁有 art.* 權(quán)限時
StpUtil.hasPermission("art.add"); // true
StpUtil.hasPermission("art.update"); // true
StpUtil.hasPermission("goods.add"); // false
// 當擁有 *.delete 權(quán)限時
StpUtil.hasPermission("art.delete"); // true
StpUtil.hasPermission("user.delete"); // true
StpUtil.hasPermission("user.update"); // false
// 當擁有 *.js 權(quán)限時
StpUtil.hasPermission("index.js"); // true
StpUtil.hasPermission("index.css"); // false
StpUtil.hasPermission("index.html"); // false上帝權(quán)限:當一個賬號擁有 "*" 權(quán)限時,他可以驗證通過任何權(quán)限碼 (角色認證同理)
如何把權(quán)限精確到按鈕級?
權(quán)限精確到按鈕級的意思就是指:權(quán)限范圍可以控制到頁面上的每一個按鈕是否顯示。
思路:如此精確的范圍控制只依賴后端已經(jīng)難以完成,此時需要前端進行一定的邏輯判斷。
如果是前后端一體項目,可以參考:Thymeleaf 標簽方言,如果是前后端分離項目,則:
- 在登錄時,把當前賬號擁有的所有權(quán)限碼一次性返回給前端。
- 前端將權(quán)限碼集合保存在localStorage或其它全局狀態(tài)管理對象中。
- 在需要權(quán)限控制的按鈕上,使用 js 進行邏輯判斷,例如在Vue框架中我們可以使用如下寫法:
其中:arr是當前用戶擁有的權(quán)限碼數(shù)組,user.delete是顯示按鈕需要擁有
本文名稱:再見,Shiro!你好,Sa-Token!
瀏覽地址:http://www.dlmjj.cn/article/djeossd.html


咨詢
建站咨詢
