Springboot 如何使用 SaToken 進行登錄認證、權(quán)限管理及路由規(guī)則接口攔截
Springboot 使用 SaToken 進行登錄認證、權(quán)限管理以及路由規(guī)則接口攔截
前言
Sa-Token 是一個輕量級 Java 權(quán)限認證框架,主要解決:登錄認證、權(quán)限認證、單點登錄、OAuth2.0、分布式Session會話、微服務網(wǎng)關(guān)鑒權(quán) 等一系列權(quán)限相關(guān)問題。
還有踢人下線、賬號封禁、路由攔截規(guī)則、微服務網(wǎng)關(guān)鑒權(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: 10s3、配置全局異常處理
這一步可以不配置,配置的作用是,在鑒權(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é)
到此這篇關(guān)于Springboot 使用 SaToken 進行登錄認證、權(quán)限管理以及路由規(guī)則接口攔截的文章就介紹到這了,更多相關(guān)Springboot SaToken登錄認證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實現(xiàn)字符串轉(zhuǎn)String數(shù)組的方法示例
這篇文章主要介紹了java實現(xiàn)字符串轉(zhuǎn)String數(shù)組的方法,涉及java字符串的遍歷、分割、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10
eclipse實現(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-04
java?實現(xiàn)獲取指定位置后的第一個數(shù)字
這篇文章主要介紹了java?實現(xiàn)獲取指定位置后的第一個數(shù)字,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
SpringBoot2.1 RESTful API項目腳手架(種子)項目
這篇文章主要介紹了SpringBoot2.1 RESTful API項目腳手架(種子)項目,用于搭建RESTful API工程的腳手架,只需三分鐘你就可以開始編寫業(yè)務代碼,不再煩惱于構(gòu)建項目與風格統(tǒng)一,感興趣的小伙伴們可以參考一下2018-12-12

