一、需求分析
在前文,我們詳細的講述了在 Sa-Token 如何使用註解進行權限認證,註解鑑權雖然方便,卻並不適合所有鑑權場景。
假設有如下需求:項目中所有接口均需要登錄認證校驗,只有 “登錄接口” 本身對外開放。
如果我們對項目所有接口都加上 @SaCheckLogin 註解,會顯得非常冗餘且沒有必要,在這個需求中我們真正需要的是一種基於路由攔截的鑑權模式,那麼在 Sa-Token 怎麼實現路由攔截鑑權呢?
Sa-Token 是一個輕量級 java 權限認證框架,主要解決登錄認證、權限認證、單點登錄、OAuth2、微服務網關鑑權 等一系列權限相關問題。
Gitee 開源地址:https://gitee.com/dromara/sa-token
二、註冊 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 即可。
新建配置類SaTokenConfigure.java
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 註冊攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 註冊 Sa-Token 攔截器,校驗規則為 StpUtil.checkLogin() 登錄校驗。
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/**")
.excludePathPatterns("/user/doLogin");
}
}
以上代碼,我們註冊了一個基於 StpUtil.checkLogin() 的登錄校驗攔截器,並且排除了/user/doLogin接口用來開放登錄(除了/user/doLogin以外的所有接口都需要登錄才能訪問)。
SaInterceptor 是新版本提供的攔截器,點此 查看舊版本代碼遷移示例。
三、校驗函數詳解
自定義認證規則:new SaInterceptor(handle -> StpUtil.checkLogin()) 是最簡單的寫法,代表只進行登錄校驗功能。
我們可以往構造函數塞一個完整的 lambda 表達式,來定義詳細的校驗規則,例如:
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 註冊 Sa-Token 攔截器,定義詳細認證規則
registry.addInterceptor(new SaInterceptor(handler -> {
// 指定一條 match 規則
SaRouter
.match("/**") // 攔截的 path 列表,可以寫多個 */
.notMatch("/user/doLogin") // 排除掉的 path 列表,可以寫多個
.check(r -> StpUtil.checkLogin()); // 要執行的校驗動作,可以寫完整的 lambda 表達式
// 根據路由劃分模塊,不同模塊不同鑑權
SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment"));
})).addPathPatterns("/**");
}
}
SaRouter.match() 匹配函數有兩個參數:
- 參數一:要匹配的path路由。
- 參數二:要執行的校驗函數。
在校驗函數內不只可以使用 StpUtil.checkPermission("xxx") 進行權限校驗,你還可以寫任意代碼,例如:
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 註冊 Sa-Token 的攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 註冊路由攔截器,自定義認證規則
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"));
// 權限校驗 -- 不同模塊校驗不同權限
SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment"));
// 甚至你可以隨意的寫一個打印語句
SaRouter.match("/**", r -> System.out.println("----啦啦啦----"));
// 連綴寫法
SaRouter.match("/**").check(r -> System.out.println("----啦啦啦----"));
})).addPathPatterns("/**");
}
}
四、匹配特徵詳解
除了上述示例的 path 路由匹配,還可以根據很多其它特徵進行匹配,以下是所有可匹配的特徵:
// 基礎寫法樣例:匹配一個path,執行一個校驗函數
SaRouter.match("/user/**").check(r -> StpUtil.checkLogin());
// 根據 path 路由匹配 ——— 支持寫多個path,支持寫 restful 風格路由
// 功能説明: 使用 /user , /goods 或者 /art/get 開頭的任意路由都將進入 check 方法
SaRouter.match("/user/**", "/goods/**", "/art/get/{id}").check( /* 要執行的校驗函數 */ );
// 根據 path 路由排除匹配
// 功能説明: 使用 .html , .css 或者 .js 結尾的任意路由都將跳過, 不會進入 check 方法
SaRouter.match("/**").notMatch("*.html", "*.css", "*.js").check( /* 要執行的校驗函數 */ );
// 根據請求類型匹配
SaRouter.match(SaHttpMethod.GET).check( /* 要執行的校驗函數 */ );
// 根據一個 boolean 條件進行匹配
SaRouter.match( StpUtil.isLogin() ).check( /* 要執行的校驗函數 */ );
// 根據一個返回 boolean 結果的lambda表達式匹配
SaRouter.match( r -> StpUtil.isLogin() ).check( /* 要執行的校驗函數 */ );
// 多個條件一起使用
// 功能説明: 必須是 Get 請求 並且 請求路徑以 `/user/` 開頭
SaRouter.match(SaHttpMethod.GET).match("/user/**").check( /* 要執行的校驗函數 */ );
// 可以無限連綴下去
// 功能説明: 同時滿足 Get 方式請求, 且路由以 /admin 開頭, 路由中間帶有 /send/ 字符串, 路由結尾不能是 .js 和 .css
SaRouter
.match(SaHttpMethod.GET)
.match("/admin/**")
.match("/**/send/**")
.notMatch("/**/*.js")
.notMatch("/**/*.css")
// ....
.check( /* 只有上述所有條件都匹配成功,才會執行最後的check校驗函數 */ );
五、提前退出匹配鏈
使用 SaRouter.stop() 可以提前退出匹配鏈,例:
registry.addInterceptor(new SaInterceptor(handler -> {
SaRouter.match("/**").check(r -> System.out.println("進入1"));
SaRouter.match("/**").check(r -> System.out.println("進入2")).stop();
SaRouter.match("/**").check(r -> System.out.println("進入3"));
SaRouter.match("/**").check(r -> System.out.println("進入4"));
SaRouter.match("/**").check(r -> System.out.println("進入5"));
})).addPathPatterns("/**");
如上示例,代碼運行至第2條匹配鏈時,會在stop函數處提前退出整個匹配函數,從而忽略掉剩餘的所有match匹配
除了stop()函數,SaRouter還提供了 back() 函數,用於:停止匹配,結束執行,直接向前端返回結果
// 執行back函數後將停止匹配,也不會進入Controller,而是直接將 back參數 作為返回值輸出到前端
SaRouter.match("/user/back").back("要返回到前端的內容");
stop() 與 back() 函數的區別在於:
SaRouter.stop()會停止匹配,進入Controller。SaRouter.back()會停止匹配,直接返回結果到前端。
六、使用free打開一個獨立的作用域
// 進入 free 獨立作用域
SaRouter.match("/**").free(r -> {
SaRouter.match("/a/**").check(/* --- */);
SaRouter.match("/b/**").check(/* --- */).stop();
SaRouter.match("/c/**").check(/* --- */);
});
// 執行 stop() 函數跳出 free 後繼續執行下面的 match 匹配
SaRouter.match("/**").check(/* --- */);
free() 的作用是:打開一個獨立的作用域,使內部的 stop() 不再一次性跳出整個 Auth 函數,而是僅僅跳出當前 free 作用域。
七、使用註解忽略掉路由攔截校驗
我們可以使用 @SaIgnore 註解,忽略掉路由攔截認證:
1、先配置好了攔截規則:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handler -> {
// 根據路由劃分模塊,不同模塊不同鑑權
SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
// ...
})).addPathPatterns("/**");
}
2、然後在 Controller 裏又添加了忽略校驗的註解
@SaIgnore
@RequestMapping("/user/getList")
public SaResult getList() {
System.out.println("------------ 訪問進來方法");
return SaResult.ok();
}
請求將會跳過攔截器的校驗,直接進入 Controller 的方法中。
注意點:此註解的忽略效果只針對 SaInterceptor攔截器 和 AOP註解鑑權 生效,對自定義攔截器與過濾器不生效。
八、關閉註解校驗
SaInterceptor 只要註冊到項目中,默認就會打開註解校驗,如果要關閉此能力,需要:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new SaInterceptor(handle -> {
SaRouter.match("/**").check(r -> StpUtil.checkLogin());
}).isAnnotation(false) // 指定關閉掉註解鑑權能力,這樣框架就只會做路由攔截校驗了
).addPathPatterns("/**");
}
參考資料
- Sa-Token 文檔:https://sa-token.cc
- Gitee 倉庫地址:https://gitee.com/dromara/sa-token
- GitHub 倉庫地址:https://github.com/dromara/sa-token