權(quán)限框架之Sa-Token基礎講解

1 Sa-Token

1.1 引言

之前進行鑒權(quán)、授權(quán)都要寫一大堆代碼。如果使用像Spring Security這樣的框架,又要花好多時間學習,拿過來一用,好多配置項也不知道是干嘛用的,又不想了解。要是不用Spring Security,token的生成、校驗、刷新,權(quán)限的驗證分配,又全要自己寫,想想都頭大。
Spring Security太重而且配置繁瑣。自己實現(xiàn)所有的點必須又要顧及到,更是麻煩。

點擊了解 SpringSecurity和JWT實現(xiàn)認證和授權(quán)
新舊版本SpringSecurity使用對比

最近看到一個權(quán)限認證框架,真是夠簡單高效。這里分享一個使用Sa-Tokengateway鑒權(quán)demo。

1.2 簡介

Sa-TokenSimple & All)是一個基于 Java 的權(quán)限認證框架,用于在 Web 和普通 Java 應用中進行身份認證和權(quán)限控制。它的設計理念是簡單易用、功能全面,適用于各種場景。
官方地址:https://sa-token.cc/
Sa-Token 的一些主要特性:

  • 簡單易用: 提供了簡潔的 API,易于學習和使用。
  • 全面功能: 支持身份認證、權(quán)限控制、單點登錄、會話管理等多種功能。
  • 支持多種存儲方式: 可以選擇內(nèi)存存儲、數(shù)據(jù)庫存儲等多種方式存儲會話信息。
  • 適用于 Web 和非 Web 環(huán)境: 既可以在 Web 框架(如 Spring Boot、Spring MVC)中使用,也可以在普通 Java 應用中使用。
  • 支持多種身份驗證方式: 支持賬號密碼登錄、Token 登錄、微信登錄等多種身份驗證方式。

1.3 簡單操作

示例以springboot集成

1.3.1 pom.xml

<!-- Sa-Token 權(quán)限認證, 在線文檔:https://sa-token.cc -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.37.0</version>
</dependency>

注意:如果使用的是 SpringBoot 3.x,只需要將 sa-token-spring-boot-starter 修改為 sa-token-spring-boot3-starter 即可。

1.3.2 配置文件

server:
    # 端口
    port: 8081
    
############## Sa-Token 配置 (文檔: https://sa-token.cc) ##############
sa-token: 
    # token 名稱(同時也是 cookie 名稱)
    token-name: satoken
    # token 有效期(單位:秒) 默認30天,-1 代表永久有效
    timeout: 2592000
    # token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統(tǒng)就會被凍結(jié),默認-1 代表不限制,永不凍結(jié)
    active-timeout: -1
    # 是否允許同一賬號多地同時登錄 (為 true 時允許一起登錄, 為 false 時新登錄擠掉舊登錄)
    is-concurrent: true
    # 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token, 為 false 時每次登錄新建一個 token)
    is-share: true
    # token 風格(默認可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
    token-style: uuid
    # 是否輸出操作日志 
    is-log: true

1.3.3 controller層面

@RestController
@RequestMapping("/user/")
public class UserController {

    // 測試登錄,瀏覽器訪問: http://localhost:8081/user/doLogin?username=zhang&password=123456
    @RequestMapping("doLogin")
    public String doLogin(String username, String password) {
        // 此處僅作模擬示例,真實項目需要從數(shù)據(jù)庫中查詢數(shù)據(jù)進行比對 
        if("zhang".equals(username) && "123456".equals(password)) {
            StpUtil.login(10001);
            return "登錄成功";
        }
        return "登錄失敗";
    }

    // 查詢登錄狀態(tài),瀏覽器訪問: http://localhost:8081/user/isLogin
    @RequestMapping("isLogin")
    public String isLogin() {
        return "當前會話是否登錄:" + StpUtil.isLogin();
    }
    
}

1.4 登錄注銷相關方法

// 當前會話注銷登錄
StpUtil.logout();
// 獲取當前會話是否已經(jīng)登錄,返回true=已登錄,false=未登錄
StpUtil.isLogin();
// 檢驗當前會話是否已經(jīng)登錄, 如果未登錄,則拋出異常:`NotLoginException`
StpUtil.checkLogin();

會話查詢

