spring boot 配置sa-token
前言
- sa-token一個(gè)國(guó)產(chǎn)的,權(quán)限認(rèn)證框架。
- 功能上類似Apache Shiro
、Spring Security等。但是更加強(qiáng)大、容易上手 - 主要解決: 登錄認(rèn)證、權(quán)限認(rèn)證、Session會(huì)話 等一系列權(quán)限相關(guān)問(wèn)題。大部分api調(diào)用都是一行代碼就能解決
- sa-token功能很強(qiáng)大。以下簡(jiǎn)單記錄登錄認(rèn)證、權(quán)限認(rèn)證、路由攔截鑒權(quán)等功能
- 在
sa-token中,登錄授權(quán)就是如此的簡(jiǎn)單,不需要什么全局過(guò)濾器,不需要各種亂七八糟的配置!只需要一行簡(jiǎn)單的API調(diào)用,即可完成會(huì)話的登錄授權(quán)! - 只要完成登錄認(rèn)證、權(quán)限認(rèn)證、路由攔截鑒權(quán)就可以簡(jiǎn)單的為項(xiàng)目增加一個(gè)鑒權(quán)系統(tǒng)。
- 如果只需要登錄認(rèn)證,權(quán)限認(rèn)證都不需要,就更簡(jiǎn)單了。。寫(xiě)一個(gè)路由攔截配置文件、用戶登錄調(diào)用login方法就搞定一切了。
1.spring boot 引入sa-token
<!-- Sa-Token 權(quán)限認(rèn)證, 在線文檔:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.29.0</version>
</dependency>
2.配置
application.yml文件增加如下配置項(xiàng)目,當(dāng)然也可以不增加:
# Sa-Token配置 sa-token: # token名稱 (同時(shí)也是cookie名稱) token-name: satoken # token有效期,單位s 默認(rèn)30天, -1代表永不過(guò)期 timeout: 2592000 # token臨時(shí)有效期 (指定時(shí)間內(nèi)無(wú)操作就視為token過(guò)期) 單位: 秒 activity-timeout: -1 # 是否允許同一賬號(hào)并發(fā)登錄 (為true時(shí)允許一起登錄, 為false時(shí)新登錄擠掉舊登錄) is-concurrent: true # 在多人登錄同一賬號(hào)時(shí),是否共用一個(gè)token (為true時(shí)所有登錄共用一個(gè)token, 為false時(shí)每次登錄新建一個(gè)token) is-share: false # token風(fēng)格 token-style: uuid # 是否輸出操作日志 is-log: false
3.登錄認(rèn)證
所謂登錄認(rèn)證,說(shuō)白了就是限制某些API接口必須登錄后才能訪問(wèn)。
如何判斷一個(gè)會(huì)話是否已經(jīng)登錄?用戶登錄后,服務(wù)器端調(diào)用
login方法。sa-token會(huì)幫我們對(duì)會(huì)話進(jìn)行做個(gè)標(biāo)記。每次需要登錄認(rèn)證的時(shí)候校驗(yàn)這些標(biāo)記。有標(biāo)記者視為已登錄,無(wú)標(biāo)記者視為未登錄。默認(rèn),不特殊處理的情況下,sa-token是通過(guò)cookie來(lái)標(biāo)記的。如果清掉了cookie,則需要重新登錄。
// 標(biāo)記當(dāng)前會(huì)話登錄的賬號(hào)id // 建議的參數(shù)類型:long | int | String, 不可以傳入復(fù)雜類型,如:User、Admin等等 StpUtil.login(Object id); // 當(dāng)前會(huì)話注銷登錄 StpUtil.logout(); // 獲取當(dāng)前會(huì)話是否已經(jīng)登錄,返回true=已登錄,false=未登錄 StpUtil.isLogin(); // 檢驗(yàn)當(dāng)前會(huì)話是否已經(jīng)登錄, 如果未登錄,則拋出異常:`NotLoginException` StpUtil.checkLogin() // 獲取當(dāng)前會(huì)話賬號(hào)id, 如果未登錄,則拋出異常:`NotLoginException` StpUtil.getLoginId(); // 類似查詢API還有: StpUtil.getLoginIdAsString(); // 獲取當(dāng)前會(huì)話賬號(hào)id, 并轉(zhuǎn)化為`String`類型 StpUtil.getLoginIdAsInt(); // 獲取當(dāng)前會(huì)話賬號(hào)id, 并轉(zhuǎn)化為`int`類型 StpUtil.getLoginIdAsLong(); // 獲取當(dāng)前會(huì)話賬號(hào)id, 并轉(zhuǎn)化為`long`類型 // ---------- 指定未登錄情形下返回的默認(rèn)值 ---------- // 獲取當(dāng)前會(huì)話賬號(hào)id, 如果未登錄,則返回null StpUtil.getLoginIdDefaultNull(); // 獲取當(dāng)前會(huì)話賬號(hào)id, 如果未登錄,則返回默認(rèn)值 (`defaultValue`可以為任意類型) StpUtil.getLoginId(T defaultValue); // 獲取指定token對(duì)應(yīng)的賬號(hào)id,如果未登錄,則返回 null StpUtil.getLoginIdByToken(String tokenValue); // 獲取當(dāng)前`StpLogic`的token名稱 StpUtil.getTokenName(); // 獲取當(dāng)前會(huì)話的token值 StpUtil.getTokenValue(); // 獲取當(dāng)前會(huì)話的token信息參數(shù) StpUtil.getTokenInfo();
4.權(quán)限認(rèn)證
所謂權(quán)限認(rèn)證,認(rèn)證的核心就是一個(gè)賬號(hào)是否擁有一個(gè)權(quán)限碼。有,就讓你通過(guò)。沒(méi)有?那么禁止訪問(wèn)!
再往底了說(shuō),就是每個(gè)賬號(hào)都會(huì)擁有一個(gè)權(quán)限碼集合,我來(lái)校驗(yàn)這個(gè)集合中是否包含指定的權(quán)限碼
例如:當(dāng)前賬號(hào)擁有權(quán)限碼集合:
["user-add", "user-delete", "user-get"],這時(shí)候我來(lái)校驗(yàn)權(quán)限"user-update",則其結(jié)果就是:驗(yàn)證失敗,禁止訪問(wèn)所以核心問(wèn)題如下:
4.1 獲取當(dāng)前賬號(hào)的權(quán)限碼集合
你需要做的就是新建一個(gè)類,實(shí)現(xiàn)
StpInterface接口添加
@Component保證此類被SpringBoot掃描,完成Sa-Token的自定義權(quán)限驗(yàn)證擴(kuò)展注意方法傳入的loginID。實(shí)際項(xiàng)目需要更加loginID實(shí)際返回該ID對(duì)于的權(quán)限。
/** * 自定義權(quán)限驗(yàn)證接口擴(kuò)展 */ @Component // 保證此類被SpringBoot掃描,完成Sa-Token的自定義權(quán)限驗(yàn)證擴(kuò)展 public class StpInterfaceImpl implements StpInterface { /** * 返回一個(gè)賬號(hào)所擁有的權(quán)限碼集合 */ @Override public List<String> getPermissionList(Object loginId, String loginType) { // 本list僅做模擬,實(shí)際項(xiàng)目中要根據(jù)具體業(yè)務(wù)邏輯來(lái)查詢權(quán)限 List<String> list = new ArrayList<String>(); list.add("101"); list.add("user-add"); list.add("user-delete"); list.add("user-update"); list.add("user-get"); list.add("article-get"); return list; } /** * 返回一個(gè)賬號(hào)所擁有的角色標(biāo)識(shí)集合 (權(quán)限與角色可分開(kāi)校驗(yàn)) */ @Override public List<String> getRoleList(Object loginId, String loginType) { // 本list僅做模擬,實(shí)際項(xiàng)目中要根據(jù)具體業(yè)務(wù)邏輯來(lái)查詢角色 List<String> list = new ArrayList<String>(); list.add("admin"); list.add("super-admin"); return list; } }
4.2權(quán)限認(rèn)證
告訴了sa-token用戶所擁有的權(quán)限或角色后。就可以用以下api來(lái)鑒權(quán)了
// 判斷:當(dāng)前賬號(hào)是否含有指定權(quán)限, 返回true或false StpUtil.hasPermission("user-update"); // 校驗(yàn):當(dāng)前賬號(hào)是否含有指定權(quán)限, 如果驗(yàn)證未通過(guò),則拋出異常: NotPermissionException StpUtil.checkPermission("user-update"); // 校驗(yàn):當(dāng)前賬號(hào)是否含有指定權(quán)限 [指定多個(gè),必須全部驗(yàn)證通過(guò)] StpUtil.checkPermissionAnd("user-update", "user-delete"); // 校驗(yàn):當(dāng)前賬號(hào)是否含有指定權(quán)限 [指定多個(gè),只要其一驗(yàn)證通過(guò)即可] StpUtil.checkPermissionOr("user-update", "user-delete"); // 判斷:當(dāng)前賬號(hào)是否擁有指定角色, 返回true或false StpUtil.hasRole("super-admin"); // 校驗(yàn):當(dāng)前賬號(hào)是否含有指定角色標(biāo)識(shí), 如果驗(yàn)證未通過(guò),則拋出異常: NotRoleException StpUtil.checkRole("super-admin"); // 校驗(yàn):當(dāng)前賬號(hào)是否含有指定角色標(biāo)識(shí) [指定多個(gè),必須全部驗(yàn)證通過(guò)] StpUtil.checkRoleAnd("super-admin", "shop-admin"); // 校驗(yàn):當(dāng)前賬號(hào)是否含有指定角色標(biāo)識(shí) [指定多個(gè),只要其一驗(yàn)證通過(guò)即可] StpUtil.checkRoleOr("super-admin", "shop-admin");
5.路由攔截鑒權(quán)
為什么需要路由攔截鑒權(quán)?
方法1:每個(gè)方法手動(dòng)調(diào)用如上的鑒權(quán)api。如果沒(méi)有權(quán)限就不執(zhí)行。。這個(gè)辦法侵入性太高
方法2:注解鑒權(quán)。。。這個(gè)辦法同樣不太方便。。。需要修改api接口的注解。
方法3:自己動(dòng)手書(shū)寫(xiě)全局?jǐn)r截器
方法4:sa-token提供的路由攔截鑒權(quán)。。這個(gè)是比較方便的。。改動(dòng)比較少。只需要配置相關(guān)配置類?;旧纤惺虑榫透愣恕?。。
增加如下配置類即可。如下代碼注冊(cè)了一個(gè)登錄認(rèn)證攔截器,并且排除了
/user/doLogin接口用來(lái)開(kāi)放登錄(除了/user/doLogin以外的所有接口都需要登錄才能訪問(wèn))@Configuration public class SaTokenConfigure implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 注冊(cè)路由攔截器,自定義認(rèn)證規(guī)則 registry.addInterceptor(new SaRouteInterceptor((req, res, 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")); SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders")); SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice")); SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment")); })).addPathPatterns("/**") .excludePathPatterns("/user/doLogin"); } }在校驗(yàn)函數(shù)內(nèi)不只可以使用
StpUtil.checkPermission("xxx")進(jìn)行權(quán)限校驗(yàn),你還可以寫(xiě)任意代碼最重要的是日記記錄。。
// 登錄認(rèn)證 -- 攔截所有路由,并排除/user/doLogin 用于開(kāi)放登錄 SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin()); // 角色認(rèn)證 -- 攔截以 admin 開(kāi)頭的路由,必須具備 admin 角色或者 super-admin 角色才可以通過(guò)認(rèn)證 SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin")); // 甚至你可以隨意的寫(xiě)一個(gè)打印語(yǔ)句 SaRouter.match("/**", r -> System.out.println("----啦啦啦----")); // 連綴寫(xiě)法 SaRouter.match("/**").check(r -> System.out.println("----啦啦啦----"));除了上述示例的 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)格路由 SaRouter.match("/user/**", "/goods/**", "/art/get/{id}").check( /* 要執(zhí)行的校驗(yàn)函數(shù) */ ); // 根據(jù) path 路由排除匹配 SaRouter.match("/**").notMatch("*.html", "*.css", "*.js").check( /* 要執(zhí)行的校驗(yàn)函數(shù) */ ); // 根據(jù)請(qǐng)求類型匹配 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è)條件一起使用 SaRouter.match(SaHttpMethod.GET).match("/**").check( /* 要執(zhí)行的校驗(yàn)函數(shù) */ ); // 可以無(wú)限連綴下去 SaRouter .match(SaHttpMethod.GET) .match("/admin/**") .match("/user/**") .notMatch("/**/*.js") .notMatch("/**/*.css") // .... .check( /* 只有上述所有條件都匹配成功,才會(huì)執(zhí)行最后的check校驗(yàn)函數(shù) */ );