SpringBoot 使用 Sa-Token 實現(xiàn)賬號封禁腔呜、分類封禁、階梯封禁

一再悼、需求分析

之前的章節(jié)中核畴,我們學(xué)習(xí)了 踢人下線 和 強制注銷 功能,用于清退違規(guī)賬號冲九。在部分場景下谤草,我們還需要將其 賬號封禁,以防止其再次登錄莺奸。

Sa-Token 是一個輕量級 java 權(quán)限認證框架丑孩,主要解決登錄認證、權(quán)限認證灭贷、單點登錄温学、OAuth2、微服務(wù)網(wǎng)關(guān)鑒權(quán) 等一系列權(quán)限相關(guān)問題甚疟。
Gitee 開源地址:https://gitee.com/dromara/sa-token

Sa-Token 提供的封禁操作有三種:

  • 賬號封禁:封禁掉一個賬號的登錄能力仗岖,使其無法登錄逃延。
  • 分類封禁:封禁掉一個賬號的部分業(yè)務(wù)操作權(quán)限,不影響賬號的整體登錄等基礎(chǔ)功能箩帚。
  • 階梯封禁:按照不同的違規(guī)程度真友,給與其不同的封禁力度黄痪。

本篇文章將介紹在 Sa-Token 中如何完成上述三種封禁操作紧帕。

首先在項目中引入 Sa-Token 依賴:

<!-- Sa-Token 權(quá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 即可桅打。

二是嗜、賬號封禁

對指定賬號進行封禁:

// 封禁指定賬號 
StpUtil.disable(10001, 86400); 

參數(shù)含義:

  • 參數(shù)1:要封禁的賬號id。
  • 參數(shù)2:封禁時間挺尾,單位:秒鹅搪,此為 86400秒 = 1天(此值為 -1 時,代表永久封禁)遭铺。

注意點:對于正在登錄的賬號丽柿,將其封禁并不會使它立即掉線,如果我們需要它即刻下線魂挂,可采用先踢再封禁的策略甫题,例如:

// 先踢下線
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); 

三歉闰、分類封禁

有的時候,我們并不需要將整個賬號禁掉舍杜,而是只禁止其訪問部分服務(wù)新娜。

假設(shè)我們在開發(fā)一個電商系統(tǒng),對于違規(guī)賬號的處罰既绩,我們設(shè)定三種分類封禁:

  • 1概龄、封禁評價能力:賬號A 因為多次虛假好評,被限制訂單評價功能饲握。
  • 2私杜、封禁下單能力:賬號B 因為多次薅羊毛蚕键,被限制下單功能。
  • 3衰粹、封禁開店能力:賬號C 因為店鋪銷售假貨锣光,被限制開店功能。

相比于封禁賬號的一刀切處罰铝耻,這里的關(guān)鍵點在于:每一項能力封禁的同時誊爹,都不會對其它能力造成影響。

也就是說我們需要一種只對部分服務(wù)進行限制的能力瓢捉,對應(yīng)到代碼層面频丘,就是只禁止部分接口的調(diào)用。

// 封禁指定用戶評論能力泡态,期限為 1天
StpUtil.disable(10001, "comment", 86400);

參數(shù)釋義:

  • 參數(shù)1:要封禁的賬號id搂漠。
  • 參數(shù)2:針對這個賬號,要封禁的服務(wù)標識(可以是任意的自定義字符串)某弦。
  • 參數(shù)3:要封禁的時間桐汤,單位:秒,此為 86400秒 = 1天(此值為 -1 時靶壮,代表永久封禁)怔毛。

分類封禁模塊所有可用API:

/*
 * 以下示例中:"comment"=評論服務(wù)標識、"place-order"=下單服務(wù)標識亮钦、"open-shop"=開店服務(wù)標識
 */

// 封禁指定用戶評論能力馆截,期限為 1天
StpUtil.disable(10001, "comment", 86400);

// 在評論接口,校驗一下蜂莉,會拋出異常:`DisableServiceException`蜡娶,使用 e.getService() 可獲取業(yè)務(wù)標識 `comment` 
StpUtil.checkDisable(10001, "comment");