// 獲取當前會話賬號id, 如果未登錄,則拋出異常:`NotLoginException`
StpUtil.getLoginId();
// 類似查詢API還有:
StpUtil.getLoginIdAsString();    // 獲取當前會話賬號id, 并轉(zhuǎn)化為`String`類型
StpUtil.getLoginIdAsInt();       // 獲取當前會話賬號id, 并轉(zhuǎn)化為`int`類型
StpUtil.getLoginIdAsLong();      // 獲取當前會話賬號id, 并轉(zhuǎn)化為`long`類型
// ---------- 指定未登錄情形下返回的默認值 ----------
// 獲取當前會話賬號id, 如果未登錄,則返回 null 
StpUtil.getLoginIdDefaultNull();
// 獲取當前會話賬號id, 如果未登錄,則返回默認值 (`defaultValue`可以為任意類型)
StpUtil.getLoginId(T defaultValue);

token 查詢

// 獲取當前會話的 token 值
StpUtil.getTokenValue();
// 獲取當前`StpLogic`的 token 名稱
StpUtil.getTokenName();
// 獲取指定 token 對應的賬號id,如果未登錄,則返回 null
StpUtil.getLoginIdByToken(String tokenValue);
// 獲取當前會話剩余有效期(單位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();
// 獲取當前會話的 token 信息參數(shù)
StpUtil.getTokenInfo();

1.5 權(quán)限認證

1.5.1 思路

所謂權(quán)限認證,核心邏輯就是判斷一個賬號是否擁有指定權(quán)限:
有,就讓你通過。
沒有?那么禁止訪問!
深入到底層數(shù)據(jù)中,就是每個賬號都會擁有一組權(quán)限碼集合,框架來校驗這個集合中是否包含指定的權(quán)限碼。

例如:當前賬號擁有權(quán)限碼集合 ["user-add", "user-delete", "user-get"],這時候我來校驗權(quán)限 "user-update",則其結(jié)果就是:驗證失敗,禁止訪問。
所以現(xiàn)在問題的核心就是兩個:

如何獲取一個賬號所擁有的權(quán)限碼集合?
本次操作需要驗證的權(quán)限碼是哪個?

1.5.2 獲取當前賬號權(quán)限碼集合

因為每個項目的需求不同,其權(quán)限設計也千變?nèi)f化,因此 [ 獲取當前賬號權(quán)限碼集合 ] 這一操作不可能內(nèi)置到框架中, 所以 Sa-Token 將此操作以接口的方式暴露給你,以方便你根據(jù)自己的業(yè)務邏輯進行重寫。

新建一個類,實現(xiàn) StpInterface接口,例如以下代碼:

/**
 * 自定義權(quán)限加載接口實現(xiàn)類
 */
@Component    // 保證此類被 SpringBoot 掃描,完成 Sa-Token 的自定義權(quán)限驗證擴展 
public class StpInterfaceImpl implements StpInterface {

