一吮播、需求分析
在前文说贝,我們?cè)敿?xì)的講述了在 Sa-Token 如何使用注解進(jìn)行權(quán)限認(rèn)證,注解鑒權(quán)雖然方便践惑,卻并不適合所有鑒權(quán)場(chǎng)景腹泌。
假設(shè)有如下需求:項(xiàng)目中所有接口均需要登錄認(rèn)證校驗(yàn),只有 “登錄接口” 本身對(duì)外開(kāi)放尔觉。
如果我們對(duì)項(xiàng)目所有接口都加上 @SaCheckLogin
注解凉袱,會(huì)顯得非常冗余且沒(méi)有必要,在這個(gè)需求中我們真正需要的是一種基于路由攔截的鑒權(quán)模式穷娱,那么在 Sa-Token 怎么實(shí)現(xiàn)路由攔截鑒權(quán)呢绑蔫?
Sa-Token 是一個(gè)輕量級(jí) java 權(quán)限認(rèn)證框架,主要解決登錄認(rèn)證泵额、權(quán)限認(rèn)證配深、單點(diǎn)登錄、OAuth2嫁盲、微服務(wù)網(wǎng)關(guān)鑒權(quán) 等一系列權(quán)限相關(guān)問(wèn)題篓叶。
Gitee 開(kāi)源地址:https://gitee.com/dromara/sa-token
二、注冊(cè) Sa-Token 路由攔截器
首先在項(xiàng)目中引入 Sa-Token 依賴(lài):
<!-- Sa-Token 權(quán)限認(rèn)證 -->
<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
即可缸托。
新建配置類(lèi)SaTokenConfigure.java
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注冊(cè)攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注冊(cè) Sa-Token 攔截器,校驗(yàn)規(guī)則為 StpUtil.checkLogin() 登錄校驗(yàn)瘾蛋。
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/**")
.excludePathPatterns("/user/doLogin");
}
}
以上代碼俐镐,我們注冊(cè)了一個(gè)基于 StpUtil.checkLogin()
的登錄校驗(yàn)攔截器,并且排除了/user/doLogin
接口用來(lái)開(kāi)放登錄(除了/user/doLogin
以外的所有接口都需要登錄才能訪問(wèn))哺哼。
SaInterceptor
是新版本提供的攔截器佩抹,點(diǎn)此 查看舊版本代碼遷移示例。
三取董、校驗(yàn)函數(shù)詳解
自定義認(rèn)證規(guī)則:new SaInterceptor(handle -> StpUtil.checkLogin())
是最簡(jiǎn)單的寫(xiě)法棍苹,代表只進(jìn)行登錄校驗(yàn)功能。
我們可以往構(gòu)造函數(shù)塞一個(gè)完整的 lambda 表達(dá)式茵汰,來(lái)定義詳細(xì)的校驗(yàn)規(guī)則枢里,例如:
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注冊(cè) Sa-Token 攔截器,定義詳細(xì)認(rèn)證規(guī)則
registry.addInterceptor(new SaInterceptor(handler -> {
// 指定一條 match 規(guī)則
SaRouter
.match("/**") // 攔截的 path 列表蹂午,可以寫(xiě)多個(gè) */
.notMatch("/user/doLogin") // 排除掉的 path 列表栏豺,可以寫(xiě)多個(gè)
.check(r -> StpUtil.checkLogin()); // 要執(zhí)行的校驗(yàn)動(dòng)作,可以寫(xiě)完整的 lambda 表達(dá)式
// 根據(jù)路由劃分模塊豆胸,不同模塊不同鑒權(quán)
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() 匹配函數(shù)有兩個(gè)參數(shù):
- 參數(shù)一:要匹配的path路由奥洼。
- 參數(shù)二:要執(zhí)行的校驗(yàn)函數(shù)。
在校驗(yàn)函數(shù)內(nèi)不只可以使用 StpUtil.checkPermission("xxx")
進(jìn)行權(quán)限校驗(yàn)配乱,你還可以寫(xiě)任意代碼溉卓,例如:
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注冊(cè) Sa-Token 的攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注冊(cè)路由攔截器,自定義認(rèn)證規(guī)則
registry.addInterceptor(new SaInterceptor(handler -> {
// 登錄校驗(yàn) -- 攔截所有路由搬泥,并排除/user/doLogin 用于開(kāi)放登錄
SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin());
// 角色校驗(yàn) -- 攔截以 admin 開(kāi)頭的路由桑寨,必須具備 admin 角色或者 super-admin 角色才可以通過(guò)認(rèn)證
SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin"));
// 權(quán)限校驗(yàn) -- 不同模塊校驗(yàn)不同權(quán)限
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"));
// 甚至你可以隨意的寫(xiě)一個(gè)打印語(yǔ)句
SaRouter.match("/**", r -> System.out.println("----啦啦啦----"));
// 連綴寫(xiě)法
SaRouter.match("/**").check(r -> System.out.println("----啦啦啦----"));
})).addPathPatterns("/**");
}
}
四、匹配特征詳解
除了上述示例的 path 路由匹配忿檩,還可以根據(jù)很多其它特征進(jìn)行匹配尉尾,以下是所有可匹配的特征:
// 基礎(chǔ)寫(xiě)法樣例:匹配一個(gè)path,執(zhí)行一個(gè)校驗(yàn)函數(shù)
SaRouter.match("/user/**").check(r -> StpUtil.checkLogin());
// 根據(jù) path 路由匹配 ——— 支持寫(xiě)多個(gè)path燥透,支持寫(xiě) restful 風(fēng)格路由
// 功能說(shuō)明: 使用 /user , /goods 或者 /art/get 開(kāi)頭的任意路由都將進(jìn)入 check 方法
SaRouter.match("/user/**", "/goods/**", "/art/get/{id}").check( /* 要執(zhí)行的校驗(yàn)函數(shù) */ );
// 根據(jù) path 路由排除匹配
// 功能說(shuō)明: 使用 .html , .css 或者 .js 結(jié)尾的任意路由都將跳過(guò), 不會(huì)進(jìn)入 check 方法
SaRouter.match("/**").notMatch("*.html", "*.css", "*.js").check( /* 要執(zhí)行的校驗(yàn)函數(shù) */ );
// 根據(jù)請(qǐng)求類(lèi)型匹配
SaRouter.match(SaHttpMethod.GET).check( /* 要執(zhí)行的校驗(yàn)函數(shù) */ );
// 根據(jù)一個(gè) boolean 條件進(jìn)行匹配
SaRouter.match( StpUtil.isLogin() ).check( /* 要執(zhí)行的校驗(yàn)函數(shù) */ );
// 根據(jù)一個(gè)返回 boolean 結(jié)果的lambda表達(dá)式匹配
SaRouter.match( r -> StpUtil.isLogin() ).check( /* 要執(zhí)行的校驗(yàn)函數(shù) */ );
// 多個(gè)條件一起使用
// 功能說(shuō)明: 必須是 Get 請(qǐng)求 并且 請(qǐng)求路徑以 `/user/` 開(kāi)頭
SaRouter.match(SaHttpMethod.GET).match("/user/**").check( /* 要執(zhí)行的校驗(yàn)函數(shù) */ );
// 可以無(wú)限連綴下去
// 功能說(shuō)明: 同時(shí)滿(mǎn)足 Get 方式請(qǐng)求, 且路由以 /admin 開(kāi)頭, 路由中間帶有 /send/ 字符串, 路由結(jié)尾不能是 .js 和 .css
SaRouter
.match(SaHttpMethod.GET)
.match("/admin/**")
.match("/**/send/**")
.notMatch("/**/*.js")
.notMatch("/**/*.css")
// ....
.check( /* 只有上述所有條件都匹配成功沙咏,才會(huì)執(zhí)行最后的check校驗(yàn)函數(shù) */ );
五、提前退出匹配鏈
使用 SaRouter.stop()
可以提前退出匹配鏈班套,例:
registry.addInterceptor(new SaInterceptor(handler -> {
SaRouter.match("/**").check(r -> System.out.println("進(jìn)入1"));
SaRouter.match("/**").check(r -> System.out.println("進(jìn)入2")).stop();
SaRouter.match("/**").check(r -> System.out.println("進(jìn)入3"));
SaRouter.match("/**").check(r -> System.out.println("進(jìn)入4"));
SaRouter.match("/**").check(r -> System.out.println("進(jìn)入5"));
})).addPathPatterns("/**");
如上示例肢藐,代碼運(yùn)行至第2條匹配鏈時(shí),會(huì)在stop函數(shù)處提前退出整個(gè)匹配函數(shù)吱韭,從而忽略掉剩余的所有match匹配
除了stop()
函數(shù)吆豹,SaRouter
還提供了 back()
函數(shù),用于:停止匹配理盆,結(jié)束執(zhí)行痘煤,直接向前端返回結(jié)果
// 執(zhí)行back函數(shù)后將停止匹配,也不會(huì)進(jìn)入Controller猿规,而是直接將 back參數(shù) 作為返回值輸出到前端
SaRouter.match("/user/back").back("要返回到前端的內(nèi)容");
stop() 與 back() 函數(shù)的區(qū)別在于:
-
SaRouter.stop()
會(huì)停止匹配衷快,進(jìn)入Controller。 -
SaRouter.back()
會(huì)停止匹配姨俩,直接返回結(jié)果到前端蘸拔。
六、使用free打開(kāi)一個(gè)獨(dú)立的作用域
// 進(jìn)入 free 獨(dú)立作用域
SaRouter.match("/**").free(r -> {
SaRouter.match("/a/**").check(/* --- */);
SaRouter.match("/b/**").check(/* --- */).stop();
SaRouter.match("/c/**").check(/* --- */);
});
// 執(zhí)行 stop() 函數(shù)跳出 free 后繼續(xù)執(zhí)行下面的 match 匹配
SaRouter.match("/**").check(/* --- */);
free() 的作用是:打開(kāi)一個(gè)獨(dú)立的作用域哼勇,使內(nèi)部的 stop() 不再一次性跳出整個(gè) Auth 函數(shù)都伪,而是僅僅跳出當(dāng)前 free 作用域。
七积担、使用注解忽略掉路由攔截校驗(yàn)
我們可以使用 @SaIgnore
注解陨晶,忽略掉路由攔截認(rèn)證:
1、先配置好了攔截規(guī)則:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handler -> {
// 根據(jù)路由劃分模塊帝璧,不同模塊不同鑒權(quán)
SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
// ...
})).addPathPatterns("/**");
}
2先誉、然后在 Controller
里又添加了忽略校驗(yàn)的注解
@SaIgnore
@RequestMapping("/user/getList")
public SaResult getList() {
System.out.println("------------ 訪問(wèn)進(jìn)來(lái)方法");
return SaResult.ok();
}
請(qǐng)求將會(huì)跳過(guò)攔截器的校驗(yàn),直接進(jìn)入 Controller 的方法中的烁。
注意點(diǎn):此注解的忽略效果只針對(duì) SaInterceptor攔截器 和 AOP注解鑒權(quán) 生效褐耳,對(duì)自定義攔截器與過(guò)濾器不生效。
八渴庆、關(guān)閉注解校驗(yàn)
SaInterceptor
只要注冊(cè)到項(xiàng)目中铃芦,默認(rèn)就會(huì)打開(kāi)注解校驗(yàn)雅镊,如果要關(guān)閉此能力,需要:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new SaInterceptor(handle -> {
SaRouter.match("/**").check(r -> StpUtil.checkLogin());
}).isAnnotation(false) // 指定關(guān)閉掉注解鑒權(quán)能力刃滓,這樣框架就只會(huì)做路由攔截校驗(yàn)了
).addPathPatterns("/**");
}
參考資料
- Sa-Token 文檔:https://sa-token.cc
- Gitee 倉(cāng)庫(kù)地址:https://gitee.com/dromara/sa-token
- GitHub 倉(cāng)庫(kù)地址:https://github.com/dromara/sa-token