博客 / 詳情

返回

SpringBoot 使用 Sa-Token 完成路由攔截鑑權

一、需求分析

在前文,我們詳細的講述了在 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
user avatar snower 頭像 mo_or 頭像 deltaf 頭像 lingfeng23 頭像 u_16213586 頭像 redorblack 頭像 tracy_5cb7dfc1f3f67 頭像 goudantiezhuerzi 頭像 kenx_5e23c96d15b1d 頭像 qiehxb8 頭像 luoshenshen 頭像 leefj 頭像
26 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.