    /**
     * 返回一個賬號所擁有的權(quán)限碼集合 
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 本 list 僅做模擬,實際項目中要根據(jù)具體業(yè)務邏輯來查詢權(quán)限
        List<String> list = new ArrayList<String>();    
        list.add("101");
        list.add("user.add");
        list.add("user.update");
        list.add("user.get");
        // list.add("user.delete");
        list.add("art.*");
        return list;
    }

    /**
     * 返回一個賬號所擁有的角色標識集合 (權(quán)限與角色可分開校驗)
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // 本 list 僅做模擬,實際項目中要根據(jù)具體業(yè)務邏輯來查詢角色
        List<String> list = new ArrayList<String>();    
        list.add("admin");
        list.add("super-admin");
        return list;
    }

}

1.5.3 權(quán)限校驗

使用以下 api 來鑒權(quán)了

// 獲取:當前賬號所擁有的權(quán)限集合
StpUtil.getPermissionList();
// 判斷:當前賬號是否含有指定權(quán)限, 返回 true 或 false
StpUtil.hasPermission("user.add");        
// 校驗:當前賬號是否含有指定權(quán)限, 如果驗證未通過,則拋出異常: NotPermissionException 
StpUtil.checkPermission("user.add");        
// 校驗:當前賬號是否含有指定權(quán)限 [指定多個,必須全部驗證通過]
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");        
// 校驗:當前賬號是否含有指定權(quán)限 [指定多個,只要其一驗證通過即可]
StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");    

1.5.4 角色校驗

在 Sa-Token 中,角色和權(quán)限可以分開獨立驗證

// 獲取:當前賬號所擁有的角色集合
StpUtil.getRoleList();
// 判斷:當前賬號是否擁有指定角色, 返回 true 或 false
StpUtil.hasRole("super-admin");        
// 校驗:當前賬號是否含有指定角色標識, 如果驗證未通過,則拋出異常: NotRoleException
StpUtil.checkRole("super-admin");        
// 校驗:當前賬號是否含有指定角色標識 [指定多個,必須全部驗證通過]
StpUtil.checkRoleAnd("super-admin", "shop-admin");        
// 校驗:當前賬號是否含有指定角色標識 [指定多個,只要其一驗證通過即可] 
StpUtil.checkRoleOr("super-admin", "shop-admin");        

1.5.5 權(quán)限通配符

Sa-Token允許根據(jù)通配符指定泛權(quán)限,例如當一個賬號擁有art.*的權(quán)限時,art.add、art.delete、art.update都將匹配通過

// 當擁有 art.* 權(quán)限時
StpUtil.hasPermission("art.add");        // true
StpUtil.hasPermission("art.update");     // true
StpUtil.hasPermission("goods.add");      // false

// 當擁有 *.delete 權(quán)限時
StpUtil.hasPermission("art.delete");      // true
StpUtil.hasPermission("user.delete");     // true
StpUtil.hasPermission("user.update");     // false

// 當擁有 *.js 權(quán)限時
StpUtil.hasPermission("index.js");        // true
StpUtil.hasPermission("index.css");       // false
StpUtil.hasPermission("index.html");      // false

1.6 踢人下線

所謂踢人下線,核心操作就是找到指定 loginId 對應的 Token,并設置其失效。

強制注銷

StpUtil.logout(10001);                    // 強制指定賬號注銷下線 
StpUtil.logout(10001, "PC");              // 強制指定賬號指定端注銷下線 
StpUtil.logoutByTokenValue("token");      // 強制指定 Token 注銷下線 

踢人下線

StpUtil.kickout(10001);                    // 將指定賬號踢下線 
StpUtil.kickout(10001, "PC");              // 將指定賬號指定端踢下線
StpUtil.kickoutByTokenValue("token");      // 將指定 Token 踢下線

強制注銷 和 踢人下線 的區(qū)別在于:

  • 強制注銷等價于對方主動調(diào)用了注銷方法,再次訪問會提示Token無效。
  • 踢人下線不會清除Token信息,而是將其打上特定標記,再次訪問會提示Token已被踢下線。

1.7 注解鑒權(quán)

1.7.1 介紹

  • @SaCheckLogin: 登錄校驗 —— 只有登錄之后才能進入該方法。
  • @SaCheckRole("admin"): 角色校驗 —— 必須具有指定角色標識才能進入該方法。
  • @SaCheckPermission("user:add"): 權(quán)限校驗 —— 必須具有指定權(quán)限才能進入該方法。
  • @SaCheckSafe: 二級認證校驗 —— 必須二級認證之后才能進入該方法。
  • @SaCheckBasic: HttpBasic校驗 —— 只有通過 Basic 認證后才能進入該方法。
  • @SaIgnore:忽略校驗 —— 表示被修飾的方法或類無需進行注解鑒權(quán)和路由攔截器鑒權(quán)。
    • 修飾方法時代表這個方法可以被游客訪問,修飾類時代表這個類中的所有接口都可以游客訪問。
    • 具有最高優(yōu)先級,當 @SaIgnore 和其它鑒權(quán)注解一起出現(xiàn)時,其它鑒權(quán)注解都將被忽略。
    • 同樣可以忽略掉 Sa-Token 攔截器中的路由鑒權(quán)
  • @SaCheckDisable("comment"):賬號服務封禁校驗 —— 校驗當前賬號指定服務是否被封禁。

注意:以上注解都可以加在類上,代表為這個類所有方法進行鑒權(quán)

Sa-Token 使用全局攔截器完成注解鑒權(quán)功能,為了不為項目帶來不必要的性能負擔,攔截器默認處于關閉狀態(tài),使用攔截器模式,只能在Controller層進行注解鑒權(quán) ,因此,為了使用注解鑒權(quán),必須手動將 Sa-Token 的全局攔截器注冊到項目中

1.7.2 注冊攔截器

以SpringBoot2.0為例,新建配置類SaTokenConfigure.java

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注冊 Sa-Token 攔截器,打開注解式鑒權(quán)功能 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注冊 Sa-Token 攔截器,打開注解式鑒權(quán)功能 
        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");    
    }
}

注意:使用攔截器模式,只能在Controller層進行注解鑒權(quán)

1.7.3 設定校驗模式

@SaCheckRole@SaCheckPermission注解可設置校驗模式,例如:

// 注解式鑒權(quán):只要具有其中一個權(quán)限即可通過校驗 
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)        
public SaResult atJurOr() {
    return SaResult.data("用戶信息");
}

mode有兩種取值:

  • SaMode.AND:標注一組權(quán)限,會話必須全部具有才可通過校驗。
  • SaMode.OR:標注一組權(quán)限,會話只要具有其一即可通過校驗。

1.7.4 角色權(quán)限雙重 or校驗

假設有以下業(yè)務場景:一個接口在具有權(quán)限 user.add 或角色 admin 時可以調(diào)通。怎么寫?

// 角色權(quán)限雙重 “or校驗”:具備指定權(quán)限或者指定角色即可通過校驗
@RequestMapping("userAdd")
@SaCheckPermission(value = "user.add", orRole = "admin")        
public SaResult userAdd() {
    return SaResult.data("用戶信息");
}

orRole 字段代表權(quán)限校驗未通過時的次要選擇,兩者只要其一校驗成功即可進入請求方法,其有三種寫法:

  • rRole = "admin":代表需要擁有角色 admin 。
  • orRole = {"admin", "manager", "staff"}:代表具有三個角色其一即可。
  • orRole = {"admin, manager, staff"}:代表必須同時具有三個角色。

1.7.5 批量注解鑒權(quán)

使用 @SaCheckOr 表示批量注解鑒權(quán):

// 在 `@SaCheckOr` 中可以指定多個注解,只要當前會話滿足其中一個注解即可通過驗證,進入方法。
@SaCheckOr(
        login = @SaCheckLogin,
        role = @SaCheckRole("admin"),
        permission = @SaCheckPermission("user.add"),
        safe = @SaCheckSafe("update-password"),
        basic = @SaCheckBasic(account = "sa:123456"),
        disable = @SaCheckDisable("submit-orders")
)
@RequestMapping("test")
public SaResult test() {
    // ... 
    return SaResult.ok(); 
}

每一項屬性都可以寫成數(shù)組形式,例如:

// 當前客戶端只要有 [ login 賬號登錄] 或者 [user 賬號登錄] 其一,就可以通過驗證進入方法。
// 注意:`type = "login"` 和 `type = "user"` 是多賬號模式章節(jié)的擴展屬性,此處你可以先略過這個知識點。
@SaCheckOr(
    login = { @SaCheckLogin(type = "login"), @SaCheckLogin(type = "user") }
)
@RequestMapping("test")
public SaResult test() {
    // ... 
    return SaResult.ok(); 
}

疑問:既然有了 @SaCheckOr,為什么沒有與之對應的 @SaCheckAnd 呢?
因為當寫多個注解時,其天然就是 and 校驗關系,例如:

// 當你在一個方法上寫多個注解鑒權(quán)時,其默認就是要滿足所有注解規(guī)則后,才可以進入方法,只要有一個不滿足,就會拋出異常
@SaCheckLogin
@SaCheckRole("admin")
@SaCheckPermission("user.add")
@RequestMapping("test")
public SaResult test() {
    // ... 
    return SaResult.ok(); 
}

1.7.6 關閉注解校驗

SaInterceptor 只要注冊到項目中,默認就會打開注解校驗,如果要關閉此能力,需要:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(
        new SaInterceptor(handle -> {
            SaRouter.match("/**").check(r -> StpUtil.checkLogin());
        }).isAnnotation(false)  // 指定關閉掉注解鑒權(quán)能力,這樣框架就只會做路由攔截校驗了 
    ).addPathPatterns("/**");
}

1.8 路由攔截

假設我們有如下需求:

項目中所有接口均需要登錄認證,只有 “登錄接口” 本身對外開放
我們怎么實現(xiàn)呢?給每個接口加上鑒權(quán)注解?手寫全局攔截器?似乎都不是非常方便。

在這個需求中我們真正需要的是一種基于路由攔截的鑒權(quán)模式,那么在Sa-Token怎么實現(xiàn)路由攔截鑒權(quán)呢?

1.8.1 注冊 Sa-Token 路由攔截器

以SpringBoot2.0為例,新建配置類SaTokenConfigure.java

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注冊攔截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注冊 Sa-Token 攔截器,校驗規(guī)則為 StpUtil.checkLogin() 登錄校驗。
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                .excludePathPatterns("/user/doLogin"); 
    }
}

以上代碼,我們注冊了一個基于 StpUtil.checkLogin() 的登錄校驗攔截器,并且排除了/user/doLogin接口用來開放登錄(除了/user/doLogin以外的所有接口都需要登錄才能訪問)

1.8.2 校驗函數(shù)詳解

自定義認證規(guī)則:new SaInterceptor(handle -> StpUtil.checkLogin()) 是最簡單的寫法,代表只進行登錄校驗功能。
我們可以往構(gòu)造函數(shù)塞一個完整的 lambda 表達式,來定義詳細的校驗規(guī)則,例如:

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注冊 Sa-Token 攔截器,定義詳細認證規(guī)則 
        registry.addInterceptor(new SaInterceptor(handler -> {
            // 指定一條 match 規(guī)則
            SaRouter
                .match("/**")    // 攔截的 path 列表,可以寫多個 */
                .notMatch("/user/doLogin")        // 排除掉的 path 列表,可以寫多個 
                .check(r -> StpUtil.checkLogin());        // 要執(zhí)行的校驗動作,可以寫完整的 lambda 表達式
                
