浙江建設廳網(wǎng)站官網(wǎng)seo關鍵詞排名系統(tǒng)
文章目錄
- 1. Sa-Token 介紹
- 2. 登錄認證
- 2.1 登錄與注銷
- 2.2 會話查詢
- 2.3 Token 查詢
- 3. 權限認證
- 3.1 獲取當前賬號權限碼集合
- 3.2 權限校驗
- 3.3 角色校驗
- 4. 前后臺分離(無Cookie模式)
- 5. Sa-Token 集成 Redis
- 6. SpringBoot 集成 Sa-Token
- 6.1 創(chuàng)建項目
- 6.2 添加依賴
- 6.3 設置配置文件
- 6.4 創(chuàng)建啟動類
- 6.5 定義用戶信息類
- 6.6 自定義權限驗證接口擴展
- 6.7 創(chuàng)建測試Controller
- 6.8 運行
1. Sa-Token 介紹
Sa-Token 是一個輕量級 Java 權限認證框架,主要解決:登錄認證、權限認證、單點登錄、OAuth2.0、分布式Session會話、微服務網(wǎng)關鑒權 等一系列權限相關問題。
功能結構圖
2. 登錄認證
對于一些登錄之后才能訪問的接口(例如:查詢我的賬號資料),我們通常的做法是增加一層接口校驗:
- 如果校驗通過,則:正常返回數(shù)據(jù)。
- 如果校驗未通過,則:拋出異常,告知其需要先進行登錄。
那么,判斷會話是否登錄的依據(jù)是什么?我們先來簡單分析一下登錄訪問流程:
- 用戶提交
name
+password
參數(shù),調(diào)用登錄接口。 - 登錄成功,返回這個用戶的
Token
會話憑證。 - 用戶后續(xù)的每次請求,都攜帶上這個
Token
。 - 服務器根據(jù)
Token
判斷此會話是否登錄成功。
所謂登錄認證,指的就是服務器校驗賬號密碼,為用戶頒發(fā) Token
會話憑證的過程,這個Token
也是我們后續(xù)判斷會話是否登錄的關鍵所在。
2.1 登錄與注銷
// 會話登錄:參數(shù)填寫要登錄的賬號id,建議的數(shù)據(jù)類型:long | int | String, 不可以傳入復雜類型,如:User、Admin 等等
StpUtil.login(Object id);
只此一句代碼,便可以使會話登錄成功,實際上,Sa-Token 在背后做了大量的工作,包括但不限于:
- 檢查此賬號是否之前已有登錄
- 為賬號生成
Token
憑證與Session
會話 - 通知全局偵聽器,xx 賬號登錄成功
- 將
Token
注入到請求上下文 - 等等其它工作……
只需要記住關鍵一點:Sa-Token 為這個賬號創(chuàng)建了一個Token憑證,且通過 Cookie 上下文返回給了前端
。
此處僅僅做了會話登錄,但并沒有主動向前端返回 Token
信息。嚴格來講是需要的,只不過 StpUtil.login(id)
方法利用了 Cookie
自動注入的特性,省略了你手寫返回 Token
的代碼。
// 當前會話注銷登錄
StpUtil.logout();// 獲取當前會話是否已經(jīng)登錄,返回true=已登錄,false=未登錄
StpUtil.isLogin();// 檢驗當前會話是否已經(jīng)登錄, 如果未登錄,則拋出異常:`NotLoginException`
StpUtil.checkLogin();
2.2 會話查詢
// 獲取當前會話賬號id, 如果未登錄,則拋出異常:`NotLoginException`
StpUtil.getLoginId();// 類似查詢API還有:
StpUtil.getLoginIdAsString(); // 獲取當前會話賬號id, 并轉化為`String`類型
StpUtil.getLoginIdAsInt(); // 獲取當前會話賬號id, 并轉化為`int`類型
StpUtil.getLoginIdAsLong(); // 獲取當前會話賬號id, 并轉化為`long`類型// ---------- 指定未登錄情形下返回的默認值 ----------// 獲取當前會話賬號id, 如果未登錄,則返回null
StpUtil.getLoginIdDefaultNull();// 獲取當前會話賬號id, 如果未登錄,則返回默認值 (`defaultValue`可以為任意類型)
StpUtil.getLoginId(T defaultValue);
2.3 Token 查詢
// 獲取當前會話的token值
StpUtil.getTokenValue();// 獲取當前`StpLogic`的token名稱
StpUtil.getTokenName();// 獲取指定token對應的賬號id,如果未登錄,則返回 null
StpUtil.getLoginIdByToken(String tokenValue);// 獲取當前會話剩余有效期(單位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();// 獲取當前會話的token信息參數(shù)
StpUtil.getTokenInfo();
3. 權限認證
所謂權限認證,核心邏輯就是判斷一個賬號是否擁有指定權限:
- 有,就讓你通過。
- 沒有?那么禁止訪問!
深入到底層數(shù)據(jù)中,就是每個賬號都會擁有一個權限碼集合,框架來校驗這個集合中是否包含指定的權限碼。
例如:當前賬號擁有權限碼集合 ["user-add", "user-delete", "user-get"]
,這時候我來校驗權限 "user-update"
,則其結果就是:驗證失敗,禁止訪問。
3.1 獲取當前賬號權限碼集合
因為每個項目的需求不同,其權限設計也千變?nèi)f化,因此 [ 獲取當前賬號權限碼集合 ] 這一操作不可能內(nèi)置到框架中, 所以 Sa-Token 將此操作以接口的方式暴露給你,以方便你根據(jù)自己的業(yè)務邏輯進行重寫。
你需要做的就是新建一個類,實現(xiàn) StpInterface
接口,例如以下代碼:
/*** 自定義權限驗證接口擴展*/
@Component // 保證此類被SpringBoot掃描,完成Sa-Token的自定義權限驗證擴展
public class StpInterfaceImpl implements StpInterface {/*** 返回一個賬號所擁有的權限碼集合 */@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {// 本list僅做模擬,實際項目中要根據(jù)具體業(yè)務邏輯來查詢權限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;}/*** 返回一個賬號所擁有的角色標識集合 (權限與角色可分開校驗)*/@Overridepublic 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;}
}
參數(shù)解釋:
loginId
:賬號id,即你在調(diào)用 StpUtil.login(id)
時寫入的標識值。
loginType
:賬號體系標識,此處暫時忽略,可以詳細了解 [ 多賬戶認證 ]
3.2 權限校驗
然后就可以用以下api來鑒權了
// 獲取:當前賬號所擁有的權限集合
StpUtil.getPermissionList();// 判斷:當前賬號是否含有指定權限, 返回 true 或 false
StpUtil.hasPermission("user.add"); // 校驗:當前賬號是否含有指定權限, 如果驗證未通過,則拋出異常: NotPermissionException
StpUtil.checkPermission("user.add"); // 校驗:當前賬號是否含有指定權限 [指定多個,必須全部驗證通過]
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get"); // 校驗:當前賬號是否含有指定權限 [指定多個,只要其一驗證通過即可]
StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");
3.3 角色校驗
在Sa-Token中,角色和權限可以獨立驗證
// 獲取:當前賬號所擁有的角色集合
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");
權限通配符
Sa-Token允許你根據(jù)通配符指定泛權限,例如當一個賬號擁有art.*
的權限時,art.add、art.delete、art.update
都將匹配通過
上帝權限:當一個賬號擁有
"*"
權限時,他可以驗證通過任何權限碼 (角色認證同理)
前端有了鑒權后端還需要鑒權嗎?
需要!
前端的鑒權只是一個輔助功能,對于專業(yè)人員這些限制都是可以輕松繞過的,為保證服務器安全,無論前端是否進行了權限校驗,后端接口都需要對會話請求再次進行權限校驗!
4. 前后臺分離(無Cookie模式)
何為無 Cookie
模式?
無 Cookie
模式:特指不支持 Cookie
功能的終端,通俗來講就是我們常說的 —— 前后臺分離模式。
- 后端將
token
返回到前端
首先調(diào)用 StpUtil.login(id)
進行登錄。
調(diào)用 StpUtil.getTokenInfo()
返回當前會話的 token
詳細參數(shù)。
- 此方法返回一個對象,其有兩個關鍵屬性:
tokenName
和tokenValue
(token 的名稱和 token 的值)。 - 將此對象傳遞到前臺,讓前端人員將這兩個值保存到本地。
- 前端將
token
提交到后端
- 無論是app還是小程序,其傳遞方式都大同小異。
- 那就是,將 token 塞到請求
header
里 ,格式為:{tokenName: tokenValue}
。
- 只要按照如此方法將
token
值傳遞到后端,Sa-Token 就能像傳統(tǒng)PC端一樣自動讀取到 token 值,進行鑒權。 - 你可能會有疑問,難道我每個
ajax
都要寫這么一坨?豈不是麻煩死了?
你當然不能每個 ajax 都寫這么一坨,因為這種重復性代碼都是要封裝在一個函數(shù)里統(tǒng)一調(diào)用的。
5. Sa-Token 集成 Redis
Sa-Token 默認將數(shù)據(jù)保存在內(nèi)存中,此模式讀寫速度最快,且避免了序列化與反序列化帶來的性能消耗,但是此模式也有一些缺點,比如:
- 重啟后數(shù)據(jù)會丟失。
- 無法在分布式環(huán)境中共享數(shù)據(jù)。
為此,Sa-Token 提供了擴展接口,你可以輕松將會話數(shù)據(jù)存儲在 Redis
、Memcached
等專業(yè)的緩存中間件中, 做到重啟數(shù)據(jù)不丟失,而且保證分布式環(huán)境下多節(jié)點的會話一致性。
以下是官方提供的 Redis 集成包:
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-dao-redis-jackson</artifactId><version>1.34.0</version>
</dependency>
集成 Redis 請注意:
- 無論使用哪種序列化方式,你都必須為項目提供一個 Redis 實例化方案,例如:
<!-- 提供Redis連接池 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>
- 配置Redis 連接信息
spring: # redis配置 redis:# Redis數(shù)據(jù)庫索引(默認為0)database: 0# Redis服務器地址host: 127.0.0.1# Redis服務器連接端口port: 6379# Redis服務器連接密碼(默認為空)# password: # 連接超時時間timeout: 10slettuce:pool:# 連接池最大連接數(shù)max-active: 200# 連接池最大阻塞等待時間(使用負值表示沒有限制)max-wait: -1ms# 連接池中的最大空閑連接max-idle: 10# 連接池中的最小空閑連接min-idle: 0
-
集成
Redis
后,是我額外手動保存數(shù)據(jù),還是框架自動保存?
框架自動保存。集成Redis
只需要引入對應的pom
依賴 即可,框架所有上層 API 保持不變。 -
集成包版本問題
Sa-Token-Redis
集成包的版本盡量與Sa-Token-Starter
集成包的版本一致,否則可能出現(xiàn)兼容性問題。
6. SpringBoot 集成 Sa-Token
以最新版本 1.34.0為例
6.1 創(chuàng)建項目
在 IDE 中新建一個 SpringBoot 項目,例如:sa-token-demo-springboot
6.2 添加依賴
在項目中添加依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token 權限認證,在線文檔:https://sa-token.cc -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.34.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-dao-redis-jackson</artifactId><version>1.34.0</version>
</dependency>
<!-- 提供Redis連接池 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
6.3 設置配置文件
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: falsespring:# redis配置redis:# Redis數(shù)據(jù)庫索引(默認為0)database: 0# Redis服務器地址host: 127.0.0.1# Redis服務器連接端口port: 6379# Redis服務器連接密碼(默認為空)password: abc123# 連接超時時間timeout: 10slettuce:pool:# 連接池最大連接數(shù)max-active: 200# 連接池最大阻塞等待時間(使用負值表示沒有限制)max-wait: -1ms# 連接池中的最大空閑連接max-idle: 10# 連接池中的最小空閑連接min-idle: 0
6.4 創(chuàng)建啟動類
@SpringBootApplication
public class SaTokenDemoApplication {public static void main(String[] args) throws JsonProcessingException {SpringApplication.run(SaTokenDemoApplication.class, args);System.out.println("啟動成功:Sa-Token配置如下:" + SaManager.getConfig());}
}
6.5 定義用戶信息類
@Data
@Accessors(chain = true)
@AllArgsConstructor
public class UserInfo {private Long userId;private String name;private String email;
}
6.6 自定義權限驗證接口擴展
@Component // 保證此類被SpringBoot掃描,完成Sa-Token的自定義權限驗證擴展
public class StpInterfaceImpl implements StpInterface {/*** 返回一個賬號所擁有的權限碼集合*/@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {// 本list僅做模擬,實際項目中要根據(jù)具體業(yè)務邏輯來查詢權限
// List<String> list = new ArrayList<String>();
// list.add("user.add");
// list.add("user.get");// list.add("user.delete");
// list.add("art.*");//從redis中獲取權限List<String> authList = (List<String>) StpUtil.getSession().get("authList");return authList;}/*** 返回一個賬號所擁有的角色標識集合 (權限與角色可分開校驗)* @param loginId 賬號id,即你在調(diào)用 StpUtil.login(id) 時寫入的標識值* @param loginType 賬號體系標識* @author: yh* @date: 2023/2/12*/@Overridepublic 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;}
}
6.7 創(chuàng)建測試Controller
@RestController
@RequestMapping("/user")
public class UserController {/*** 測試登錄,瀏覽器訪問: http://localhost:8081/user/doLogin?username=zhang&password=123456*/@RequestMapping("/doLogin")public SaResult doLogin(String username, String password) {// 此處僅作模擬示例,真實項目需要從數(shù)據(jù)庫中查詢數(shù)據(jù)進行比對 if("zhang".equals(username) && "123456".equals(password)) {// 第1步,先登錄上StpUtil.login(10001);}//第2步,加載用戶信息和權限信息StpUtil.getSession().set("loginInfo", new UserInfo(10001L, "張三", "123123@163.com"));//加載權限,只給user.add的權限List<String> authList = new ArrayList<String>();authList.add("user.add");StpUtil.getSession().set("authList", authList);// 第3步,獲取 Token 相關參數(shù)SaTokenInfo tokenInfo = StpUtil.getTokenInfo();// 第4步,返回給前端return SaResult.data(tokenInfo);}/*** 查詢登錄狀態(tài)*/@RequestMapping("/isLogin")public String isLogin() {return "當前會話是否登錄:" + StpUtil.isLogin();}/*** 獲取用戶信息*/@RequestMapping("/getUserInfo")public UserInfo getUserInfo() {UserInfo loginInfo = (UserInfo) StpUtil.getSession().get("loginInfo");return loginInfo;}/*** 測試方法校驗權限*/@GetMapping(value = "/add")public String add(){StpUtil.checkPermission("user.add");return "ok";}@GetMapping(value = "/update")public String update(){StpUtil.checkPermission("user.update");return "ok";}
}
6.8 運行
啟動項目,Copy Configuration
再啟動一個實例,這時候我們就同時啟動了兩個實例
這樣就可以測試出在集群部署情況下登錄信息會不會有問題,然后用接口測試工具依次訪問上述測試接口
1、 登錄
http://localhost:8081/user/doLogin?username=zhang&password=123456
此時查看redis
中數(shù)據(jù),可以看到登錄信息和權限都保存在redis中了
2、 查詢登錄狀態(tài)
http://localhost:8081/user/isLogin
注意header
里沒有cookie
把登錄接口返回的tokenName
和tokenValue
加入到請求header
中
只有請求攜帶對應的token
,登錄狀態(tài)才為:true
3、 獲取用戶信息
這里調(diào)用端口為8082
的實例
http://localhost:8082/user/getUserInfo
4、 調(diào)用添加用戶接口
測試是否有添加接口權限,登錄的時候我們賦予了添加用戶的權限
http://localhost:8082/user/add
5、 調(diào)用更新用接口
測試是否有更新用戶接口權限,登錄的時候我們沒有賦予更新用戶的權限
http://localhost:8082/user/update
結果可以看到?jīng)]有更新用戶接口的權限。
我的博客即將同步至騰訊云開發(fā)者社區(qū),邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=rrez71s8jyqy