SpringBoot使用Sa-Token實現(xiàn)登錄認證
一、設計思路
對于一些登錄之后才能訪問的接口(例如:查詢我的賬號資料),我們通常的做法是增加一層接口校驗:
- 如果校驗通過,則:正常返回數(shù)據(jù)。
- 如果校驗未通過,則:拋出異常,告知其需要先進行登錄。
那么,判斷會話是否登錄的依據(jù)是什么?我們先來簡單分析一下登錄訪問流程:
- 用戶提交 name + password 參數(shù),調用登錄接口。
- 登錄成功,返回這個用戶的 Token 會話憑證。
- 用戶后續(xù)的每次請求,都攜帶上這個 Token。
- 服務器根據(jù) Token 判斷此會話是否登錄成功。
所謂登錄認證,指的就是服務器校驗賬號密碼,為用戶頒發(fā) Token 會話憑證的過程,這個 Token 也是我們后續(xù)判斷會話是否登錄的關鍵所在。
動態(tài)圖演示:
接下來,我們將介紹在 SpringBoot 中如何使用 Sa-Token 完成登錄認證操作。
Sa-Token 是一個 java 權限認證框架,主要解決登錄認證、權限認證、單點登錄、OAuth2、微服務網(wǎng)關鑒權 等一系列權限相關問題。
Gitee 開源地址:https://gitee.com/dromara/sa-token
首先在項目中引入 Sa-Token 依賴:
<!-- Sa-Token 權限認證 --> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.34.0</version> </dependency>
注:如果你使用的是 SpringBoot 3.x,只需要將 sa-token-spring-boot-starter 修改為 sa-token-spring-boot3-starter 即可。
二、登錄與注銷
根據(jù)以上思路,我們需要一個會話登錄的函數(shù):
// 會話登錄:參數(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 上下文返回給了前端。
所以一般情況下,我們的登錄接口代碼,會大致類似如下:
// 會話登錄接口 @RequestMapping("doLogin") public SaResult doLogin(String name, String pwd) { // 第一步:比對前端提交的賬號名稱、密碼 if("zhang".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)過期 …… 等等。
Sa-Token 未登錄場景值參照表:
場景值 | 對應常量 | 含義說明 |
---|---|---|
-1 | NotLoginException.NOT_TOKEN | 未能從請求中讀取到 Token |
-2 | NotLoginException.INVALID_TOKEN | 已讀取到 Token,但是 Token無效 |
-3 | NotLoginException.TOKEN_TIMEOUT | 已讀取到 Token,但是 Token已經(jīng)過期 |
-4 | NotLoginException.BE_REPLACED | 已讀取到 Token,但是 Token 已被頂下線 |
-5 | NotLoginException.KICK_OUT | 已讀取到 Token,但是 Token 已被踢下線 |
那么,如何獲取場景值呢?廢話少說直接上代碼:
// 全局異常攔截(攔截項目中的NotLoginException異常) @ExceptionHandler(NotLoginException.class) public SaResult handlerNotLoginException(NotLoginException nle) ?? ??? ?throws Exception { ?? ?// 打印堆棧,以供調試 ?? ?nle.printStackTrace();? ?? ? ?? ?// 判斷場景值,定制化異常信息? ?? ?String message = ""; ?? ?if(nle.getType().equals(NotLoginException.NOT_TOKEN)) { ?? ??? ?message = "未提供token"; ?? ?} ?? ?else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) { ?? ??? ?message = "token無效"; ?? ?} ?? ?else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) { ?? ??? ?message = "token已過期"; ?? ?} ?? ?else if(nle.getType().equals(NotLoginException.BE_REPLACED)) { ?? ??? ?message = "token已被頂下線"; ?? ?} ?? ?else if(nle.getType().equals(NotLoginException.KICK_OUT)) { ?? ??? ?message = "token已被踢下線"; ?? ?} ?? ?else { ?? ??? ?message = "當前會話未登錄"; ?? ?} ?? ? ?? ?// 返回給前端 ?? ?return SaResult.error(message); }
注意:以上代碼并非處理邏輯的最佳方式,只為以最簡單的代碼演示出場景值的獲取與應用,大家可以根據(jù)自己的項目需求來定制化處理
三、會話查詢
// 獲取當前會話賬號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);
四、Token 查詢
// 獲取當前會話的token值 StpUtil.getTokenValue(); // 獲取當前`StpLogic`的token名稱 StpUtil.getTokenName(); // 獲取指定token對應的賬號id,如果未登錄,則返回 null StpUtil.getLoginIdByToken(String tokenValue); // 獲取當前會話剩余有效期(單位:s,返回-1代表永久有效) StpUtil.getTokenTimeout(); // 獲取當前會話的token信息參數(shù) StpUtil.getTokenInfo();
TokenInfo 是 Token 信息 Model,用來描述一個 Token 的常用參數(shù):
{ "tokenName": "satoken", // token名稱 "tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", // token值 "isLogin": true, // 此token是否已經(jīng)登錄 "loginId": "10001", // 此token對應的LoginId,未登錄時為null "loginType": "login", // 賬號類型標識 "tokenTimeout": 2591977, // token剩余有效期 (單位: 秒) "sessionTimeout": 2591977, // User-Session剩余有效時間 (單位: 秒) "tokenSessionTimeout": -2, // Token-Session剩余有效時間 (單位: 秒) (-2表示系統(tǒng)中不存在這個緩存) "tokenActivityTimeout": -1, // token剩余無操作有效時間 (單位: 秒) "loginDevice": "default-device" // 登錄設備類型 }
五、來個小測試,加深一下理解
新建 LoginAuthController,復制以下代碼
package com.pj.cases.use; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; /** ?* Sa-Token 登錄認證示例? ?*? ?* @author kong ?* @since 2022-10-13 ?*/ @RestController @RequestMapping("/acc/") public class LoginAuthController { ?? ?// 會話登錄接口 ?---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456 ?? ?@RequestMapping("doLogin") ?? ?public SaResult doLogin(String name, String pwd) { ?? ??? ? ?? ??? ?// 第一步:比對前端提交的 賬號名稱 & 密碼 是否正確,比對成功后開始登錄? ?? ??? ?// ?? ??? ?此處僅作模擬示例,真實項目需要從數(shù)據(jù)庫中查詢數(shù)據(jù)進行比對? ?? ??? ?if("zhang".equals(name) && "123456".equals(pwd)) { ?? ??? ??? ? ?? ??? ??? ?// 第二步:根據(jù)賬號id,進行登錄? ?? ??? ??? ?// ?? ??? ?此處填入的參數(shù)應該保持用戶表唯一,比如用戶id,不可以直接填入整個 User 對象? ?? ??? ??? ?StpUtil.login(10001); ?? ??? ??? ? ?? ??? ??? ?// SaResult 是 Sa-Token 中對返回結果的簡單封裝,下面的示例將不再贅述? ?? ??? ??? ?return SaResult.ok("登錄成功"); ?? ??? ?} ?? ??? ? ?? ??? ?return SaResult.error("登錄失敗"); ?? ?} ?? ?// 查詢當前登錄狀態(tài) ?---- http://localhost:8081/acc/isLogin ?? ?@RequestMapping("isLogin") ?? ?public SaResult isLogin() { ?? ??? ?// StpUtil.isLogin() 查詢當前客戶端是否登錄,返回 true 或 false? ?? ??? ?boolean isLogin = StpUtil.isLogin(); ?? ??? ?return SaResult.ok("當前客戶端是否登錄:" + isLogin); ?? ?} ?? ?// 校驗當前登錄狀態(tài) ?---- http://localhost:8081/acc/checkLogin ?? ?@RequestMapping("checkLogin") ?? ?public SaResult checkLogin() { ?? ??? ?// 檢驗當前會話是否已經(jīng)登錄, 如果未登錄,則拋出異常:`NotLoginException` ?? ??? ?StpUtil.checkLogin(); ?? ??? ?// 拋出異常后,代碼將走入全局異常處理(GlobalException.java),如果沒有拋出異常,則代表通過了登錄校驗,返回下面信息? ?? ??? ?return SaResult.ok("校驗登錄成功,這行字符串是只有登錄后才會返回的信息"); ?? ?} ?? ?// 獲取當前登錄的賬號是誰 ?---- http://localhost:8081/acc/getLoginId ?? ?@RequestMapping("getLoginId") ?? ?public SaResult getLoginId() { ?? ??? ?// 需要注意的是,StpUtil.getLoginId() 自帶登錄校驗效果 ?? ??? ?// 也就是說如果在未登錄的情況下調用這句代碼,框架就會拋出 `NotLoginException` 異常,效果和 StpUtil.checkLogin() 是一樣的? ?? ??? ?Object userId = StpUtil.getLoginId(); ?? ??? ?System.out.println("當前登錄的賬號id是:" + userId); ?? ??? ? ?? ??? ?// 如果不希望 StpUtil.getLoginId() 觸發(fā)登錄校驗效果,可以填入一個默認值 ?? ??? ?// 如果會話未登錄,則返回這個默認值,如果會話已登錄,將正常返回登錄的賬號id? ?? ??? ?Object userId2 = StpUtil.getLoginId(0); ?? ??? ?System.out.println("當前登錄的賬號id是:" + userId2); ?? ??? ? ?? ??? ?// 或者使其在未登錄的時候返回 null? ?? ??? ?Object userId3 = StpUtil.getLoginIdDefaultNull(); ?? ??? ?System.out.println("當前登錄的賬號id是:" + userId3); ?? ??? ? ?? ??? ?// 類型轉換: ?? ??? ?// StpUtil.getLoginId() 返回的是 Object 類型,你可以使用以下方法指定其返回的類型? ?? ??? ?int userId4 = StpUtil.getLoginIdAsInt(); ?// 將返回值轉換為 int 類型? ?? ??? ?long userId5 = StpUtil.getLoginIdAsLong(); ?// 將返回值轉換為 long 類型? ?? ??? ?String userId6 = StpUtil.getLoginIdAsString(); ?// 將返回值轉換為 String 類型? ?? ??? ? ?? ??? ?// 疑問:數(shù)據(jù)基本類型不是有八個嗎,為什么只封裝以上三種類型的轉換? ?? ??? ?// 因為大多數(shù)項目都是拿 int、long 或 String 聲明 UserId 的類型的,實在沒見過哪個項目用 double、float、boolean 之類來聲明 UserId? ?? ??? ?System.out.println("當前登錄的賬號id是:" + userId4 + " --- " + userId5 + " --- " + userId6); ?? ??? ? ?? ??? ?// 返回給前端? ?? ??? ?return SaResult.ok("當前客戶端登錄的賬號id是:" + userId); ?? ?} ?? ?// 查詢 Token 信息 ?---- http://localhost:8081/acc/tokenInfo ?? ?@RequestMapping("tokenInfo") ?? ?public SaResult tokenInfo() { ?? ??? ?// TokenName 是 Token 名稱的意思,此值也決定了前端提交 Token 時應該使用的參數(shù)名稱? ?? ??? ?String tokenName = StpUtil.getTokenName(); ?? ??? ?System.out.println("前端提交 Token 時應該使用的參數(shù)名稱:" + tokenName); ?? ??? ? ?? ??? ?// 使用 StpUtil.getTokenValue() 獲取前端提交的 Token 值? ?? ??? ?// 框架默認前端可以從以下三個途徑中提交 Token: ?? ??? ?// ?? ??? ?Cookie ?? ??? ?(瀏覽器自動提交) ?? ??? ?// ?? ??? ?Header頭?? ?(代碼手動提交) ?? ??? ?// ?? ??? ?Query 參數(shù)?? ?(代碼手動提交) 例如: /user/getInfo?satoken=xxxx-xxxx-xxxx-xxxx? ?? ??? ?// 讀取順序為: Query 參數(shù) --> Header頭 -- > Cookie? ?? ??? ?// 以上三個地方都讀取不到 Token 信息的話,則視為前端沒有提交 Token? ?? ??? ?String tokenValue = StpUtil.getTokenValue(); ?? ??? ?System.out.println("前端提交的Token值為:" + tokenValue); ?? ??? ? ?? ??? ?// TokenInfo 包含了此 Token 的大多數(shù)信息? ?? ??? ?SaTokenInfo info = StpUtil.getTokenInfo(); ?? ??? ?System.out.println("Token 名稱:" + info.getTokenName()); ?? ??? ?System.out.println("Token 值:" + info.getTokenValue()); ?? ??? ?System.out.println("當前是否登錄:" + info.getIsLogin()); ?? ??? ?System.out.println("當前登錄的賬號id:" + info.getLoginId()); ?? ??? ?System.out.println("當前登錄賬號的類型:" + info.getLoginType()); ?? ??? ?System.out.println("當前登錄客戶端的設備類型:" + info.getLoginDevice()); ?? ??? ?System.out.println("當前 Token 的剩余有效期:" + info.getTokenTimeout()); // 單位:秒,-1代表永久有效,-2代表值不存在 ?? ??? ?System.out.println("當前 Token 的剩余臨時有效期:" + info.getTokenActivityTimeout()); // 單位:秒,-1代表永久有效,-2代表值不存在 ?? ??? ?System.out.println("當前 User-Session 的剩余有效期" + info.getSessionTimeout()); // 單位:秒,-1代表永久有效,-2代表值不存在 ?? ??? ?System.out.println("當前 Token-Session 的剩余有效期" + info.getTokenSessionTimeout()); // 單位:秒,-1代表永久有效,-2代表值不存在 ?? ??? ? ?? ??? ?// 返回給前端? ?? ??? ?return SaResult.data(StpUtil.getTokenInfo()); ?? ?} ?? ? ?? ?// 會話注銷 ?---- http://localhost:8081/acc/logout ?? ?@RequestMapping("logout") ?? ?public SaResult logout() { ?? ??? ?// 退出登錄會清除三個地方的數(shù)據(jù): ?? ??? ?// ?? ??? ?1、Redis中保存的 Token 信息 ?? ??? ?// ?? ??? ?2、當前請求上下文中保存的 Token 信息? ?? ??? ?// ?? ??? ?3、Cookie 中保存的 Token 信息(如果未使用Cookie模式則不會清除) ?? ??? ?StpUtil.logout(); ?? ??? ? ?? ??? ?// StpUtil.logout() 在未登錄時也是可以調用成功的, ?? ??? ?// 也就是說,無論客戶端有沒有登錄,執(zhí)行完 StpUtil.logout() 后,都會處于未登錄狀態(tài)? ?? ??? ?System.out.println("當前是否處于登錄狀態(tài):" + StpUtil.isLogin()); ?? ??? ? ?? ??? ?// 返回給前端? ?? ??? ?return SaResult.ok("退出登錄成功"); ?? ?} ?? ? }
代碼注釋已針對每一步操作做出詳細解釋,大家可根據(jù)可參照注釋中的訪問鏈接進行逐步測試。
本示例代碼已上傳至 Gitee,可參考:
參考資料
Gitee 倉庫地址:https://gitee.com/dromara/sa-token
GitHub 倉庫地址:https://github.com/dromara/sa-token
Sa-Token 在線文檔:https://sa-token.dev33.cn/
到此這篇關于SpringBoot使用Sa-Token實現(xiàn)登錄認證的文章就介紹到這了,更多相關SpringBoot Sa-Token登錄認證內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- 基于SpringBoot整合oauth2實現(xiàn)token認證
- springboot+jwt實現(xiàn)token登陸權限認證的實現(xiàn)
- SpringBoot和Redis實現(xiàn)Token權限認證的實例講解
- SpringBoot整合Sa-Token實現(xiàn)登錄認證的示例代碼
- SpringBoot整合token實現(xiàn)登錄認證的示例代碼
- SpringBoot使用Sa-Token實現(xiàn)權限認證
- 在SpringBoot中使用jwt實現(xiàn)token身份認證的實例代碼
- Springboot微服務分布式框架Rouyi Cloud權限認證(登錄流程之token解析)
- Springboot 如何使用 SaToken 進行登錄認證、權限管理及路由規(guī)則接口攔截
- springBoot整合jwt實現(xiàn)token令牌認證的示例代碼
相關文章
通過MyBatis讀取數(shù)據(jù)庫數(shù)據(jù)并提供rest接口訪問
這篇文章主要介紹了通過MyBatis讀取數(shù)據(jù)庫數(shù)據(jù)并提供rest接口訪問 的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-08-08IDEA2022創(chuàng)建Maven Web項目教程(圖文)
本文主要介紹了IDEA2022創(chuàng)建Maven Web項目教程,文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07