// 在下單時,我們校驗一下 下單能力映穗,并不會拋出異常窖张,因為我們沒有限制其下單功能
StpUtil.checkDisable(10001, "place-order");

// 現(xiàn)在我們再將其下單能力封禁一下,期限為 7天 
StpUtil.disable(10001, "place-order", 86400 * 7);

// 然后在下單接口蚁滋,我們添加上校驗代碼宿接,此時用戶便會因為下單能力被封禁而無法下單(代碼拋出異常)
StpUtil.checkDisable(10001, "place-order");

// 但是此時,用戶如果調(diào)用開店功能的話辕录,還是可以通過睦霎,因為我們沒有限制其開店能力 (除非我們再調(diào)用了封禁開店的代碼)
StpUtil.checkDisable(10001, "open-shop");

通過以上示例,你應(yīng)該大致可以理解 業(yè)務(wù)封禁 -> 業(yè)務(wù)校驗 的處理步驟走诞。

有關(guān)分類封禁的所有方法:

// 封禁:指定賬號的指定服務(wù) 
StpUtil.disable(10001, "<業(yè)務(wù)標識>", 86400); 

// 判斷:指定賬號的指定服務(wù) 是否已被封禁 (true=已被封禁, false=未被封禁) 
StpUtil.isDisable(10001, "<業(yè)務(wù)標識>"); 

// 校驗:指定賬號的指定服務(wù) 是否已被封禁副女,如果被封禁則拋出異常 `DisableServiceException`
StpUtil.checkDisable(10001, "<業(yè)務(wù)標識>"); 

// 獲取:指定賬號的指定服務(wù) 剩余封禁時間蚣旱,單位:秒(-1=永久封禁碑幅,-2=未被封禁)
StpUtil.getDisableTime(10001, "<業(yè)務(wù)標識>"); 

// 解封:指定賬號的指定服務(wù)
StpUtil.untieDisable(10001, "<業(yè)務(wù)標識>"); 

四戴陡、階梯封禁

對于多次違規(guī)的用戶,我們常常采取階梯處罰的策略沟涨,這種 “階梯” 一般有兩種形式:

  • 處罰時間階梯:首次違規(guī)封禁 1 天恤批,第二次封禁 7 天,第三次封禁 30 天裹赴,依次順延……
  • 處罰力度階梯:首次違規(guī)消息提醒喜庞、第二次禁言禁評論、第三次禁止賬號登錄篮昧,等等……

基于處罰時間的階梯赋荆,我們只需在封禁時 StpUtil.disable(10001, 86400) 傳入不同的封禁時間即可笋妥,下面我們著重探討一下基于處罰力度的階梯形式懊昨。

假設(shè)我們在開發(fā)一個論壇系統(tǒng),對于違規(guī)賬號的處罰春宣,我們設(shè)定三種力度:

  • 1酵颁、輕度違規(guī):封禁其發(fā)帖、評論能力月帝,但允許其點贊躏惋、關(guān)注等操作。
  • 2嚷辅、中度違規(guī):封禁其發(fā)帖簿姨、評論、點贊簸搞、關(guān)注等一切與別人互動的能力扁位,但允許其瀏覽帖子、瀏覽評論趁俊。
  • 3域仇、重度違規(guī):封禁其登錄功能寺擂,限制一切能力暇务。

解決這種需求的關(guān)鍵在于,我們需要把不同處罰力度怔软,量化成不同的處罰等級垦细,比如上述的 輕度中度挡逼、重度 3 個力度括改,
我們將其量化為一級封禁二級封禁挚瘟、三級封禁 3個等級叹谁,數(shù)字越大代表封禁力度越高饲梭。

然后我們就可以使用階梯封禁的API,進行鑒權(quán)了:

// 階梯封禁焰檩,參數(shù):封禁賬號憔涉、封禁級別、封禁時間 
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 時,框架就會拋出異常峦萎。

如果業(yè)務(wù)足夠復(fù)雜屡久,我們還可能將 分類封禁 和 階梯封禁 組合使用:

