SpringBoot 使用 Sa-Token 完成路由攔截鑒權(quán)

一吮播、需求分析

在前文说贝,我們?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("/**");
}

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仁烹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子咧虎,更是在濱河造成了極大的恐慌卓缰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砰诵,死亡現(xiàn)場(chǎng)離奇詭異征唬,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)茁彭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)总寒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人理肺,你說(shuō)我怎么就攤上這事偿乖。” “怎么了哲嘲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵贪薪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我眠副,道長(zhǎng)画切,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任囱怕,我火速辦了婚禮霍弹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娃弓。我一直安慰自己典格,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布台丛。 她就那樣靜靜地躺著耍缴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挽霉。 梳的紋絲不亂的頭發(fā)上防嗡,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音侠坎,去河邊找鬼蚁趁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛实胸,可吹牛的內(nèi)容都是我干的他嫡。 我是一名探鬼主播番官,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼钢属!你這毒婦竟也來(lái)了鲤拿?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤署咽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后生音,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體宁否,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年缀遍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了慕匠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡域醇,死狀恐怖台谊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情譬挚,我是刑警寧澤锅铅,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站减宣,受9級(jí)特大地震影響盐须,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜漆腌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一贼邓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧闷尿,春花似錦塑径、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至劳景,卻和暖如春绑咱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背枢泰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工描融, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衡蚂。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓窿克,卻偏偏與公主長(zhǎng)得像骏庸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子年叮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容