            // 根據(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ù)有兩個參數(shù):要匹配的path路由,要執(zhí)行的校驗函數(shù)。
在校驗函數(shù)內(nèi)不只可以使用 StpUtil.checkPermission("xxx") 進行權(quán)限校驗,還可以寫任意代碼,例如:

// 甚至你可以隨意的寫一個打印語句
SaRouter.match("/**", r -> System.out.println("----啦啦啦----"));

// 連綴寫法
SaRouter.match("/**").check(r -> System.out.println("----啦啦啦----"));

1.8.3 匹配特征詳解

除了上述示例的 path 路由匹配,還可以根據(jù)很多其它特征進行匹配,以下是所有可匹配的特征:

// 基礎寫法樣例:匹配一個path,執(zhí)行一個校驗函數(shù) 
SaRouter.match("/user/**").check(r -> StpUtil.checkLogin());

// 根據(jù) path 路由匹配   ——— 支持寫多個path,支持寫 restful 風格路由 
// 功能說明: 使用 /user , /goods 或者 /art/get 開頭的任意路由都將進入 check 方法
SaRouter.match("/user/**", "/goods/**", "/art/get/{id}").check( /* 要執(zhí)行的校驗函數(shù) */ );

// 根據(jù) path 路由排除匹配 
// 功能說明: 使用 .html , .css 或者 .js 結(jié)尾的任意路由都將跳過, 不會進入 check 方法
SaRouter.match("/**").notMatch("*.html", "*.css", "*.js").check( /* 要執(zhí)行的校驗函數(shù) */ );

// 根據(jù)請求類型匹配 
SaRouter.match(SaHttpMethod.GET).check( /* 要執(zhí)行的校驗函數(shù) */ );

// 根據(jù)一個 boolean 條件進行匹配 
SaRouter.match( StpUtil.isLogin() ).check( /* 要執(zhí)行的校驗函數(shù) */ );

// 根據(jù)一個返回 boolean 結(jié)果的lambda表達式匹配 
SaRouter.match( r -> StpUtil.isLogin() ).check( /* 要執(zhí)行的校驗函數(shù) */ );

// 多個條件一起使用 
// 功能說明: 必須是 Get 請求 并且 請求路徑以 `/user/` 開頭 
SaRouter.match(SaHttpMethod.GET).match("/user/**").check( /* 要執(zhí)行的校驗函數(shù) */ );

// 可以無限連綴下去 
// 功能說明: 同時滿足 Get 方式請求, 且路由以 /admin 開頭, 路由中間帶有 /send/ 字符串, 路由結(jié)尾不能是 .js 和 .css
SaRouter
    .match(SaHttpMethod.GET)
    .match("/admin/**")
    .match("/**/send/**") 
    .notMatch("/**/*.js")
    .notMatch("/**/*.css")
    // ....
    .check( /* 只有上述所有條件都匹配成功,才會執(zhí)行最后的check校驗函數(shù) */ );

1.8.4 提前退出匹配鏈

使用 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函數(shù)處提前退出整個匹配函數(shù),從而忽略掉剩余的所有match匹配
除了stop()函數(shù),SaRouter還提供了 back() 函數(shù),用于:停止匹配,結(jié)束執(zhí)行,直接向前端返回結(jié)果

// 執(zhí)行back函數(shù)后將停止匹配,也不會進入Controller,而是直接將 back參數(shù) 作為返回值輸出到前端
SaRouter.match("/user/back").back("要返回到前端的內(nèi)容");

stop() 與 back() 函數(shù)的區(qū)別在于:

  • SaRouter.stop():會停止匹配,進入Controller。
  • SaRouter.back():會停止匹配,直接返回結(jié)果到前端。

1.8.5 使用free打開一個獨立的作用域

// 進入 free 獨立作用域 
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() 的作用是:打開一個獨立的作用域,使內(nèi)部的 stop() 不再一次性跳出整個 Auth 函數(shù),而是僅僅跳出當前 free 作用域。

1.8.6 使用注解忽略掉路由攔截校驗

我們可以使用 @SaIgnore 注解,忽略掉路由攔截認證:
在 Controller 里添加了忽略校驗的注解

@SaIgnore
@RequestMapping("/user/getList")
public SaResult getList() {
    System.out.println("------------ 訪問進來方法"); 
    return SaResult.ok(); 
}

請求將會跳過攔截器的校驗,直接進入 Controller 的方法中
注意:此注解的忽略效果只針對 SaInterceptor攔截器 和 AOP注解鑒權(quán) 生效,對自定義攔截器與過濾器不生效。

1.9 Session

1.9.1 簡介

Session 是會話中專業(yè)的數(shù)據(jù)緩存組件,通過 Session 我們可以很方便的緩存一些高頻讀寫數(shù)據(jù),提高程序性能,例如:

// 在登錄時緩存 user 對象 
StpUtil.getSession().set("user", user);

// 然后我們就可以在任意處使用這個 user 對象
SysUser user = (SysUser) StpUtil.getSession().get("user");

Sa-Token 中,Session 分為三種,分別是:

  • Account-Session: 指的是框架為每個 賬號id 分配的 Session
  • Token-Session: 指的是框架為每個 token 分配的 Session
  • Custom-Session: 指的是以一個 特定的值 作為SessionId,來分配的 Session

1.9.2 Account-Session

有關 賬號-Session 的 API 如下:

// 獲取當前賬號 id 的 Account-Session (必須是登錄后才能調(diào)用)
StpUtil.getSession();
// 獲取當前賬號 id 的 Account-Session, 并決定在 Session 尚未創(chuàng)建時,是否新建并返回
StpUtil.getSession(true);
// 獲取賬號 id 為 10001 的 Account-Session
StpUtil.getSessionByLoginId(10001);
// 獲取賬號 id 為 10001 的 Account-Session, 并決定在 Session 尚未創(chuàng)建時,是否新建并返回
StpUtil.getSessionByLoginId(10001, true);
// 獲取 SessionId 為 xxxx-xxxx 的 Account-Session, 在 Session 尚未創(chuàng)建時, 返回 null 
StpUtil.getSessionBySessionId("xxxx-xxxx");
復制到剪貼板錯誤復制成功
Token-Session
有關 令牌-Session 的 API 如下:
// 獲取當前 Token 的 Token-Session 對象
StpUtil.getTokenSession();
// 獲取指定 Token 的 Token-Session 對象
StpUtil.getTokenSessionByToken(token);

1.9.3 Custom-Session

自定義 Session 指的是以一個特定的值作為 SessionId 來分配的Session, 借助自定義Session,可以為系統(tǒng)中的任意元素分配相應的session
例如以商品 id 作為 key 為每個商品分配一個Session,以便于緩存和商品相關的數(shù)據(jù),其相關API如下:

// 查詢指定key的Session是否存在
SaSessionCustomUtil.isExists("goods-10001");
// 獲取指定key的Session,如果沒有,則新建并返回
SaSessionCustomUtil.getSessionById("goods-10001");
// 獲取指定key的Session,如果沒有,第二個參數(shù)決定是否新建并返回  
SaSessionCustomUtil.getSessionById("goods-10001", false);   
// 刪除指定key的Session
SaSessionCustomUtil.deleteSessionById("goods-10001");

1.9.4 在 Session 上存取值

以上三種 Session 均為框架設計概念上的區(qū)分,實際上在獲取它們時,返回的都是 SaSession 對象,你可以使用以下 API 在 SaSession 對象上存取值:

// 寫值 
session.set("name", "zhang"); 
// 寫值 (只有在此key原本無值的時候才會寫入)
session.setDefaultValue("name", "zhang");
// 取值
session.get("name");
// 取值 (指定默認值)
session.get("name", "<defaultValue>"); 
// 取值 (若無值則執(zhí)行參數(shù)方法, 之后將結(jié)果保存到此鍵名下,并返回此結(jié)果   若有值則直接返回, 無需執(zhí)行參數(shù)方法)
session.get("name", () -> {
            return ...;
        });
// ---------- 數(shù)據(jù)類型轉(zhuǎn)換: ----------
session.getInt("age");         // 取值 (轉(zhuǎn)int類型)
session.getLong("age");        // 取值 (轉(zhuǎn)long類型)
session.getString("name");     // 取值 (轉(zhuǎn)String類型)
session.getDouble("result");   // 取值 (轉(zhuǎn)double類型)
session.getFloat("result");    // 取值 (轉(zhuǎn)float類型)
session.getModel("key", Student.class);     // 取值 (指定轉(zhuǎn)換類型)
session.getModel("key", Student.class, <defaultValue>);  // 取值 (指定轉(zhuǎn)換類型, 并指定值為Null時返回的默認值)
// 是否含有某個key (返回 true 或 false)
session.has("key"); 
// 刪值 
session.delete('name');          
// 清空所有值 
session.clear();                 
// 獲取此 Session 的所有key (返回Set<String>)
session.keys();      

1.10 框架配置

可以零配置啟動框架,但同時也可以通過一定的參數(shù)配置,定制性使用框架,Sa-Token支持多種方式配置框架信息

1.10.1 配置

1.10.1.1 yml配置

yaml 風格
############## Sa-Token 配置 (文檔: https://sa-token.cc) ##############
sa-token: 
    # token 名稱(同時也是 cookie 名稱)
    token-name: satoken
    # token 有效期(單位:秒) 默認30天,-1 代表永久有效
    timeout: 2592000
    # token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統(tǒng)就會被凍結(jié),默認-1 代表不限制,永不凍結(jié)
    active-timeout: -1
    # 是否允許同一賬號多地同時登錄 (為 true 時允許一起登錄, 為 false 時新登錄擠掉舊登錄)
    is-concurrent: true
    # 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token, 為 false 時每次登錄新建一個 token)
    is-share: true
    # token 風格(默認可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
    token-style: uuid
    # 是否輸出操作日志 
    is-log: true

1.10.1.2 通過代碼配置

模式 1

/**
 * Sa-Token 配置類
 */
@Configuration
public class SaTokenConfigure {
    // Sa-Token 參數(shù)配置,參考文檔:https://sa-token.cc
    // 此配置會覆蓋 application.yml 中的配置
    @Bean
    @Primary
    public SaTokenConfig getSaTokenConfigPrimary() {
        SaTokenConfig config = new SaTokenConfig();
        config.setTokenName("satoken");             // token 名稱(同時也是 cookie 名稱)
        config.setTimeout(30 * 24 * 60 * 60);       // token 有效期(單位:秒),默認30天,-1代表永不過期 
        config.setActiveTimeout(-1);              // token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統(tǒng)就會被凍結(jié),默認-1 代表不限制,永不凍結(jié)
        config.setIsConcurrent(true);               // 是否允許同一賬號多地同時登錄(為 true 時允許一起登錄,為 false 時新登錄擠掉舊登錄)
        config.setIsShare(true);                    // 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token,為 false 時每次登錄新建一個 token)
        config.setTokenStyle("uuid");               // token 風格
        config.setIsLog(false);                     // 是否輸出操作日志 
        return config;
    }
}

模式 2

/**
 * Sa-Token 配置類
 */
@Configuration
public class SaTokenConfigure {
    // Sa-Token 參數(shù)配置,參考文檔:https://sa-token.cc
    // 此配置會與 application.yml 中的配置合并 (代碼配置優(yōu)先)
    @Autowired
    public void configSaToken(SaTokenConfig config) {
        config.setTokenName("satoken");             // token 名稱(同時也是 cookie 名稱)
        config.setTimeout(30 * 24 * 60 * 60);       // token 有效期(單位:秒),默認30天,-1代表永不過期 
        config.setActiveTimeout(-1);              // token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統(tǒng)就會被凍結(jié),默認-1 代表不限制,永不凍結(jié)
        config.setIsConcurrent(true);               // 是否允許同一賬號多地同時登錄(為 true 時允許一起登錄,為 false 時新登錄擠掉舊登錄)
        config.setIsShare(true);                    // 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token,為 false 時每次登錄新建一個 token)
        config.setTokenStyle("uuid");               // token 風格
        config.setIsLog(false);                     // 是否輸出操作日志 
    }
}

兩者的區(qū)別在于:

  • 模式 1 會覆蓋 application.yml 中的配置。
  • 模式 2 會與 application.yml 中的配置合并(代碼配置優(yōu)先)。

1.10.2 配置項

參數(shù)名稱 類型 默認值 說明
tokenName String satoken Token 名稱 (同時也是 Cookie 名稱、數(shù)據(jù)持久化前綴)
timeout long 2592000 Token 有效期(單位:秒),默認30天,-1代表永不過期
activeTimeout long -1 Token 最低活躍頻率(單位:秒),如果 token 超過此時間沒有訪問系統(tǒng)就會被凍結(jié),默認-1 代表不限制,永不凍結(jié)(例如可以設置為1800代表30分鐘內(nèi)無操作就凍結(jié))
dynamicActiveTimeout Boolean false 是否啟用動態(tài) activeTimeout 功能,如不需要請設置為 false,節(jié)省緩存請求次數(shù)
isConcurrent Boolean true 是否允許同一賬號并發(fā)登錄 (為 true 時允許一起登錄,為 false 時新登錄擠掉舊登錄)
isShare Boolean true 在多人登錄同一賬號時,是否共用一個 token (為 true 時所有登錄共用一個 token,為 false 時每次登錄新建一個 token)
maxLoginCount int 12 同一賬號最大登錄數(shù)量,-1代表不限 (只有在 isConcurrent=true,isShare=false 時此配置才有效)
maxTryTimes int 12 在每次創(chuàng)建 Token 時的最高循環(huán)次數(shù),用于保證 Token 唯一性(-1=不循環(huán)重試,直接使用)
isReadBody Boolean true 是否嘗試從 請求體 里讀取 Token
isReadHeader Boolean true 是否嘗試從 header 里讀取 Token
isReadCookie Boolean true 是否嘗試從 cookie 里讀取 Token,此值為 false 后,StpUtil.login(id) 登錄時也不會再往前端注入Cookie
isWriteHeader Boolean false 是否在登錄后將 Token 寫入到響應頭
tokenStyle String uuid token風格
dataRefreshPeriod int 30 默認數(shù)據(jù)持久組件實現(xiàn)類中,每次清理過期數(shù)據(jù)間隔的時間 (單位: 秒) ,默認值30秒,設置為-1代表不啟動定時清理
tokenSessionCheckLogin Boolean true 獲取 Token-Session 時是否必須登錄 (如果配置為true,會在每次獲取 Token-Session 時校驗是否登錄)
autoRenew Boolean true 是否打開自動續(xù)簽 (如果此值為true,框架會在每次直接或間接調(diào)用 getLoginId() 時進行一次過期檢查與續(xù)簽操作)
tokenPrefix String null token前綴,例如填寫 Bearer 實際傳參 satoken: Bearer xxxx-xxxx-xxxx-xxxx
isPrint Boolean true 是否在初始化配置時打印版本字符畫
isLog Boolean false 是否打印操作日志
logLevel String trace 日志等級(trace、debug、info、warn、error、fatal),此值與 logLevelInt 聯(lián)動
logLevelInt int 1 日志等級 int 值(1=trace、2=debug、3=info、4=warn、5=error、6=fatal),此值與 logLevel 聯(lián)動
isColorLog Boolean null 是否打印彩色日志,true=打印彩色日志,false=打印黑白日志,null=框架根據(jù)運行終端自行判斷是否打印彩色日志
jwtSecretKey String null jwt秘鑰 (只有集成 sa-token-temp-jwt 模塊時此參數(shù)才會生效)
sameTokenTimeout long 86400 Same-Token的有效期 (單位: 秒)
basic String "" Http Basic 認證的賬號和密碼
currDomain String null 配置當前項目的網(wǎng)絡訪問地址
checkSameToken Boolean false 是否校驗Same-Token(部分rpc插件有效)
cookie Object new SaCookieConfig() Cookie配置對象

Cookie相關配置:

參數(shù)名稱 類型 默認值 說明
domain String null 作用域(寫入Cookie時顯式指定的作用域, 常用于單點登錄二級域名共享Cookie的場景)
path String / 路徑,默認寫在域名根路徑下
secure Boolean false 是否只在 https 協(xié)議下有效
httpOnly Boolean false 是否禁止 js 操作 Cookie
sameSite String Lax 第三方限制級別(Strict=完全禁止,Lax=部分允許,None=不限制)

Cookie 配置示例:

# Sa-Token 配置
sa-token: 
    # Cookie 相關配置 
    cookie: 
        domain: stp.com
        path: /
        secure: false
        httpOnly: true
        sameSite: Lax
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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