您好,登錄后才能下訂單哦!
這篇文章主要講解了“SpringBoot怎么使用Sa-Token實(shí)現(xiàn)登錄認(rèn)證”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“SpringBoot怎么使用Sa-Token實(shí)現(xiàn)登錄認(rèn)證”吧!
對于一些登錄之后才能訪問的接口(例如:查詢我的賬號資料),我們通常的做法是增加一層接口校驗(yàn):
如果校驗(yàn)通過,則:正常返回?cái)?shù)據(jù)。
如果校驗(yàn)未通過,則:拋出異常,告知其需要先進(jìn)行登錄。
那么,判斷會(huì)話是否登錄的依據(jù)是什么?我們先來簡單分析一下登錄訪問流程:
用戶提交 name + password 參數(shù),調(diào)用登錄接口。
登錄成功,返回這個(gè)用戶的 Token 會(huì)話憑證。
用戶后續(xù)的每次請求,都攜帶上這個(gè) Token。
服務(wù)器根據(jù) Token 判斷此會(huì)話是否登錄成功。
所謂登錄認(rèn)證,指的就是服務(wù)器校驗(yàn)賬號密碼,為用戶頒發(fā) Token 會(huì)話憑證的過程,這個(gè) Token 也是我們后續(xù)判斷會(huì)話是否登錄的關(guān)鍵所在。
動(dòng)態(tài)圖演示:
接下來,我們將介紹在 SpringBoot 中如何使用 Sa-Token 完成登錄認(rèn)證操作。
Sa-Token 是一個(gè) java 權(quán)限認(rèn)證框架,主要解決登錄認(rèn)證、權(quán)限認(rèn)證、單點(diǎn)登錄、OAuth3、微服務(wù)網(wǎng)關(guān)鑒權(quán) 等一系列權(quán)限相關(guān)問題。
Gitee 開源地址:https://gitee.com/dromara/sa-token
首先在項(xiàng)目中引入 Sa-Token 依賴:
<!-- Sa-Token 權(quán)限認(rèn)證 --> <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ù)以上思路,我們需要一個(gè)會(huì)話登錄的函數(shù):
// 會(huì)話登錄:參數(shù)填寫要登錄的賬號id,建議的數(shù)據(jù)類型:long | int | String, 不可以傳入復(fù)雜類型,如:User、Admin 等等 StpUtil.login(Object id);
只此一句代碼,便可以使會(huì)話登錄成功,實(shí)際上,Sa-Token 在背后做了大量的工作,包括但不限于:
檢查此賬號是否之前已有登錄
為賬號生成 Token 憑證與 Session 會(huì)話
通知全局偵聽器,xx 賬號登錄成功
將 Token 注入到請求上下文
等等其它工作……
你暫時(shí)不需要完整的了解整個(gè)登錄過程,你只需要記住關(guān)鍵一點(diǎn):Sa-Token 為這個(gè)賬號創(chuàng)建了一個(gè)Token憑證,且通過 Cookie 上下文返回給了前端。
所以一般情況下,我們的登錄接口代碼,會(huì)大致類似如下:
// 會(huì)話登錄接口 @RequestMapping("doLogin") public SaResult doLogin(String name, String pwd) { // 第一步:比對前端提交的賬號名稱、密碼 if("zhang".equals(name) && "123456".equals(pwd)) { // 第二步:根據(jù)賬號id,進(jìn)行登錄 StpUtil.login(10001); return SaResult.ok("登錄成功"); } return SaResult.error("登錄失敗"); }
如果你對以上代碼閱讀沒有壓力,你可能會(huì)注意到略顯奇怪的一點(diǎn):此處僅僅做了會(huì)話登錄,但并沒有主動(dòng)向前端返回 Token 信息。
是因?yàn)椴恍枰獑???yán)格來講是需要的,只不過 StpUtil.login(id) 方法利用了 Cookie 自動(dòng)注入的特性,省略了你手寫返回 Token 的代碼。
如果你對 Cookie 功能還不太了解,也不用擔(dān)心,我們會(huì)在之后的 [ 前后端分離 ] 章節(jié)中詳細(xì)的闡述 Cookie 功能,現(xiàn)在你只需要了解最基本的兩點(diǎn):
Cookie 可以從后端控制往瀏覽器中寫入 Token 值。
Cookie 會(huì)在前端每次發(fā)起請求時(shí)自動(dòng)提交 Token 值。
因此,在 Cookie 功能的加持下,我們可以僅靠 StpUtil.login(id) 一句代碼就完成登錄認(rèn)證。
除了登錄方法,我們還需要:
// 當(dāng)前會(huì)話注銷登錄 StpUtil.logout(); // 獲取當(dāng)前會(huì)話是否已經(jīng)登錄,返回true=已登錄,false=未登錄 StpUtil.isLogin(); // 檢驗(yàn)當(dāng)前會(huì)話是否已經(jīng)登錄, 如果未登錄,則拋出異常:`NotLoginException` StpUtil.checkLogin();
異常 NotLoginException 代表當(dāng)前會(huì)話暫未登錄,可能的原因有很多:
前端沒有提交 Token、前端提交的 Token 是無效的、前端提交的 Token 已經(jīng)過期 …… 等等。
Sa-Token 未登錄場景值參照表:
場景值 | 對應(yīng)常量 | 含義說明 |
---|---|---|
-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 已被踢下線 |
那么,如何獲取場景值呢?廢話少說直接上代碼:
// 全局異常攔截(攔截項(xiàng)目中的NotLoginException異常) @ExceptionHandler(NotLoginException.class) public SaResult handlerNotLoginException(NotLoginException nle) throws Exception { // 打印堆棧,以供調(diào)試 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 = "當(dāng)前會(huì)話未登錄"; } // 返回給前端 return SaResult.error(message); }
注意:以上代碼并非處理邏輯的最佳方式,只為以最簡單的代碼演示出場景值的獲取與應(yīng)用,大家可以根據(jù)自己的項(xiàng)目需求來定制化處理
// 獲取當(dāng)前會(huì)話賬號id, 如果未登錄,則拋出異常:`NotLoginException` StpUtil.getLoginId(); // 類似查詢API還有: StpUtil.getLoginIdAsString(); // 獲取當(dāng)前會(huì)話賬號id, 并轉(zhuǎn)化為`String`類型 StpUtil.getLoginIdAsInt(); // 獲取當(dāng)前會(huì)話賬號id, 并轉(zhuǎn)化為`int`類型 StpUtil.getLoginIdAsLong(); // 獲取當(dāng)前會(huì)話賬號id, 并轉(zhuǎn)化為`long`類型 // ---------- 指定未登錄情形下返回的默認(rèn)值 ---------- // 獲取當(dāng)前會(huì)話賬號id, 如果未登錄,則返回null StpUtil.getLoginIdDefaultNull(); // 獲取當(dāng)前會(huì)話賬號id, 如果未登錄,則返回默認(rèn)值 (`defaultValue`可以為任意類型) StpUtil.getLoginId(T defaultValue);
// 獲取當(dāng)前會(huì)話的token值 StpUtil.getTokenValue(); // 獲取當(dāng)前`StpLogic`的token名稱 StpUtil.getTokenName(); // 獲取指定token對應(yīng)的賬號id,如果未登錄,則返回 null StpUtil.getLoginIdByToken(String tokenValue); // 獲取當(dāng)前會(huì)話剩余有效期(單位:s,返回-1代表永久有效) StpUtil.getTokenTimeout(); // 獲取當(dāng)前會(huì)話的token信息參數(shù) StpUtil.getTokenInfo();
TokenInfo 是 Token 信息 Model,用來描述一個(gè) Token 的常用參數(shù):
{ "tokenName": "satoken", // token名稱 "tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", // token值 "isLogin": true, // 此token是否已經(jīng)登錄 "loginId": "10001", // 此token對應(yīng)的LoginId,未登錄時(shí)為null "loginType": "login", // 賬號類型標(biāo)識(shí) "tokenTimeout": 2591977, // token剩余有效期 (單位: 秒) "sessionTimeout": 2591977, // User-Session剩余有效時(shí)間 (單位: 秒) "tokenSessionTimeout": -2, // Token-Session剩余有效時(shí)間 (單位: 秒) (-2表示系統(tǒng)中不存在這個(gè)緩存) "tokenActivityTimeout": -1, // token剩余無操作有效時(shí)間 (單位: 秒) "loginDevice": "default-device" // 登錄設(shè)備類型 }
新建 LoginAuthController,復(fù)制以下代碼
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 登錄認(rèn)證示例 * * @author kong * @since 2022-10-13 */ @RestController @RequestMapping("/acc/") public class LoginAuthController { // 會(huì)話登錄接口 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456 @RequestMapping("doLogin") public SaResult doLogin(String name, String pwd) { // 第一步:比對前端提交的 賬號名稱 & 密碼 是否正確,比對成功后開始登錄 // 此處僅作模擬示例,真實(shí)項(xiàng)目需要從數(shù)據(jù)庫中查詢數(shù)據(jù)進(jìn)行比對 if("zhang".equals(name) && "123456".equals(pwd)) { // 第二步:根據(jù)賬號id,進(jìn)行登錄 // 此處填入的參數(shù)應(yīng)該保持用戶表唯一,比如用戶id,不可以直接填入整個(gè) User 對象 StpUtil.login(10001); // SaResult 是 Sa-Token 中對返回結(jié)果的簡單封裝,下面的示例將不再贅述 return SaResult.ok("登錄成功"); } return SaResult.error("登錄失敗"); } // 查詢當(dāng)前登錄狀態(tài) ---- http://localhost:8081/acc/isLogin @RequestMapping("isLogin") public SaResult isLogin() { // StpUtil.isLogin() 查詢當(dāng)前客戶端是否登錄,返回 true 或 false boolean isLogin = StpUtil.isLogin(); return SaResult.ok("當(dāng)前客戶端是否登錄:" + isLogin); } // 校驗(yàn)當(dāng)前登錄狀態(tài) ---- http://localhost:8081/acc/checkLogin @RequestMapping("checkLogin") public SaResult checkLogin() { // 檢驗(yàn)當(dāng)前會(huì)話是否已經(jīng)登錄, 如果未登錄,則拋出異常:`NotLoginException` StpUtil.checkLogin(); // 拋出異常后,代碼將走入全局異常處理(GlobalException.java),如果沒有拋出異常,則代表通過了登錄校驗(yàn),返回下面信息 return SaResult.ok("校驗(yàn)登錄成功,這行字符串是只有登錄后才會(huì)返回的信息"); } // 獲取當(dāng)前登錄的賬號是誰 ---- http://localhost:8081/acc/getLoginId @RequestMapping("getLoginId") public SaResult getLoginId() { // 需要注意的是,StpUtil.getLoginId() 自帶登錄校驗(yàn)效果 // 也就是說如果在未登錄的情況下調(diào)用這句代碼,框架就會(huì)拋出 `NotLoginException` 異常,效果和 StpUtil.checkLogin() 是一樣的 Object userId = StpUtil.getLoginId(); System.out.println("當(dāng)前登錄的賬號id是:" + userId); // 如果不希望 StpUtil.getLoginId() 觸發(fā)登錄校驗(yàn)效果,可以填入一個(gè)默認(rèn)值 // 如果會(huì)話未登錄,則返回這個(gè)默認(rèn)值,如果會(huì)話已登錄,將正常返回登錄的賬號id Object userId2 = StpUtil.getLoginId(0); System.out.println("當(dāng)前登錄的賬號id是:" + userId2); // 或者使其在未登錄的時(shí)候返回 null Object userId3 = StpUtil.getLoginIdDefaultNull(); System.out.println("當(dāng)前登錄的賬號id是:" + userId3); // 類型轉(zhuǎn)換: // StpUtil.getLoginId() 返回的是 Object 類型,你可以使用以下方法指定其返回的類型 int userId4 = StpUtil.getLoginIdAsInt(); // 將返回值轉(zhuǎn)換為 int 類型 long userId5 = StpUtil.getLoginIdAsLong(); // 將返回值轉(zhuǎn)換為 long 類型 String userId6 = StpUtil.getLoginIdAsString(); // 將返回值轉(zhuǎn)換為 String 類型 // 疑問:數(shù)據(jù)基本類型不是有八個(gè)嗎,為什么只封裝以上三種類型的轉(zhuǎn)換? // 因?yàn)榇蠖鄶?shù)項(xiàng)目都是拿 int、long 或 String 聲明 UserId 的類型的,實(shí)在沒見過哪個(gè)項(xiàng)目用 double、float、boolean 之類來聲明 UserId System.out.println("當(dāng)前登錄的賬號id是:" + userId4 + " --- " + userId5 + " --- " + userId6); // 返回給前端 return SaResult.ok("當(dāng)前客戶端登錄的賬號id是:" + userId); } // 查詢 Token 信息 ---- http://localhost:8081/acc/tokenInfo @RequestMapping("tokenInfo") public SaResult tokenInfo() { // TokenName 是 Token 名稱的意思,此值也決定了前端提交 Token 時(shí)應(yīng)該使用的參數(shù)名稱 String tokenName = StpUtil.getTokenName(); System.out.println("前端提交 Token 時(shí)應(yīng)該使用的參數(shù)名稱:" + tokenName); // 使用 StpUtil.getTokenValue() 獲取前端提交的 Token 值 // 框架默認(rèn)前端可以從以下三個(gè)途徑中提交 Token: // Cookie (瀏覽器自動(dòng)提交) // Header頭 (代碼手動(dòng)提交) // Query 參數(shù) (代碼手動(dòng)提交) 例如: /user/getInfo?satoken=xxxx-xxxx-xxxx-xxxx // 讀取順序?yàn)椋?nbsp;Query 參數(shù) --> Header頭 -- > Cookie // 以上三個(gè)地方都讀取不到 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("當(dāng)前是否登錄:" + info.getIsLogin()); System.out.println("當(dāng)前登錄的賬號id:" + info.getLoginId()); System.out.println("當(dāng)前登錄賬號的類型:" + info.getLoginType()); System.out.println("當(dāng)前登錄客戶端的設(shè)備類型:" + info.getLoginDevice()); System.out.println("當(dāng)前 Token 的剩余有效期:" + info.getTokenTimeout()); // 單位:秒,-1代表永久有效,-2代表值不存在 System.out.println("當(dāng)前 Token 的剩余臨時(shí)有效期:" + info.getTokenActivityTimeout()); // 單位:秒,-1代表永久有效,-2代表值不存在 System.out.println("當(dāng)前 User-Session 的剩余有效期" + info.getSessionTimeout()); // 單位:秒,-1代表永久有效,-2代表值不存在 System.out.println("當(dāng)前 Token-Session 的剩余有效期" + info.getTokenSessionTimeout()); // 單位:秒,-1代表永久有效,-2代表值不存在 // 返回給前端 return SaResult.data(StpUtil.getTokenInfo()); } // 會(huì)話注銷 ---- http://localhost:8081/acc/logout @RequestMapping("logout") public SaResult logout() { // 退出登錄會(huì)清除三個(gè)地方的數(shù)據(jù): // 1、Redis中保存的 Token 信息 // 2、當(dāng)前請求上下文中保存的 Token 信息 // 3、Cookie 中保存的 Token 信息(如果未使用Cookie模式則不會(huì)清除) StpUtil.logout(); // StpUtil.logout() 在未登錄時(shí)也是可以調(diào)用成功的, // 也就是說,無論客戶端有沒有登錄,執(zhí)行完 StpUtil.logout() 后,都會(huì)處于未登錄狀態(tài) System.out.println("當(dāng)前是否處于登錄狀態(tài):" + StpUtil.isLogin()); // 返回給前端 return SaResult.ok("退出登錄成功"); } }
感謝各位的閱讀,以上就是“SpringBoot怎么使用Sa-Token實(shí)現(xiàn)登錄認(rèn)證”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對SpringBoot怎么使用Sa-Token實(shí)現(xiàn)登錄認(rèn)證這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。