1 Sa-Token
1.1 引言
之前進行鑒權(quán)、授權(quán)都要寫一大堆代碼。如果使用像Spring Security這樣的框架,又要花好多時間學習,拿過來一用,好多配置項也不知道是干嘛用的,又不想了解。要是不用Spring Security,token的生成、校驗、刷新,權(quán)限的驗證分配,又全要自己寫,想想都頭大。
Spring Security太重而且配置繁瑣。自己實現(xiàn)所有的點必須又要顧及到,更是麻煩。
點擊了解 SpringSecurity和JWT實現(xiàn)認證和授權(quán)
新舊版本SpringSecurity使用對比
最近看到一個權(quán)限認證框架,真是夠簡單高效。這里分享一個使用Sa-Token的gateway鑒權(quán)demo。
1.2 簡介
Sa-Token(Simple & All)是一個基于 Java 的權(quán)限認證框架,用于在 Web 和普通 Java 應用中進行身份認證和權(quán)限控制。它的設計理念是簡單易用、功能全面,適用于各種場景。
官方地址:https://sa-token.cc/
Sa-Token 的一些主要特性:
- 簡單易用: 提供了簡潔的 API,易于學習和使用。
- 全面功能: 支持身份認證、權(quán)限控制、單點登錄、會話管理等多種功能。
- 支持多種存儲方式: 可以選擇內(nèi)存存儲、數(shù)據(jù)庫存儲等多種方式存儲會話信息。
- 適用于 Web 和非 Web 環(huán)境: 既可以在 Web 框架(如 Spring Boot、Spring MVC)中使用,也可以在普通 Java 應用中使用。
- 支持多種身份驗證方式: 支持賬號密碼登錄、Token 登錄、微信登錄等多種身份驗證方式。
1.3 簡單操作
示例以springboot集成
1.3.1 pom.xml
<!-- Sa-Token 權(quán)限認證, 在線文檔:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
注意:如果使用的是 SpringBoot 3.x,只需要將 sa-token-spring-boot-starter 修改為 sa-token-spring-boot3-starter 即可。
1.3.2 配置文件
server:
# 端口
port: 8081
############## Sa-Token 配置 (文檔: https://sa-token.cc) ##############
sa-token:
# token 名稱(同時也是 cookie 名稱)
token-name: satoken
# token 有效期(單位:秒) 默認30天,-1 代表永久有效
timeout: 2592000
# token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統(tǒng)就會被凍結(jié),默認-1 代表不限制,永不凍結(jié)
active-timeout: -1
# 是否允許同一賬號多地同時登錄 (為 true 時允許一起登錄, 為 false 時新登錄擠掉舊登錄)
is-concurrent: true
# 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token, 為 false 時每次登錄新建一個 token)
is-share: true
# token 風格(默認可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否輸出操作日志
is-log: true
1.3.3 controller層面
@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("zhang".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();
}
}
1.4 登錄注銷相關方法
// 當前會話注銷登錄
StpUtil.logout();
// 獲取當前會話是否已經(jīng)登錄,返回true=已登錄,false=未登錄
StpUtil.isLogin();
// 檢驗當前會話是否已經(jīng)登錄, 如果未登錄,則拋出異常:`NotLoginException`
StpUtil.checkLogin();
會話查詢
// 獲取當前會話賬號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();
1.5 權(quán)限認證
1.5.1 思路
所謂權(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)限碼是哪個?
1.5.2 獲取當前賬號權(quán)限碼集合
因為每個項目的需求不同,其權(quán)限設計也千變?nèi)f化,因此 [ 獲取當前賬號權(quán)限碼集合 ] 這一操作不可能內(nèi)置到框架中, 所以 Sa-Token 將此操作以接口的方式暴露給你,以方便你根據(jù)自己的業(yè)務邏輯進行重寫。
新建一個類,實現(xiàn) StpInterface接口,例如以下代碼:
/**
* 自定義權(quán)限加載接口實現(xiàn)類
*/
@Component // 保證此類被 SpringBoot 掃描,完成 Sa-Token 的自定義權(quán)限驗證擴展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一個賬號所擁有的權(quán)限碼集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本 list 僅做模擬,實際項目中要根據(jù)具體業(yè)務邏輯來查詢權(quán)限
List<String> list = new ArrayList<String>();
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<String> getRoleList(Object loginId, String loginType) {
// 本 list 僅做模擬,實際項目中要根據(jù)具體業(yè)務邏輯來查詢角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}
1.5.3 權(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");
1.5.4 角色校驗
在 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");
1.5.5 權(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
1.6 踢人下線
所謂踢人下線,核心操作就是找到指定 loginId 對應的 Token,并設置其失效。
強制注銷
StpUtil.logout(10001); // 強制指定賬號注銷下線
StpUtil.logout(10001, "PC"); // 強制指定賬號指定端注銷下線
StpUtil.logoutByTokenValue("token"); // 強制指定 Token 注銷下線
踢人下線
StpUtil.kickout(10001); // 將指定賬號踢下線
StpUtil.kickout(10001, "PC"); // 將指定賬號指定端踢下線
StpUtil.kickoutByTokenValue("token"); // 將指定 Token 踢下線
強制注銷 和 踢人下線 的區(qū)別在于:
- 強制注銷等價于對方主動調(diào)用了注銷方法,再次訪問會提示
Token無效。 - 踢人下線不會清除Token信息,而是將其打上特定標記,再次訪問會提示
Token已被踢下線。
1.7 注解鑒權(quán)
1.7.1 介紹
-
@SaCheckLogin: 登錄校驗 —— 只有登錄之后才能進入該方法。 -
@SaCheckRole("admin"): 角色校驗 —— 必須具有指定角色標識才能進入該方法。 -
@SaCheckPermission("user:add"): 權(quán)限校驗 —— 必須具有指定權(quán)限才能進入該方法。 -
@SaCheckSafe: 二級認證校驗 —— 必須二級認證之后才能進入該方法。 -
@SaCheckBasic:HttpBasic校驗 —— 只有通過 Basic 認證后才能進入該方法。 -
@SaIgnore:忽略校驗 —— 表示被修飾的方法或類無需進行注解鑒權(quán)和路由攔截器鑒權(quán)。- 修飾方法時代表這個方法可以被游客訪問,修飾類時代表這個類中的所有接口都可以游客訪問。
- 具有最高優(yōu)先級,當
@SaIgnore和其它鑒權(quán)注解一起出現(xiàn)時,其它鑒權(quán)注解都將被忽略。 - 同樣可以忽略掉
Sa-Token攔截器中的路由鑒權(quán)
-
@SaCheckDisable("comment"):賬號服務封禁校驗 —— 校驗當前賬號指定服務是否被封禁。
注意:以上注解都可以加在類上,代表為這個類所有方法進行鑒權(quán)
Sa-Token 使用全局攔截器完成注解鑒權(quán)功能,為了不為項目帶來不必要的性能負擔,攔截器默認處于關閉狀態(tài),使用攔截器模式,只能在Controller層進行注解鑒權(quán) ,因此,為了使用注解鑒權(quán),必須手動將 Sa-Token 的全局攔截器注冊到項目中
1.7.2 注冊攔截器
以SpringBoot2.0為例,新建配置類SaTokenConfigure.java
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注冊 Sa-Token 攔截器,打開注解式鑒權(quán)功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注冊 Sa-Token 攔截器,打開注解式鑒權(quán)功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
注意:使用攔截器模式,只能在Controller層進行注解鑒權(quán)
1.7.3 設定校驗模式
@SaCheckRole與@SaCheckPermission注解可設置校驗模式,例如:
// 注解式鑒權(quán):只要具有其中一個權(quán)限即可通過校驗
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
public SaResult atJurOr() {
return SaResult.data("用戶信息");
}
mode有兩種取值:
-
SaMode.AND:標注一組權(quán)限,會話必須全部具有才可通過校驗。 -
SaMode.OR:標注一組權(quán)限,會話只要具有其一即可通過校驗。
1.7.4 角色權(quán)限雙重 or校驗
假設有以下業(yè)務場景:一個接口在具有權(quán)限 user.add 或角色 admin 時可以調(diào)通。怎么寫?
// 角色權(quán)限雙重 “or校驗”:具備指定權(quán)限或者指定角色即可通過校驗
@RequestMapping("userAdd")
@SaCheckPermission(value = "user.add", orRole = "admin")
public SaResult userAdd() {
return SaResult.data("用戶信息");
}
orRole 字段代表權(quán)限校驗未通過時的次要選擇,兩者只要其一校驗成功即可進入請求方法,其有三種寫法:
-
rRole = "admin":代表需要擁有角色 admin 。 -
orRole = {"admin", "manager", "staff"}:代表具有三個角色其一即可。 -
orRole = {"admin, manager, staff"}:代表必須同時具有三個角色。
1.7.5 批量注解鑒權(quán)
使用 @SaCheckOr 表示批量注解鑒權(quán):
// 在 `@SaCheckOr` 中可以指定多個注解,只要當前會話滿足其中一個注解即可通過驗證,進入方法。
@SaCheckOr(
login = @SaCheckLogin,
role = @SaCheckRole("admin"),
permission = @SaCheckPermission("user.add"),
safe = @SaCheckSafe("update-password"),
basic = @SaCheckBasic(account = "sa:123456"),
disable = @SaCheckDisable("submit-orders")
)
@RequestMapping("test")
public SaResult test() {
// ...
return SaResult.ok();
}
每一項屬性都可以寫成數(shù)組形式,例如:
// 當前客戶端只要有 [ login 賬號登錄] 或者 [user 賬號登錄] 其一,就可以通過驗證進入方法。
// 注意:`type = "login"` 和 `type = "user"` 是多賬號模式章節(jié)的擴展屬性,此處你可以先略過這個知識點。
@SaCheckOr(
login = { @SaCheckLogin(type = "login"), @SaCheckLogin(type = "user") }
)
@RequestMapping("test")
public SaResult test() {
// ...
return SaResult.ok();
}
疑問:既然有了 @SaCheckOr,為什么沒有與之對應的 @SaCheckAnd 呢?
因為當寫多個注解時,其天然就是 and 校驗關系,例如:
// 當你在一個方法上寫多個注解鑒權(quán)時,其默認就是要滿足所有注解規(guī)則后,才可以進入方法,只要有一個不滿足,就會拋出異常
@SaCheckLogin
@SaCheckRole("admin")
@SaCheckPermission("user.add")
@RequestMapping("test")
public SaResult test() {
// ...
return SaResult.ok();
}
1.7.6 關閉注解校驗
SaInterceptor 只要注冊到項目中,默認就會打開注解校驗,如果要關閉此能力,需要:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new SaInterceptor(handle -> {
SaRouter.match("/**").check(r -> StpUtil.checkLogin());
}).isAnnotation(false) // 指定關閉掉注解鑒權(quán)能力,這樣框架就只會做路由攔截校驗了
).addPathPatterns("/**");
}
1.8 路由攔截
假設我們有如下需求:
項目中所有接口均需要登錄認證,只有 “登錄接口” 本身對外開放
我們怎么實現(xiàn)呢?給每個接口加上鑒權(quán)注解?手寫全局攔截器?似乎都不是非常方便。
在這個需求中我們真正需要的是一種基于路由攔截的鑒權(quán)模式,那么在Sa-Token怎么實現(xiàn)路由攔截鑒權(quán)呢?
1.8.1 注冊 Sa-Token 路由攔截器
以SpringBoot2.0為例,新建配置類SaTokenConfigure.java
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注冊攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注冊 Sa-Token 攔截器,校驗規(guī)則為 StpUtil.checkLogin() 登錄校驗。
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/**")
.excludePathPatterns("/user/doLogin");
}
}
以上代碼,我們注冊了一個基于 StpUtil.checkLogin() 的登錄校驗攔截器,并且排除了/user/doLogin接口用來開放登錄(除了/user/doLogin以外的所有接口都需要登錄才能訪問)
1.8.2 校驗函數(shù)詳解
自定義認證規(guī)則:new SaInterceptor(handle -> StpUtil.checkLogin()) 是最簡單的寫法,代表只進行登錄校驗功能。
我們可以往構(gòu)造函數(shù)塞一個完整的 lambda 表達式,來定義詳細的校驗規(guī)則,例如:
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注冊 Sa-Token 攔截器,定義詳細認證規(guī)則
registry.addInterceptor(new SaInterceptor(handler -> {
// 指定一條 match 規(guī)則
SaRouter
.match("/**") // 攔截的 path 列表,可以寫多個 */
.notMatch("/user/doLogin") // 排除掉的 path 列表,可以寫多個
.check(r -> StpUtil.checkLogin()); // 要執(zhí)行的校驗動作,可以寫完整的 lambda 表達式
// 根據(jù)路由劃分模塊,不同模塊不同鑒權(quán)
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"));
SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment"));
})).addPathPatterns("/**");
}
}
SaRouter.match() 匹配函數(shù)有兩個參數(shù):要匹配的path路由,要執(zhí)行的校驗函數(shù)。
在校驗函數(shù)內(nèi)不只可以使用 StpUtil.checkPermission("xxx") 進行權(quán)限校驗,還可以寫任意代碼,例如:
// 甚至你可以隨意的寫一個打印語句
SaRouter.match("/**", r -> System.out.println("----啦啦啦----"));
// 連綴寫法
SaRouter.match("/**").check(r -> System.out.println("----啦啦啦----"));
1.8.3 匹配特征詳解
除了上述示例的 path 路由匹配,還可以根據(jù)很多其它特征進行匹配,以下是所有可匹配的特征:
// 基礎寫法樣例:匹配一個path,執(zhí)行一個校驗函數(shù)
SaRouter.match("/user/**").check(r -> StpUtil.checkLogin());
// 根據(jù) path 路由匹配 ——— 支持寫多個path,支持寫 restful 風格路由
// 功能說明: 使用 /user , /goods 或者 /art/get 開頭的任意路由都將進入 check 方法
SaRouter.match("/user/**", "/goods/**", "/art/get/{id}").check( /* 要執(zhí)行的校驗函數(shù) */ );
// 根據(jù) path 路由排除匹配
// 功能說明: 使用 .html , .css 或者 .js 結(jié)尾的任意路由都將跳過, 不會進入 check 方法
SaRouter.match("/**").notMatch("*.html", "*.css", "*.js").check( /* 要執(zhí)行的校驗函數(shù) */ );
// 根據(jù)請求類型匹配
SaRouter.match(SaHttpMethod.GET).check( /* 要執(zhí)行的校驗函數(shù) */ );
// 根據(jù)一個 boolean 條件進行匹配
SaRouter.match( StpUtil.isLogin() ).check( /* 要執(zhí)行的校驗函數(shù) */ );
// 根據(jù)一個返回 boolean 結(jié)果的lambda表達式匹配
SaRouter.match( r -> StpUtil.isLogin() ).check( /* 要執(zhí)行的校驗函數(shù) */ );
// 多個條件一起使用
// 功能說明: 必須是 Get 請求 并且 請求路徑以 `/user/` 開頭
SaRouter.match(SaHttpMethod.GET).match("/user/**").check( /* 要執(zhí)行的校驗函數(shù) */ );
// 可以無限連綴下去
// 功能說明: 同時滿足 Get 方式請求, 且路由以 /admin 開頭, 路由中間帶有 /send/ 字符串, 路由結(jié)尾不能是 .js 和 .css
SaRouter
.match(SaHttpMethod.GET)
.match("/admin/**")
.match("/**/send/**")
.notMatch("/**/*.js")
.notMatch("/**/*.css")
// ....
.check( /* 只有上述所有條件都匹配成功,才會執(zhí)行最后的check校驗函數(shù) */ );
1.8.4 提前退出匹配鏈
使用 SaRouter.stop() 可以提前退出匹配鏈,例:
registry.addInterceptor(new SaInterceptor(handler -> {
SaRouter.match("/**").check(r -> System.out.println("進入1"));
SaRouter.match("/**").check(r -> System.out.println("進入2")).stop();
SaRouter.match("/**").check(r -> System.out.println("進入3"));
SaRouter.match("/**").check(r -> System.out.println("進入4"));
SaRouter.match("/**").check(r -> System.out.println("進入5"));
})).addPathPatterns("/**");
如上示例,代碼運行至第2條匹配鏈時,會在stop函數(shù)處提前退出整個匹配函數(shù),從而忽略掉剩余的所有match匹配
除了stop()函數(shù),SaRouter還提供了 back() 函數(shù),用于:停止匹配,結(jié)束執(zhí)行,直接向前端返回結(jié)果
// 執(zhí)行back函數(shù)后將停止匹配,也不會進入Controller,而是直接將 back參數(shù) 作為返回值輸出到前端
SaRouter.match("/user/back").back("要返回到前端的內(nèi)容");
stop() 與 back() 函數(shù)的區(qū)別在于:
-
SaRouter.stop():會停止匹配,進入Controller。 -
SaRouter.back():會停止匹配,直接返回結(jié)果到前端。
1.8.5 使用free打開一個獨立的作用域
// 進入 free 獨立作用域
SaRouter.match("/**").free(r -> {
SaRouter.match("/a/**").check(/* --- */);
SaRouter.match("/b/**").check(/* --- */).stop();
SaRouter.match("/c/**").check(/* --- */);
});
// 執(zhí)行 stop() 函數(shù)跳出 free 后繼續(xù)執(zhí)行下面的 match 匹配
SaRouter.match("/**").check(/* --- */);
free() 的作用是:打開一個獨立的作用域,使內(nèi)部的 stop() 不再一次性跳出整個 Auth 函數(shù),而是僅僅跳出當前 free 作用域。
1.8.6 使用注解忽略掉路由攔截校驗
我們可以使用 @SaIgnore 注解,忽略掉路由攔截認證:
在 Controller 里添加了忽略校驗的注解
@SaIgnore
@RequestMapping("/user/getList")
public SaResult getList() {
System.out.println("------------ 訪問進來方法");
return SaResult.ok();
}
請求將會跳過攔截器的校驗,直接進入 Controller 的方法中
注意:此注解的忽略效果只針對 SaInterceptor攔截器 和 AOP注解鑒權(quán) 生效,對自定義攔截器與過濾器不生效。
1.9 Session
1.9.1 簡介
Session 是會話中專業(yè)的數(shù)據(jù)緩存組件,通過 Session 我們可以很方便的緩存一些高頻讀寫數(shù)據(jù),提高程序性能,例如:
// 在登錄時緩存 user 對象
StpUtil.getSession().set("user", user);
// 然后我們就可以在任意處使用這個 user 對象
SysUser user = (SysUser) StpUtil.getSession().get("user");
在 Sa-Token 中,Session 分為三種,分別是:
-
Account-Session: 指的是框架為每個 賬號id 分配的 Session -
Token-Session: 指的是框架為每個 token 分配的 Session -
Custom-Session:指的是以一個 特定的值 作為SessionId,來分配的 Session
1.9.2 Account-Session
有關 賬號-Session 的 API 如下:
// 獲取當前賬號 id 的 Account-Session (必須是登錄后才能調(diào)用)
StpUtil.getSession();
// 獲取當前賬號 id 的 Account-Session, 并決定在 Session 尚未創(chuàng)建時,是否新建并返回
StpUtil.getSession(true);
// 獲取賬號 id 為 10001 的 Account-Session
StpUtil.getSessionByLoginId(10001);
// 獲取賬號 id 為 10001 的 Account-Session, 并決定在 Session 尚未創(chuàng)建時,是否新建并返回
StpUtil.getSessionByLoginId(10001, true);
// 獲取 SessionId 為 xxxx-xxxx 的 Account-Session, 在 Session 尚未創(chuàng)建時, 返回 null
StpUtil.getSessionBySessionId("xxxx-xxxx");
復制到剪貼板錯誤復制成功
Token-Session
有關 令牌-Session 的 API 如下:
// 獲取當前 Token 的 Token-Session 對象
StpUtil.getTokenSession();
// 獲取指定 Token 的 Token-Session 對象
StpUtil.getTokenSessionByToken(token);
1.9.3 Custom-Session
自定義 Session 指的是以一個特定的值作為 SessionId 來分配的Session, 借助自定義Session,可以為系統(tǒng)中的任意元素分配相應的session
例如以商品 id 作為 key 為每個商品分配一個Session,以便于緩存和商品相關的數(shù)據(jù),其相關API如下:
// 查詢指定key的Session是否存在
SaSessionCustomUtil.isExists("goods-10001");
// 獲取指定key的Session,如果沒有,則新建并返回
SaSessionCustomUtil.getSessionById("goods-10001");
// 獲取指定key的Session,如果沒有,第二個參數(shù)決定是否新建并返回
SaSessionCustomUtil.getSessionById("goods-10001", false);
// 刪除指定key的Session
SaSessionCustomUtil.deleteSessionById("goods-10001");
1.9.4 在 Session 上存取值
以上三種 Session 均為框架設計概念上的區(qū)分,實際上在獲取它們時,返回的都是 SaSession 對象,你可以使用以下 API 在 SaSession 對象上存取值:
// 寫值
session.set("name", "zhang");
// 寫值 (只有在此key原本無值的時候才會寫入)
session.setDefaultValue("name", "zhang");
// 取值
session.get("name");
// 取值 (指定默認值)
session.get("name", "<defaultValue>");
// 取值 (若無值則執(zhí)行參數(shù)方法, 之后將結(jié)果保存到此鍵名下,并返回此結(jié)果 若有值則直接返回, 無需執(zhí)行參數(shù)方法)
session.get("name", () -> {
return ...;
});
// ---------- 數(shù)據(jù)類型轉(zhuǎn)換: ----------
session.getInt("age"); // 取值 (轉(zhuǎn)int類型)
session.getLong("age"); // 取值 (轉(zhuǎn)long類型)
session.getString("name"); // 取值 (轉(zhuǎn)String類型)
session.getDouble("result"); // 取值 (轉(zhuǎn)double類型)
session.getFloat("result"); // 取值 (轉(zhuǎn)float類型)
session.getModel("key", Student.class); // 取值 (指定轉(zhuǎn)換類型)
session.getModel("key", Student.class, <defaultValue>); // 取值 (指定轉(zhuǎn)換類型, 并指定值為Null時返回的默認值)
// 是否含有某個key (返回 true 或 false)
session.has("key");
// 刪值
session.delete('name');
// 清空所有值
session.clear();
// 獲取此 Session 的所有key (返回Set<String>)
session.keys();
1.10 框架配置
可以零配置啟動框架,但同時也可以通過一定的參數(shù)配置,定制性使用框架,Sa-Token支持多種方式配置框架信息
1.10.1 配置
1.10.1.1 yml配置
yaml 風格
############## Sa-Token 配置 (文檔: https://sa-token.cc) ##############
sa-token:
# token 名稱(同時也是 cookie 名稱)
token-name: satoken
# token 有效期(單位:秒) 默認30天,-1 代表永久有效
timeout: 2592000
# token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統(tǒng)就會被凍結(jié),默認-1 代表不限制,永不凍結(jié)
active-timeout: -1
# 是否允許同一賬號多地同時登錄 (為 true 時允許一起登錄, 為 false 時新登錄擠掉舊登錄)
is-concurrent: true
# 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token, 為 false 時每次登錄新建一個 token)
is-share: true
# token 風格(默認可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否輸出操作日志
is-log: true
1.10.1.2 通過代碼配置
模式 1
/**
* Sa-Token 配置類
*/
@Configuration
public class SaTokenConfigure {
// Sa-Token 參數(shù)配置,參考文檔:https://sa-token.cc
// 此配置會覆蓋 application.yml 中的配置
@Bean
@Primary
public SaTokenConfig getSaTokenConfigPrimary() {
SaTokenConfig config = new SaTokenConfig();
config.setTokenName("satoken"); // token 名稱(同時也是 cookie 名稱)
config.setTimeout(30 * 24 * 60 * 60); // token 有效期(單位:秒),默認30天,-1代表永不過期
config.setActiveTimeout(-1); // token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統(tǒng)就會被凍結(jié),默認-1 代表不限制,永不凍結(jié)
config.setIsConcurrent(true); // 是否允許同一賬號多地同時登錄(為 true 時允許一起登錄,為 false 時新登錄擠掉舊登錄)
config.setIsShare(true); // 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token,為 false 時每次登錄新建一個 token)
config.setTokenStyle("uuid"); // token 風格
config.setIsLog(false); // 是否輸出操作日志
return config;
}
}
模式 2
/**
* Sa-Token 配置類
*/
@Configuration
public class SaTokenConfigure {
// Sa-Token 參數(shù)配置,參考文檔:https://sa-token.cc
// 此配置會與 application.yml 中的配置合并 (代碼配置優(yōu)先)
@Autowired
public void configSaToken(SaTokenConfig config) {
config.setTokenName("satoken"); // token 名稱(同時也是 cookie 名稱)
config.setTimeout(30 * 24 * 60 * 60); // token 有效期(單位:秒),默認30天,-1代表永不過期
config.setActiveTimeout(-1); // token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統(tǒng)就會被凍結(jié),默認-1 代表不限制,永不凍結(jié)
config.setIsConcurrent(true); // 是否允許同一賬號多地同時登錄(為 true 時允許一起登錄,為 false 時新登錄擠掉舊登錄)
config.setIsShare(true); // 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token,為 false 時每次登錄新建一個 token)
config.setTokenStyle("uuid"); // token 風格
config.setIsLog(false); // 是否輸出操作日志
}
}
兩者的區(qū)別在于:
- 模式 1 會覆蓋
application.yml中的配置。 - 模式 2 會與
application.yml中的配置合并(代碼配置優(yōu)先)。
1.10.2 配置項
| 參數(shù)名稱 | 類型 | 默認值 | 說明 |
|---|---|---|---|
| tokenName | String | satoken | Token 名稱 (同時也是 Cookie 名稱、數(shù)據(jù)持久化前綴) |
| timeout | long | 2592000 | Token 有效期(單位:秒),默認30天,-1代表永不過期 |
| activeTimeout | long | -1 | Token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統(tǒng)就會被凍結(jié),默認-1 代表不限制,永不凍結(jié)(例如可以設置為1800代表30分鐘內(nèi)無操作就凍結(jié)) |
| dynamicActiveTimeout | Boolean | false | 是否啟用動態(tài) activeTimeout 功能,如不需要請設置為 false,節(jié)省緩存請求次數(shù) |
| isConcurrent | Boolean | true | 是否允許同一賬號并發(fā)登錄 (為 true 時允許一起登錄,為 false 時新登錄擠掉舊登錄) |
| isShare | Boolean | true | 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token,為 false 時每次登錄新建一個 token) |
| maxLoginCount | int | 12 | 同一賬號最大登錄數(shù)量,-1代表不限 (只有在 isConcurrent=true,isShare=false 時此配置才有效) |
| maxTryTimes | int | 12 | 在每次創(chuàng)建 Token 時的最高循環(huán)次數(shù),用于保證 Token 唯一性(-1=不循環(huán)重試,直接使用) |
| isReadBody | Boolean | true | 是否嘗試從 請求體 里讀取 Token |
| isReadHeader | Boolean | true | 是否嘗試從 header 里讀取 Token |
| isReadCookie | Boolean | true | 是否嘗試從 cookie 里讀取 Token,此值為 false 后,StpUtil.login(id) 登錄時也不會再往前端注入Cookie |
| isWriteHeader | Boolean | false | 是否在登錄后將 Token 寫入到響應頭 |
| tokenStyle | String | uuid token風格 | |
| dataRefreshPeriod | int | 30 | 默認數(shù)據(jù)持久組件實現(xiàn)類中,每次清理過期數(shù)據(jù)間隔的時間 (單位: 秒) ,默認值30秒,設置為-1代表不啟動定時清理 |
| tokenSessionCheckLogin | Boolean | true | 獲取 Token-Session 時是否必須登錄 (如果配置為true,會在每次獲取 Token-Session 時校驗是否登錄) |
| autoRenew | Boolean | true | 是否打開自動續(xù)簽 (如果此值為true,框架會在每次直接或間接調(diào)用 getLoginId() 時進行一次過期檢查與續(xù)簽操作) |
| tokenPrefix | String | null | token前綴,例如填寫 Bearer 實際傳參 satoken: Bearer xxxx-xxxx-xxxx-xxxx |
| isPrint | Boolean | true 是否在初始化配置時打印版本字符畫 | |
| isLog | Boolean | false | 是否打印操作日志 |
| logLevel | String | trace | 日志等級(trace、debug、info、warn、error、fatal),此值與 logLevelInt 聯(lián)動 |
| logLevelInt | int | 1 日志等級 int 值(1=trace、2=debug、3=info、4=warn、5=error、6=fatal),此值與 logLevel 聯(lián)動 | |
| isColorLog | Boolean | null | 是否打印彩色日志,true=打印彩色日志,false=打印黑白日志,null=框架根據(jù)運行終端自行判斷是否打印彩色日志 |
| jwtSecretKey | String | null | jwt秘鑰 (只有集成 sa-token-temp-jwt 模塊時此參數(shù)才會生效) |
| sameTokenTimeout | long 86400 | Same-Token的有效期 (單位: 秒) | |
| basic | String | "" | Http Basic 認證的賬號和密碼 |
| currDomain | String | null | 配置當前項目的網(wǎng)絡訪問地址 |
| checkSameToken | Boolean | false | 是否校驗Same-Token(部分rpc插件有效) |
| cookie | Object | new SaCookieConfig() | Cookie配置對象 |
Cookie相關配置:
| 參數(shù)名稱 | 類型 | 默認值 | 說明 |
|---|---|---|---|
| domain | String | null | 作用域(寫入Cookie時顯式指定的作用域, 常用于單點登錄二級域名共享Cookie的場景) |
| path | String | / | 路徑,默認寫在域名根路徑下 |
| secure | Boolean | false | 是否只在 https 協(xié)議下有效 |
| httpOnly | Boolean | false | 是否禁止 js 操作 Cookie |
| sameSite | String | Lax | 第三方限制級別(Strict=完全禁止,Lax=部分允許,None=不限制) |
Cookie 配置示例:
# Sa-Token 配置
sa-token:
# Cookie 相關配置
cookie:
domain: stp.com
path: /
secure: false
httpOnly: true
sameSite: Lax