2022-04-19_spring boot 配置sa-token

spring boot 配置sa-token

前言

  • sa-token一個(gè)國(guó)產(chǎn)的,權(quán)限認(rèn)證框架。
  • 功能上類似Apache ShiroSpring 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ù) */ );
    
    

參考文章

1.sa-token官方文檔路由攔截鑒權(quán)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容