Springboot 如何使用 SaToken 進行登錄認證、權(quán)限管理及路由規(guī)則接口攔截
Springboot 使用 SaToken 進行登錄認證、權(quán)限管理以及路由規(guī)則接口攔截
前言
Sa-Token 是一個輕量級 Java 權(quán)限認證框架,主要解決:登錄認證、權(quán)限認證、單點登錄、OAuth2.0、分布式Session會話、微服務網(wǎng)關鑒權(quán) 等一系列權(quán)限相關問題。
還有踢人下線、賬號封禁、路由攔截規(guī)則、微服務網(wǎng)關鑒權(quán)、密碼加密等豐富功能
它不比 Shiro 和 SpringSecurity 的功能少,而且配置使用更加簡單
一、引入和配置
先給你們看一下 Demo 文件結(jié)構(gòu)
1.引入依賴
如果不需要將 token 信息存入 redis,只需要引入下面這一個依賴
<dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.31.0</version> </dependency>
如果需要將 token 存入 redis,則還需要引入下面的依賴(一般搭建單點登錄服務器才需要使用 redis)
使用redis ,無需任何其他配置,只需要多引入下面幾個依賴,然后下面的 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è)務緩存分離 --> <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 會報錯
如果使用了 redis,我下面的配置是業(yè)務和鑒權(quán)分離的方式,也就是說,token 存儲在
alone-redis
里面配置的數(shù)據(jù)庫,我這里配置的是0
號數(shù)據(jù)庫,它和spring reids
配置的數(shù)據(jù)庫不沖突
server: port: 8081 # Sa-Token配置 sa-token: # token前綴 # Token前綴 與 Token值 之間必須有一個空格。 # 一旦配置了 Token前綴,則前端提交 Token 時,必須帶有前綴,否則會導致框架無法讀取 Token。 # 由于Cookie中無法存儲空格字符,也就意味配置 Token 前綴后,Cookie 鑒權(quán)方式將會失效,此時只能將 Token 提交到header里進行傳輸 # token-prefix: Bearer # 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: false # token風格 token-style: uuid # 是否輸出操作日志 is-log: true # 配置 Sa-Token 單獨使用的 Redis 連接 alone-redis: # Redis數(shù)據(jù)庫索引(默認為0) database: 0 # Redis服務器地址 host: 127.0.0.1 # Redis服務器連接端口 port: 6379 # Redis服務器連接密碼(默認為空) password: # 連接超時時間 timeout: 10s spring: # 配置業(yè)務使用的 Redis 連接 redis: # Redis數(shù)據(jù)庫索引(默認為0) database: 1 # Redis服務器地址 host: 127.0.0.1 # Redis服務器連接端口 port: 6379 # Redis服務器連接密碼(默認為空) password: # 連接超時時間 timeout: 10s
3、配置全局異常處理
這一步可以不配置,配置的作用是,在鑒權(quán)失敗的時候,不會報錯,而是返回給前端鑒權(quán)失敗的原因,方便我們開發(fā)調(diào)試
下面的異常會在鑒權(quán)失敗的時候自動返回到前端,無需我們手動拋出和返回
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)失敗不會報錯,會返回給前端報錯原因 * @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)限
這里實現(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 獲取當前賬號的權(quán)限和角色列表,這個類下面的方法會在接口鑒權(quán)之前自動調(diào)用 */ @Component public class UserPermission implements StpInterface { /** * 返回一個賬號所擁有的權(quán)限碼集合 * 即你在調(diào)用 StpUtil.login(id) 時寫入的標識值。 */ @Override public List<String> getPermissionList(Object loginId, String loginType) { // 本list僅做模擬,實際項目中要根據(jù)具體業(yè)務邏輯來查詢權(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; } /** * 返回一個賬號所擁有的角色標識集合 (權(quán)限與角色可分開校驗) */ @Override public List<String> getRoleList(Object loginId, String loginType) { // 本list僅做模擬,實際項目中要根據(jù)具體業(yè)務邏輯來查詢角色 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) { // 注冊路由攔截器,自定義認證規(guī)則 registry.addInterceptor(new SaInterceptor(handler -> { // 登錄認證 -- 攔截所有路由,并排除/user/doLogin 用于開放登錄 SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin()); // 角色認證 -- 攔截以 admin 開頭的路由,必須具備 admin 角色或者 super-admin 角色才可以通過認證 SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin")); // 權(quán)限認證 -- 不同模塊認證不同權(quán)限 SaRouter.match("/user/**", r -> StpUtil.checkRole("user")); SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin")); // 甚至你可以隨意的寫一個打印語句 SaRouter.match("/**", r -> System.out.println("--------權(quán)限認證成功-------")); }).isAnnotation(true)) //攔截所有接口 .addPathPatterns("/**") //不攔截/user/doLogin登錄接口 .excludePathPatterns("/user/doLogin"); } }
6、controller里調(diào)用satoken的方法
方法上面的注解是使用權(quán)限認證和攔截器的時候用的,下面我會講到
我在下面的
UserController
演示了登錄
、注銷
、檢查是否登錄
、查看用戶token
、獲取token有效期
、對稱加密
、非對稱加密
方法,具體的方法每一行代碼的作用,都在注視中寫出來了,等一下我們測試每一個方法,為大家展示運行結(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) { //這個方法會強制在瀏覽器彈出一個認證框 SaBasicUtil.check("sa:123456"); if(username.equals(USERNAME) && password.equals(PASSWORD)) { //這個是登錄用戶的主鍵,業(yè)務中你要從數(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值獲取對應的賬號id,如果未登錄,則返回 null * @param tokenValue * @return */ @RequestMapping("/getUserByToken/{tokenValue}") public SaResult getUserByToken(@PathVariable String tokenValue){ return SaResult.ok((String) StpUtil.getLoginIdByToken(tokenValue)); } /** * 獲取當前會話剩余有效期(單位: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 = "這是一個明文用于測試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 = "這是一個明文用于測試RSA非對稱加密"; // 使用公鑰加密 String ciphertext1 = SaSecureUtil.rsaEncryptByPublic(publicKey, text1); System.out.println("公鑰加密后:" + ciphertext1); // 使用私鑰解密 String text3 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext1); System.out.println("私鑰解密后:" + text3); /** * Base64 */ // 文本 String text4 = "這是一個明文用于測試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)限認證和路由攔截的時候用
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)限才能進入該方法 * @return */ @SaCheckRole("super-admin") @RequestMapping("/delete") public SaResult delete() { return SaResult.ok("刪除成功"); } /** * 注解式鑒權(quán):SaMode.OR 只要具有其中一個權(quán)限即可通過校驗 * @return */ @RequestMapping("/add") @SaCheckPermission(value = {"user-add", "user-all"}, mode = SaMode.OR) public SaResult add() { return SaResult.ok("添加成功"); } /** * 一個接口在具有權(quán)限 user-update 或角色 admin 時可以調(diào)通 * @return */ @RequestMapping("/update") @SaCheckPermission(value = "user-add", orRole = "admin") public SaResult update() { return SaResult.ok("更新成功"); } /** * 這個接口測試用 * @return */ @RequestMapping("/testPermission") @SaCheckPermission(value = "user123") public SaResult testPermission() { return SaResult.ok("這個接口測試用"); } }
二、登錄演示
到這里,我么前期的配置就已經(jīng)結(jié)束了,下面我開始測試每一個方法,為大家展示運行結(jié)果并解析代碼,先把項目運行起來
1、登錄-doLogin
大家請看,我在請求
/doLogin
這個接口的時候,彈出了下面的認證框,這個就是方法第一行代碼的功能,這叫Basic認證
,當然可以不要這一行代碼,隨你們,認證賬號 sa 密碼 123456
//這個方法會強制在瀏覽器彈出一個認證框 SaBasicUtil.check("sa:123456");
進行 Basic認證
后,登陸成功
我這里使用了 redis ,token 已經(jīng)存儲進來了
2、驗證登錄-isLogin
3、獲取token時效-getTokenTimeout
這是我們在 yml 里面配置的時效性,30天的毫秒
4、加密
請求 encodePassword
接口
5、注銷登錄-logout
三、權(quán)限認證和攔截器演示
下面演示上面我們配置的攔截器,satoken 可以直接使用注解來進行攔截,很方便
我們可以發(fā)現(xiàn),我在兩個 controller 里面使用了 satoken 的幾個注解,注解可以用在方法上或類上
@SaIgnore
忽略該方法,不進行任何攔截和鑒權(quán)@SaCheckLogin
登錄后才可以調(diào)用該接口@SaCheckRole("super-admin")
登錄用戶必須要是"super-admin"角色才可調(diào)用@SaCheckPermission(value = "del")
登錄用戶必須要有"del"權(quán)限才可調(diào)用
1、登錄認證
下面我們調(diào)用添加了
@SaCheckLogin
注解的方法
(1) 未登錄情況
(2) 已登陸情況
可以看到調(diào)用成功,控制臺打印出我們上面攔截器配置的輸出信息
2、權(quán)限認證
登錄后,我們調(diào)用增加了
@SaCheckPermission(value = "del")
注解的方法,可以看到提示無此權(quán)限:del
,因為前面我們模擬用戶權(quán)限時,沒有給用戶分配del
權(quán)限
我們再調(diào)用增加了
@SaCheckRole("super-admin")
注解的方法,可以看到成功
總結(jié)
到此這篇關于Springboot 使用 SaToken 進行登錄認證、權(quán)限管理以及路由規(guī)則接口攔截的文章就介紹到這了,更多相關Springboot SaToken登錄認證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java實現(xiàn)字符串轉(zhuǎn)String數(shù)組的方法示例
這篇文章主要介紹了java實現(xiàn)字符串轉(zhuǎn)String數(shù)組的方法,涉及java字符串的遍歷、分割、轉(zhuǎn)換等相關操作技巧,需要的朋友可以參考下2017-10-10eclipse實現(xiàn)可認證的DH密鑰交換協(xié)議
這篇文章主要介紹了eclipse實現(xiàn)可認證的DH密鑰交換協(xié)議,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-06-06面試題:java中為什么foreach中不允許對元素進行add和remove
讀者遇到了一個比較經(jīng)典的面試題,也就是標題上說的,為什么 foreach 中不允許對元素進行 add 和 remove,本文就詳細的介紹一下,感興趣的可以了解一下2021-10-10解決springboot啟動Logback報錯ERROR in ch.qos.logback.cla
這篇文章主要介紹了解決springboot啟動Logback報錯ERROR in ch.qos.logback.classic.joran.action.ContextNameAction - Failed to rena問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04java?實現(xiàn)獲取指定位置后的第一個數(shù)字
這篇文章主要介紹了java?實現(xiàn)獲取指定位置后的第一個數(shù)字,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01SpringBoot2.1 RESTful API項目腳手架(種子)項目
這篇文章主要介紹了SpringBoot2.1 RESTful API項目腳手架(種子)項目,用于搭建RESTful API工程的腳手架,只需三分鐘你就可以開始編寫業(yè)務代碼,不再煩惱于構(gòu)建項目與風格統(tǒng)一,感興趣的小伙伴們可以參考一下2018-12-12