Springboot 如何使用 SaToken 進(jìn)行登錄認(rèn)證、權(quán)限管理及路由規(guī)則接口攔截
Springboot 使用 SaToken 進(jìn)行登錄認(rèn)證、權(quán)限管理以及路由規(guī)則接口攔截
前言
Sa-Token 是一個(gè)輕量級 Java 權(quán)限認(rèn)證框架,主要解決:登錄認(rèn)證、權(quán)限認(rèn)證、單點(diǎn)登錄、OAuth2.0、分布式Session會話、微服務(wù)網(wǎng)關(guān)鑒權(quán) 等一系列權(quán)限相關(guān)問題。
還有踢人下線、賬號封禁、路由攔截規(guī)則、微服務(wù)網(wǎng)關(guān)鑒權(quán)、密碼加密等豐富功能
它不比 Shiro 和 SpringSecurity 的功能少,而且配置使用更加簡單
一、引入和配置
先給你們看一下 Demo 文件結(jié)構(gòu)
1.引入依賴
如果不需要將 token 信息存入 redis,只需要引入下面這一個(gè)依賴
<dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.31.0</version> </dependency>
如果需要將 token 存入 redis,則還需要引入下面的依賴(一般搭建單點(diǎn)登錄服務(wù)器才需要使用 redis)
使用redis ,無需任何其他配置,只需要多引入下面幾個(gè)依賴,然后下面的 yml 加一些配置,satoken 就可以自動存儲到 redis,非常方便
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式)--> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-dao-redis-jackson</artifactId> <version>1.31.0</version> </dependency> <!-- Sa-Token插件:權(quán)限緩存與業(yè)務(wù)緩存分離 --> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-alone-redis</artifactId> <version>1.31.0</version> </dependency> <!-- 提供Redis連接池 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
2、配置yml
如下代碼,如果不需要使用 redis ,則刪除
alone-redis
和spring redis
配置,否則連接不到 redis 會報(bào)錯(cuò)
如果使用了 redis,我下面的配置是業(yè)務(wù)和鑒權(quán)分離的方式,也就是說,token 存儲在
alone-redis
里面配置的數(shù)據(jù)庫,我這里配置的是0
號數(shù)據(jù)庫,它和spring reids
配置的數(shù)據(jù)庫不沖突
server: port: 8081 # Sa-Token配置 sa-token: # token前綴 # Token前綴 與 Token值 之間必須有一個(gè)空格。 # 一旦配置了 Token前綴,則前端提交 Token 時(shí),必須帶有前綴,否則會導(dǎo)致框架無法讀取 Token。 # 由于Cookie中無法存儲空格字符,也就意味配置 Token 前綴后,Cookie 鑒權(quán)方式將會失效,此時(shí)只能將 Token 提交到header里進(jìn)行傳輸 # token-prefix: Bearer # token 名稱 (同時(shí)也是cookie名稱) token-name: satoken # token 有效期,單位s 默認(rèn)30天, -1代表永不過期 timeout: 2592000 # token 臨時(shí)有效期 (指定時(shí)間內(nèi)無操作就視為token過期) 單位: 秒 activity-timeout: -1 # 是否允許同一賬號并發(fā)登錄 (為true時(shí)允許一起登錄, 為false時(shí)新登錄擠掉舊登錄) is-concurrent: true # 在多人登錄同一賬號時(shí),是否共用一個(gè)token (為true時(shí)所有登錄共用一個(gè)token, 為false時(shí)每次登錄新建一個(gè)token) is-share: false # token風(fēng)格 token-style: uuid # 是否輸出操作日志 is-log: true # 配置 Sa-Token 單獨(dú)使用的 Redis 連接 alone-redis: # Redis數(shù)據(jù)庫索引(默認(rèn)為0) database: 0 # Redis服務(wù)器地址 host: 127.0.0.1 # Redis服務(wù)器連接端口 port: 6379 # Redis服務(wù)器連接密碼(默認(rèn)為空) password: # 連接超時(shí)時(shí)間 timeout: 10s spring: # 配置業(yè)務(wù)使用的 Redis 連接 redis: # Redis數(shù)據(jù)庫索引(默認(rèn)為0) database: 1 # Redis服務(wù)器地址 host: 127.0.0.1 # Redis服務(wù)器連接端口 port: 6379 # Redis服務(wù)器連接密碼(默認(rèn)為空) password: # 連接超時(shí)時(shí)間 timeout: 10s
3、配置全局異常處理
這一步可以不配置,配置的作用是,在鑒權(quán)失敗的時(shí)候,不會報(bào)錯(cuò),而是返回給前端鑒權(quán)失敗的原因,方便我們開發(fā)調(diào)試
下面的異常會在鑒權(quán)失敗的時(shí)候自動返回到前端,無需我們手動拋出和返回
package pers.xuyijie.satokendemo.exception; import cn.dev33.satoken.util.SaResult; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * @author 徐一杰 * @date 2022/9/23 16:45 * @description SaToken全局異常攔截 */ @RestControllerAdvice public class GlobalExceptionHandler { /** * 全局異常攔截,鑒權(quán)失敗不會報(bào)錯(cuò),會返回給前端報(bào)錯(cuò)原因 * @param e * @return */ @ExceptionHandler public SaResult handlerException(Exception e) { e.printStackTrace(); return SaResult.error(e.getMessage()); } }
4、模擬用戶角色和權(quán)限
這里我們給用戶分配一下我們模擬的角色和權(quán)限,正常你們要從數(shù)據(jù)庫讀取用戶的角色和擁有的權(quán)限
這里實(shí)現(xiàn)了 StpInterface 下面的方法,下面的方法會在接口鑒權(quán)之前自動調(diào)用,判斷角色和權(quán)限,無需我們手動調(diào)用
package pers.xuyijie.satokendemo.permission; import cn.dev33.satoken.stp.StpInterface; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * @author 徐一杰 * @date 2022/9/23 16:46 * @description 獲取當(dāng)前賬號的權(quán)限和角色列表,這個(gè)類下面的方法會在接口鑒權(quán)之前自動調(diào)用 */ @Component public class UserPermission implements StpInterface { /** * 返回一個(gè)賬號所擁有的權(quán)限碼集合 * 即你在調(diào)用 StpUtil.login(id) 時(shí)寫入的標(biāo)識值。 */ @Override public List<String> getPermissionList(Object loginId, String loginType) { // 本list僅做模擬,實(shí)際項(xiàng)目中要根據(jù)具體業(yè)務(wù)邏輯來查詢權(quán)限 List<String> list = new ArrayList<>(); list.add("1"); list.add("user-add"); list.add("user-delete"); list.add("user-update"); list.add("user-get"); list.add("article-get"); System.out.println("用戶權(quán)限列表:" + list); return list; } /** * 返回一個(gè)賬號所擁有的角色標(biāo)識集合 (權(quán)限與角色可分開校驗(yàn)) */ @Override public List<String> getRoleList(Object loginId, String loginType) { // 本list僅做模擬,實(shí)際項(xiàng)目中要根據(jù)具體業(yè)務(wù)邏輯來查詢角色 List<String> list = new ArrayList<>(); list.add("user"); list.add("admin"); list.add("super-admin"); System.out.println("用戶角色列表:" + list); return list; } }
5、配置攔截器
如果在高版本 SpringBoot (≥2.6.x) 下注冊攔截器失效,則需要添加 @EnableWebMvc 注解才可以使用
下面我們配置的規(guī)則叫作
路由攔截規(guī)則
,/user/**
意思就是接口地址為/user
開頭的所有接口,也就是說,下面的我們UserController
里面的所有接口都在攔截范圍內(nèi)
package pers.xuyijie.satokendemo.config; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.interceptor.SaInterceptor; import cn.dev33.satoken.router.SaRouter; import cn.dev33.satoken.stp.StpUtil; import org.springframework.boot.SpringBootConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author 徐一杰 * @date 2022/9/23 16:49 * @description */ @SpringBootConfiguration @EnableWebMvc public class SaTokenConfigure implements WebMvcConfigurer { /** * 注冊 Sa-Token 攔截器,打開注解式鑒權(quán)功能 * 如果在高版本 SpringBoot (≥2.6.x) 下注冊攔截器失效,則需要額外添加 @EnableWebMvc 注解才可以使用 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { // 注冊路由攔截器,自定義認(rèn)證規(guī)則 registry.addInterceptor(new SaInterceptor(handler -> { // 登錄認(rèn)證 -- 攔截所有路由,并排除/user/doLogin 用于開放登錄 SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin()); // 角色認(rèn)證 -- 攔截以 admin 開頭的路由,必須具備 admin 角色或者 super-admin 角色才可以通過認(rèn)證 SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin")); // 權(quán)限認(rèn)證 -- 不同模塊認(rèn)證不同權(quán)限 SaRouter.match("/user/**", r -> StpUtil.checkRole("user")); SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin")); // 甚至你可以隨意的寫一個(gè)打印語句 SaRouter.match("/**", r -> System.out.println("--------權(quán)限認(rèn)證成功-------")); }).isAnnotation(true)) //攔截所有接口 .addPathPatterns("/**") //不攔截/user/doLogin登錄接口 .excludePathPatterns("/user/doLogin"); } }
6、controller里調(diào)用satoken的方法
方法上面的注解是使用權(quán)限認(rèn)證和攔截器的時(shí)候用的,下面我會講到
我在下面的
UserController
演示了登錄
、注銷
、檢查是否登錄
、查看用戶token
、獲取token有效期
、對稱加密
、非對稱加密
方法,具體的方法每一行代碼的作用,都在注視中寫出來了,等一下我們測試每一個(gè)方法,為大家展示運(yùn)行結(jié)果并解析代碼
package pers.xuyijie.satokendemo.controller; import cn.dev33.satoken.annotation.SaIgnore; import cn.dev33.satoken.basic.SaBasicUtil; import cn.dev33.satoken.secure.SaBase64Util; import cn.dev33.satoken.secure.SaSecureUtil; import cn.dev33.satoken.stp.SaLoginModel; import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; /** * @author 徐一杰 * @date 2022/9/23 15:52 * @description */ @RestController @RequestMapping("/user") public class UserController { private static final String USERNAME = "xyj"; private static final String PASSWORD = "123456"; /** * 測試登錄 * @param username * @param password * @return */ @RequestMapping("/doLogin") public SaResult doLogin(String username, String password) { //這個(gè)方法會強(qiáng)制在瀏覽器彈出一個(gè)認(rèn)證框 SaBasicUtil.check("sa:123456"); if(username.equals(USERNAME) && password.equals(PASSWORD)) { //這個(gè)是登錄用戶的主鍵,業(yè)務(wù)中你要從數(shù)據(jù)庫中讀取 StpUtil.login(1); //獲取登錄生成的token tokenInfo = StpUtil.getTokenInfo(); System.out.println(tokenInfo); return SaResult.ok("登錄成功,會話ID為 " + StpUtil.getLoginId() + " ,Token為:" + StpUtil.getTokenValue()); } return SaResult.error("登錄失敗"); } /** * 查詢登錄狀態(tài) * @return */ @RequestMapping("/signOut") public SaResult signOut() { String loginId = null; if (StpUtil.isLogin()){ loginId = (String) StpUtil.getLoginId(); StpUtil.logout(); } return SaResult.ok("會話ID為 " + loginId + " 的用戶注銷登錄成功"); } /** * 查詢登錄狀態(tài) * @return */ @RequestMapping("/isLogin") public SaResult isLogin() { if (StpUtil.isLogin()){ return SaResult.ok("會話是否登錄:" + StpUtil.isLogin() + " ,會話ID為 " + StpUtil.getLoginId()); } return SaResult.ok("會話是否登錄:" + StpUtil.isLogin()); } /** * 根據(jù)Token值獲取對應(yīng)的賬號id,如果未登錄,則返回 null * @param tokenValue * @return */ @RequestMapping("/getUserByToken/{tokenValue}") public SaResult getUserByToken(@PathVariable String tokenValue){ return SaResult.ok((String) StpUtil.getLoginIdByToken(tokenValue)); } /** * 獲取當(dāng)前會話剩余有效期(單位:s,返回-1代表永久有效) * @return */ @RequestMapping("/getTokenTimeout") public SaResult getTokenTimeout(){ return SaResult.ok(String.valueOf(StpUtil.getTokenTimeout())); } @SaIgnore @RequestMapping("/encodePassword") public void encodePassword() throws Exception { /** * md5加鹽加密: md5(md5(str) + md5(salt)) */ String md5 = SaSecureUtil.md5("123456"); String md5BySalt = SaSecureUtil.md5BySalt("123456", "salt"); System.out.println("MD5加密:" + md5); System.out.println("MD5加鹽加密:" + md5BySalt); /** * AES對稱加密 */ // 定義秘鑰和明文 String key = "123456"; String text = "這是一個(gè)明文用于測試AES對稱加密"; // 加密 String ciphertext = SaSecureUtil.aesEncrypt(key, text); System.out.println("AES加密后:" + ciphertext); // 解密 String text2 = SaSecureUtil.aesDecrypt(key, ciphertext); System.out.println("AES解密后:" + text2); /** * RSA非對稱加密 */ // 定義私鑰和公鑰 HashMap<String, String> keyMap = SaSecureUtil.rsaGenerateKeyPair(); String privateKey = keyMap.get("private"); String publicKey = keyMap.get("public"); // 文本 String text1 = "這是一個(gè)明文用于測試RSA非對稱加密"; // 使用公鑰加密 String ciphertext1 = SaSecureUtil.rsaEncryptByPublic(publicKey, text1); System.out.println("公鑰加密后:" + ciphertext1); // 使用私鑰解密 String text3 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext1); System.out.println("私鑰解密后:" + text3); /** * Base64 */ // 文本 String text4 = "這是一個(gè)明文用于測試Base64"; // 使用Base64編碼 String base64Text = SaBase64Util.encode(text4); System.out.println("Base64編碼后:" + base64Text); // 使用Base64解碼 String text5 = SaBase64Util.decode(base64Text); System.out.println("Base64解碼后:" + text5); } }
下面的
TestController
里面等下演示權(quán)限認(rèn)證和路由攔截的時(shí)候用
package pers.xuyijie.satokendemo.controller; import cn.dev33.satoken.annotation.*; import cn.dev33.satoken.util.SaResult; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author 徐一杰 * @date 2022/9/23 16:38 * @description */ @RestController @RequestMapping("/test") @SaCheckLogin public class TestController { /** * 此接口加上了 @SaIgnore 可以游客訪問 * @return */ @SaIgnore @RequestMapping("/getList") public SaResult getList() { return SaResult.ok("無需登錄接口"); } /** * 登陸后才可調(diào)用該方法 * @return */ @SaCheckLogin @RequestMapping("/select") public SaResult select(){ return SaResult.ok("查詢成功"); } /** * 必須具有指定權(quán)限才能進(jìn)入該方法 * @return */ @SaCheckRole("super-admin") @RequestMapping("/delete") public SaResult delete() { return SaResult.ok("刪除成功"); } /** * 注解式鑒權(quán):SaMode.OR 只要具有其中一個(gè)權(quán)限即可通過校驗(yàn) * @return */ @RequestMapping("/add") @SaCheckPermission(value = {"user-add", "user-all"}, mode = SaMode.OR) public SaResult add() { return SaResult.ok("添加成功"); } /** * 一個(gè)接口在具有權(quán)限 user-update 或角色 admin 時(shí)可以調(diào)通 * @return */ @RequestMapping("/update") @SaCheckPermission(value = "user-add", orRole = "admin") public SaResult update() { return SaResult.ok("更新成功"); } /** * 這個(gè)接口測試用 * @return */ @RequestMapping("/testPermission") @SaCheckPermission(value = "user123") public SaResult testPermission() { return SaResult.ok("這個(gè)接口測試用"); } }
二、登錄演示
到這里,我么前期的配置就已經(jīng)結(jié)束了,下面我開始測試每一個(gè)方法,為大家展示運(yùn)行結(jié)果并解析代碼,先把項(xiàng)目運(yùn)行起來
1、登錄-doLogin
大家請看,我在請求
/doLogin
這個(gè)接口的時(shí)候,彈出了下面的認(rèn)證框,這個(gè)就是方法第一行代碼的功能,這叫Basic認(rèn)證
,當(dāng)然可以不要這一行代碼,隨你們,認(rèn)證賬號 sa 密碼 123456
//這個(gè)方法會強(qiáng)制在瀏覽器彈出一個(gè)認(rèn)證框 SaBasicUtil.check("sa:123456");
進(jìn)行 Basic認(rèn)證
后,登陸成功
我這里使用了 redis ,token 已經(jīng)存儲進(jìn)來了
2、驗(yàn)證登錄-isLogin
3、獲取token時(shí)效-getTokenTimeout
這是我們在 yml 里面配置的時(shí)效性,30天的毫秒
4、加密
請求 encodePassword
接口
5、注銷登錄-logout
三、權(quán)限認(rèn)證和攔截器演示
下面演示上面我們配置的攔截器,satoken 可以直接使用注解來進(jìn)行攔截,很方便
我們可以發(fā)現(xiàn),我在兩個(gè) controller 里面使用了 satoken 的幾個(gè)注解,注解可以用在方法上或類上
@SaIgnore
忽略該方法,不進(jìn)行任何攔截和鑒權(quán)@SaCheckLogin
登錄后才可以調(diào)用該接口@SaCheckRole("super-admin")
登錄用戶必須要是"super-admin"角色才可調(diào)用@SaCheckPermission(value = "del")
登錄用戶必須要有"del"權(quán)限才可調(diào)用
1、登錄認(rèn)證
下面我們調(diào)用添加了
@SaCheckLogin
注解的方法
(1) 未登錄情況
(2) 已登陸情況
可以看到調(diào)用成功,控制臺打印出我們上面攔截器配置的輸出信息
2、權(quán)限認(rèn)證
登錄后,我們調(diào)用增加了
@SaCheckPermission(value = "del")
注解的方法,可以看到提示無此權(quán)限:del
,因?yàn)榍懊嫖覀兡M用戶權(quán)限時(shí),沒有給用戶分配del
權(quán)限
我們再調(diào)用增加了
@SaCheckRole("super-admin")
注解的方法,可以看到成功
總結(jié)
到此這篇關(guān)于Springboot 使用 SaToken 進(jìn)行登錄認(rèn)證、權(quán)限管理以及路由規(guī)則接口攔截的文章就介紹到這了,更多相關(guān)Springboot SaToken登錄認(rèn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)字符串轉(zhuǎn)String數(shù)組的方法示例
這篇文章主要介紹了java實(shí)現(xiàn)字符串轉(zhuǎn)String數(shù)組的方法,涉及java字符串的遍歷、分割、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10Google?Kaptcha驗(yàn)證碼生成的使用實(shí)例說明
這篇文章主要為大家介紹了Google?Kaptcha驗(yàn)證碼的使用實(shí)例說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03eclipse實(shí)現(xiàn)可認(rèn)證的DH密鑰交換協(xié)議
這篇文章主要介紹了eclipse實(shí)現(xiàn)可認(rèn)證的DH密鑰交換協(xié)議,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06面試題:java中為什么foreach中不允許對元素進(jìn)行add和remove
讀者遇到了一個(gè)比較經(jīng)典的面試題,也就是標(biāo)題上說的,為什么 foreach 中不允許對元素進(jìn)行 add 和 remove,本文就詳細(xì)的介紹一下,感興趣的可以了解一下2021-10-10解決springboot啟動Logback報(bào)錯(cuò)ERROR in ch.qos.logback.cla
這篇文章主要介紹了解決springboot啟動Logback報(bào)錯(cuò)ERROR in ch.qos.logback.classic.joran.action.ContextNameAction - Failed to rena問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04java?實(shí)現(xiàn)獲取指定位置后的第一個(gè)數(shù)字
這篇文章主要介紹了java?實(shí)現(xiàn)獲取指定位置后的第一個(gè)數(shù)字,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01SpringBoot2.1 RESTful API項(xiàng)目腳手架(種子)項(xiàng)目
這篇文章主要介紹了SpringBoot2.1 RESTful API項(xiàng)目腳手架(種子)項(xiàng)目,用于搭建RESTful API工程的腳手架,只需三分鐘你就可以開始編寫業(yè)務(wù)代碼,不再煩惱于構(gòu)建項(xiàng)目與風(fēng)格統(tǒng)一,感興趣的小伙伴們可以參考一下2018-12-12SpringBoot部署在tomcat容器中運(yùn)行的部署方法
這篇文章主要介紹了SpringBoot部署在tomcat容器中運(yùn)行的部署方法,需要的朋友可以參考下2018-10-10