// 分類階梯封禁,參數(shù):封禁賬號爱榔、封禁服務(wù)被环、封禁級別、封禁時間 
StpUtil.disableLevel(10001, "comment", 3, 10000);

// 獲认暧摹:指定賬號的指定服務(wù) 封禁的級別 (如果此賬號未被封禁則返回 -2)
StpUtil.getDisableLevel(10001, "comment");

// 判斷:指定賬號的指定服務(wù) 是否已被封禁到指定級別筛欢,返回 true 或 false
StpUtil.isDisableLevel(10001, "comment", 3);

// 校驗:指定賬號的指定服務(wù) 是否已被封禁到指定級別(例如 comment服務(wù) 已被3級封禁,這里校驗是否達到2級)唇聘,如果已達到此級別版姑,則拋出異常 
StpUtil.checkDisableLevel(10001, "comment", 2);

五、使用注解完成封禁校驗

首先我們需要注冊 Sa-Token 全局攔截器:

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

然后我們就可以使用以下注解校驗賬號是否封禁:

// 校驗當前賬號是否被封禁漠酿,如果已被封禁會拋出異常,無法進入方法 
@SaCheckDisable
@PostMapping("send")
public SaResult send() {
    // ... 
    return SaResult.ok(); 
}

// 校驗當前賬號是否被封禁 comment 服務(wù)谎亩,如果已被封禁會拋出異常炒嘲,無法進入方法 
@SaCheckDisable("comment")
@PostMapping("send")
public SaResult send() {
    // ... 
    return SaResult.ok(); 
}

// 校驗當前賬號是否被封禁 comment、place-order匈庭、open-shop 等服務(wù)夫凸,指定多個值,只要有一個已被封禁阱持,就無法進入方法 
@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 服務(wù),封禁等級是否達到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) {
        /*
         * 賬號封禁:
         *  參數(shù)1:要封禁的賬號id
         *  參數(shù)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 + " 解封成功");
    }

}

測試步驟:

  1. 訪問登錄接口,可以正常登錄 ---- http://localhost:8081/disable/login?userId=10001
  2. 注銷登錄 ---- http://localhost:8081/disable/logout
  3. 禁用賬號 ---- http://localhost:8081/disable/disable?userId=10001
  4. 再次訪問登錄接口慰于,登錄失敗 ---- http://localhost:8081/disable/login?userId=10001
  5. 解封賬號 ---- http://localhost:8081/disable/untieDisable?userId=10001
  6. 再次訪問登錄接口钮科,登錄成功 ---- http://localhost:8081/disable/login?userId=10001

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市婆赠,隨后出現(xiàn)的幾起案子绵脯,更是在濱河造成了極大的恐慌,老刑警劉巖页藻,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桨嫁,死亡現(xiàn)場離奇詭異,居然都是意外死亡份帐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門楣导,熙熙樓的掌柜王于貴愁眉苦臉地迎上來废境,“玉大人,你說我怎么就攤上這事筒繁∝迹” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵毡咏,是天一觀的道長驮宴。 經(jīng)常有香客問我,道長呕缭,這世上最難降的妖魔是什么堵泽? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮恢总,結(jié)果婚禮上迎罗,老公的妹妹穿的比我還像新娘。我一直安慰自己片仿,他們只是感情好纹安,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般厢岂。 火紅的嫁衣襯著肌膚如雪光督。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天塔粒,我揣著相機與錄音可帽,去河邊找鬼。 笑死窗怒,一個胖子當著我的面吹牛映跟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扬虚,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼努隙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辜昵?” 一聲冷哼從身側(cè)響起荸镊,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎堪置,沒想到半個月后躬存,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡舀锨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年岭洲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坎匿。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡盾剩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出替蔬,到底是詐尸還是另有隱情告私,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布承桥,位于F島的核電站驻粟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏凶异。R本人自食惡果不足惜蜀撑,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唠帝。 院中可真熱鬧屯掖,春花似錦、人聲如沸襟衰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绍坝,卻和暖如春徘意,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轩褐。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工椎咧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人把介。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓勤讽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拗踢。 傳聞我的和親對象是個殘疾皇子脚牍,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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