之前的章節中,我們學習了 踢人下線 和 強制登出 功能,用於清退違規賬號。在部分場景下,我們還需要將其 賬號封禁,以防止其再次登入。
Sa-Token 是一個輕量級 java 許可權認證框架,主要解決登入認證、許可權認證、單點登入、OAuth2、微服務閘道器鑑權 等一系列許可權相關問題。
Gitee 開源地址:https://gitee.com/dromara/sa-token
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
即可。
對指定賬號進行封禁:
// 封禁指定賬號
StpUtil.disable(10001, 86400);
引數含義:
注意點:對於正在登入的賬號,將其封禁並不會使它立即掉線,如果我們需要它即刻下線,可採用先踢再封禁的策略,例如:
// 先踢下線
StpUtil.kickout(10001);
// 再封禁賬號
StpUtil.disable(10001, 86400);
待到下次登入時,我們先校驗一下這個賬號是否已被封禁:
// 校驗指定賬號是否已被封禁,如果被封禁則丟擲異常 `DisableServiceException`
StpUtil.checkDisable(10001);
// 通過校驗後,再進行登入:
StpUtil.login(10001);
舊版本在
StpUtil.login()
時會自動校驗賬號是否被封禁,v1.31.0 之後將 校驗封禁 和 登入 兩個動作分離成兩個方法,不再自動校驗,請注意其中的邏輯更改。
此模組所有方法:
// 封禁指定賬號
StpUtil.disable(10001, 86400);
// 獲取指定賬號是否已被封禁 (true=已被封禁, false=未被封禁)
StpUtil.isDisable(10001);
// 校驗指定賬號是否已被封禁,如果被封禁則丟擲異常 `DisableServiceException`
StpUtil.checkDisable(10001);
// 獲取指定賬號剩餘封禁時間,單位:秒,如果該賬號未被封禁,則返回-2
StpUtil.getDisableTime(10001);
// 解除封禁
StpUtil.untieDisable(10001);
有的時候,我們並不需要將整個賬號禁掉,而是隻禁止其存取部分服務。
假設我們在開發一個電商系統,對於違規賬號的處罰,我們設定三種分類封禁:
相比於封禁賬號的一刀切處罰,這裡的關鍵點在於:每一項能力封禁的同時,都不會對其它能力造成影響。
也就是說我們需要一種只對部分服務進行限制的能力,對應到程式碼層面,就是隻禁止部分介面的呼叫。
// 封禁指定使用者評論能力,期限為 1天
StpUtil.disable(10001, "comment", 86400);
引數釋義:
分類封禁模組所有可用API:
/*
* 以下範例中:"comment"=評論服務標識、"place-order"=下單服務標識、"open-shop"=開店服務標識
*/
// 封禁指定使用者評論能力,期限為 1天
StpUtil.disable(10001, "comment", 86400);
// 在評論介面,校驗一下,會丟擲異常:`DisableServiceException`,使用 e.getService() 可獲取業務標識 `comment`
StpUtil.checkDisable(10001, "comment");
// 在下單時,我們校驗一下 下單能力,並不會丟擲異常,因為我們沒有限制其下單功能
StpUtil.checkDisable(10001, "place-order");
// 現在我們再將其下單能力封禁一下,期限為 7天
StpUtil.disable(10001, "place-order", 86400 * 7);
// 然後在下單介面,我們新增上校驗程式碼,此時使用者便會因為下單能力被封禁而無法下單(程式碼丟擲異常)
StpUtil.checkDisable(10001, "place-order");
// 但是此時,使用者如果呼叫開店功能的話,還是可以通過,因為我們沒有限制其開店能力 (除非我們再呼叫了封禁開店的程式碼)
StpUtil.checkDisable(10001, "open-shop");
通過以上範例,你應該大致可以理解 業務封禁 -> 業務校驗
的處理步驟。
有關分類封禁的所有方法:
// 封禁:指定賬號的指定服務
StpUtil.disable(10001, "<業務標識>", 86400);
// 判斷:指定賬號的指定服務 是否已被封禁 (true=已被封禁, false=未被封禁)
StpUtil.isDisable(10001, "<業務標識>");
// 校驗:指定賬號的指定服務 是否已被封禁,如果被封禁則丟擲異常 `DisableServiceException`
StpUtil.checkDisable(10001, "<業務標識>");
// 獲取:指定賬號的指定服務 剩餘封禁時間,單位:秒(-1=永久封禁,-2=未被封禁)
StpUtil.getDisableTime(10001, "<業務標識>");
// 解封:指定賬號的指定服務
StpUtil.untieDisable(10001, "<業務標識>");
對於多次違規的使用者,我們常常採取階梯處罰的策略,這種 「階梯」 一般有兩種形式:
基於處罰時間的階梯,我們只需在封禁時 StpUtil.disable(10001, 86400)
傳入不同的封禁時間即可,下面我們著重探討一下基於處罰力度的階梯形式。
假設我們在開發一個論壇系統,對於違規賬號的處罰,我們設定三種力度:
解決這種需求的關鍵在於,我們需要把不同處罰力度,量化成不同的處罰等級,比如上述的 輕度
、中度
、重度
3 個力度,
我們將其量化為一級封禁
、二級封禁
、三級封禁
3個等級,數位越大代表封禁力度越高。
然後我們就可以使用階梯封禁的API,進行鑑權了:
// 階梯封禁,引數:封禁賬號、封禁級別、封禁時間
StpUtil.disableLevel(10001, 3, 10000);
// 獲取:指定賬號封禁的級別 (如果此賬號未被封禁則返回 -2)
StpUtil.getDisableLevel(10001);
// 判斷:指定賬號是否已被封禁到指定級別,返回 true 或 false
StpUtil.isDisableLevel(10001, 3);
// 校驗:指定賬號是否已被封禁到指定級別,如果已達到此級別(例如已被3級封禁,這裡校驗是否達到2級),則丟擲異常 `DisableServiceException`
StpUtil.checkDisableLevel(10001, 2);
注意點:DisableServiceException
異常代表當前賬號未通過封禁校驗,可以:
e.getLevel()
獲取這個賬號實際被封禁的等級。e.getLimitLevel()
獲取這個賬號在校驗時要求低於的等級。當 Level >= LimitLevel
時,框架就會丟擲異常。如果業務足夠複雜,我們還可能將 分類封禁 和 階梯封禁 組合使用:
// 分類階梯封禁,引數:封禁賬號、封禁服務、封禁級別、封禁時間
StpUtil.disableLevel(10001, "comment", 3, 10000);
// 獲取:指定賬號的指定服務 封禁的級別 (如果此賬號未被封禁則返回 -2)
StpUtil.getDisableLevel(10001, "comment");
// 判斷:指定賬號的指定服務 是否已被封禁到指定級別,返回 true 或 false
StpUtil.isDisableLevel(10001, "comment", 3);
// 校驗:指定賬號的指定服務 是否已被封禁到指定級別(例如 comment服務 已被3級封禁,這裡校驗是否達到2級),如果已達到此級別,則丟擲異常
StpUtil.checkDisableLevel(10001, "comment", 2);
首先我們需要註冊 Sa-Token 全域性攔截器:
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 註冊 Sa-Token 攔截器,開啟註解式鑑權功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
然後我們就可以使用以下註解校驗賬號是否封禁:
// 校驗當前賬號是否被封禁,如果已被封禁會丟擲異常,無法進入方法
@SaCheckDisable
@PostMapping("send")
public SaResult send() {
// ...
return SaResult.ok();
}
// 校驗當前賬號是否被封禁 comment 服務,如果已被封禁會丟擲異常,無法進入方法
@SaCheckDisable("comment")
@PostMapping("send")
public SaResult send() {
// ...
return SaResult.ok();
}
// 校驗當前賬號是否被封禁 comment、place-order、open-shop 等服務,指定多個值,只要有一個已被封禁,就無法進入方法
@SaCheckDisable({"comment", "place-order", "open-shop"})
@PostMapping("send")
public SaResult send() {
// ...
return SaResult.ok();
}
// 階梯封禁,校驗當前賬號封禁等級是否達到5級,如果達到則丟擲異常
@SaCheckDisable(level = 5)
@PostMapping("send")
public SaResult send() {
// ...
return SaResult.ok();
}
// 分類封禁 + 階梯封禁 校驗:校驗當前賬號的 comment 服務,封禁等級是否達到5級,如果達到則丟擲異常
@SaCheckDisable(value = "comment", level = 5)
@PostMapping("send")
public SaResult send() {
// ...
return SaResult.ok();
}
package com.pj.cases.up;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token 賬號封禁範例
*
* @author kong
* @since 2022-10-17
*/
@RestController
@RequestMapping("/disable/")
public class DisableController {
// 對談登入介面 ---- http://localhost:8081/disable/login?userId=10001
@RequestMapping("login")
public SaResult login(long userId) {
// 1、先檢查此賬號是否已被封禁
StpUtil.checkDisable(userId);
// 2、檢查通過後,再登入
StpUtil.login(userId);
return SaResult.ok("賬號登入成功");
}
// 對談登出介面 ---- http://localhost:8081/disable/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok("賬號退出成功");
}
// 封禁指定賬號 ---- http://localhost:8081/disable/disable?userId=10001
@RequestMapping("disable")
public SaResult disable(long userId) {
/*
* 賬號封禁:
* 引數1:要封禁的賬號id
* 引數2:要封禁的時間,單位:秒,86400秒=1天
*/
StpUtil.disable(userId, 86400);
return SaResult.ok("賬號 " + userId + " 封禁成功");
}
// 解封指定賬號 ---- http://localhost:8081/disable/untieDisable?userId=10001
@RequestMapping("untieDisable")
public SaResult untieDisable(long userId) {
StpUtil.untieDisable(userId);
return SaResult.ok("賬號 " + userId + " 解封成功");
}
}
測試步驟:
http://localhost:8081/disable/login?userId=10001
http://localhost:8081/disable/logout
http://localhost:8081/disable/disable?userId=10001
http://localhost:8081/disable/login?userId=10001
http://localhost:8081/disable/untieDisable?userId=10001
http://localhost:8081/disable/login?userId=10001