對於一些登入之後才能存取的介面(例如:查詢我的帳號資料),我們通常的做法是增加一層介面校驗:
#如果校驗通過,則:正常回傳資料。
如果校驗未通過,則:拋出例外,告知其需要先登入。
那麼,判斷會話是否登入的依據是什麼?我們先來簡單分析一下登入存取流程:
使用者提交 name password 參數,呼叫登入介面。
登入成功,傳回這個使用者的 Token 會話憑證。
用戶後續的每個請求,都會帶著這個 Token。
伺服器根據 Token 判斷此會話是否登入成功。
所謂登入認證,指的就是伺服器校驗帳號密碼,為使用者頒發 Token 會話憑證的過程,這個 Token 也是我們後續判斷會話是否登入的關鍵。
動態圖示範:
接下來,我們將介紹在 SpringBoot 中如何使用 Sa-Token 完成登入認證作業。
Sa-Token 是一個 java 權限認證框架,主要解決登入認證、權限認證、單一登入、OAuth3、微服務閘道鑑權 等一系列權限相關問題。
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 即可。
根據上述思路,我們需要一個會話登入的函數:
// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等 StpUtil.login(Object id);
只此一句程式碼,便可以讓會話登入成功,實際上,Sa-Token 在背後做了大量的工作,包括但不限於:
檢查此帳號是否之前已有登入
為帳號產生Token 憑證與Session 會話
通知全域偵聽器,xx 帳號登入成功
// 会话登录接口 @RequestMapping("doLogin") public SaResult doLogin(String name, String pwd) { // 第一步:比对前端提交的账号名称、密码 if("zhang".equals(name) && "123456".equals(pwd)) { // 第二步:根据账号id,进行登录 StpUtil.login(10001); return SaResult.ok("登录成功"); } return SaResult.error("登录失败"); }如果你對上述程式碼閱讀沒有壓力,你可能會注意到略顯奇怪的一點:此處僅僅做了會話登錄,但並沒有主動向前端傳回Token 訊息。
是因為不需要嗎?嚴格來講是需要的,只不過 StpUtil.login(id) 方法利用了 Cookie 自動注入的特性,省略了你手寫回傳 Token 的程式碼。
// 当前会话注销登录 StpUtil.logout(); // 获取当前会话是否已经登录,返回true=已登录,false=未登录 StpUtil.isLogin(); // 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException` StpUtil.checkLogin();異常NotLoginException 代表目前會話暫未登錄,可能的原因有很多:
前端沒有提交Token、前端提交的Token 是無效的、前端提交的Token 已經過期…… 等等。
對應常數 | 意義說明 | |
---|---|---|
NotLoginException.NOT_TOKEN | 無法從要求讀取到Token | |
NotLoginException.INVALID_TOKEN | #已讀取到Token,但Token無效 | |
NotLoginException.TOKEN_TIMEOUT | ##已讀取到Token,但Token已經過期 | |
NotLoginException.BE_REPLACED | ##已已讀取到Token,但Token 已被頂下線-5 | |
已讀取到Token,但Token 已被踢下線 |
// 获取当前会话账号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信息参数 StpUtil.getTokenInfo();
{ "tokenName": "satoken", // token名称 "tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", // token值 "isLogin": true, // 此token是否已经登录 "loginId": "10001", // 此token对应的LoginId,未登录时为null "loginType": "login", // 账号类型标识 "tokenTimeout": 2591977, // token剩余有效期 (单位: 秒) "sessionTimeout": 2591977, // User-Session剩余有效时间 (单位: 秒) "tokenSessionTimeout": -2, // Token-Session剩余有效时间 (单位: 秒) (-2表示系统中不存在这个缓存) "tokenActivityTimeout": -1, // token剩余无操作有效时间 (单位: 秒) "loginDevice": "default-device" // 登录设备类型 }
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) { // 第一步:比对前端提交的 账号名称 & 密码 是否正确,比对成功后开始登录 // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对 if("zhang".equals(name) && "123456".equals(pwd)) { // 第二步:根据账号id,进行登录 // 此处填入的参数应该保持用户表唯一,比如用户id,不可以直接填入整个 User 对象 StpUtil.login(10001); // SaResult 是 Sa-Token 中对返回结果的简单封装,下面的示例将不再赘述 return SaResult.ok("登录成功"); } return SaResult.error("登录失败"); } // 查询当前登录状态 ---- http://localhost:8081/acc/isLogin @RequestMapping("isLogin") public SaResult isLogin() { // StpUtil.isLogin() 查询当前客户端是否登录,返回 true 或 false boolean isLogin = StpUtil.isLogin(); return SaResult.ok("当前客户端是否登录:" + isLogin); } // 校验当前登录状态 ---- http://localhost:8081/acc/checkLogin @RequestMapping("checkLogin") public SaResult checkLogin() { // 检验当前会话是否已经登录, 如果未登录,则抛出异常:`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() 触发登录校验效果,可以填入一个默认值 // 如果会话未登录,则返回这个默认值,如果会话已登录,将正常返回登录的账号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 类型 // 疑问:数据基本类型不是有八个吗,为什么只封装以上三种类型的转换? // 因为大多数项目都是拿 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 时应该使用的参数名称 String tokenName = StpUtil.getTokenName(); System.out.println("前端提交 Token 时应该使用的参数名称:" + tokenName); // 使用 StpUtil.getTokenValue() 获取前端提交的 Token 值 // 框架默认前端可以从以下三个途径中提交 Token: // Cookie (浏览器自动提交) // Header头 (代码手动提交) // Query 参数 (代码手动提交) 例如: /user/getInfo?satoken=xxxx-xxxx-xxxx-xxxx // 读取顺序为: Query 参数 --> Header头 -- > Cookie // 以上三个地方都读取不到 Token 信息的话,则视为前端没有提交 Token String tokenValue = StpUtil.getTokenValue(); System.out.println("前端提交的Token值为:" + tokenValue); // TokenInfo 包含了此 Token 的大多数信息 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() { // 退出登录会清除三个地方的数据: // 1、Redis中保存的 Token 信息 // 2、当前请求上下文中保存的 Token 信息 // 3、Cookie 中保存的 Token 信息(如果未使用Cookie模式则不会清除) StpUtil.logout(); // StpUtil.logout() 在未登录时也是可以调用成功的, // 也就是说,无论客户端有没有登录,执行完 StpUtil.logout() 后,都会处于未登录状态 System.out.println("当前是否处于登录状态:" + StpUtil.isLogin()); // 返回给前端 return SaResult.ok("退出登录成功"); } }
以上是SpringBoot怎麼使用Sa-Token實作登入認證的詳細內容。更多資訊請關注PHP中文網其他相關文章!