對敏感操作的二次認證 —— 詳解 Sa-Token 二級認證

2023-07-05 09:00:19

一、需求分析

在某些敏感操作下,我們需要對已登入的對談進行二次驗證。

比如程式碼託管平臺的倉庫刪除操作,儘管我們已經登入了賬號,當我們點選 [刪除] 按鈕時,還是需要再次輸入一遍密碼,這麼做主要為了兩點:

  1. 保證操作者是當前賬號本人。
  2. 增加操作步驟,防止誤刪除重要資料。

這就是我們本篇要講的 —— 二級認證,即:在已登入對談的基礎上,進行再次驗證,提高對談的安全性。

Sa-Token 是一個輕量級 java 許可權認證框架,主要解決登入認證、許可權認證、單點登入、OAuth2、微服務閘道器鑑權 等一系列許可權相關問題。
Gitee 開源地址:https://gitee.com/dromara/sa-token

本文將介紹在 SpringBoot 架構下,如何使用 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 即可。

二、具體API

Sa-Token中進行二級認證非常簡單,只需要使用以下API:

// 在當前對談 開啟二級認證,時間為120秒
StpUtil.openSafe(120); 

// 獲取:當前對談是否處於二級認證時間內
StpUtil.isSafe(); 

// 檢查當前對談是否已通過二級認證,如未通過則丟擲異常
StpUtil.checkSafe(); 

// 獲取當前對談的二級認證剩餘有效時間 (單位: 秒, 返回-2代表尚未通過二級認證)
StpUtil.getSafeTime(); 

// 在當前對談 結束二級認證
StpUtil.closeSafe(); 

三、一個小范例

一個完整的二級認證業務流程,應該大致如下:

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-16 
 */
@RestController
@RequestMapping("/safe/")
public class SafeAuthController {
	
	// 刪除倉庫    ---- http://localhost:8081/safe/deleteProject
	@RequestMapping("deleteProject")
	public SaResult deleteProject(String projectId) {
	    // 第1步,先檢查當前對談是否已完成二級認證 
		// 		這個地方既可以通過 StpUtil.isSafe() 手動判斷,
		// 		也可以通過 StpUtil.checkSafe() 或者 @SaCheckSafe 來校驗(校驗不通過時將丟擲 NotSafeException 異常)
	    if(!StpUtil.isSafe()) {
	        return SaResult.error("倉庫刪除失敗,請完成二級認證後再次存取介面");
	    }
	
	    // 第2步,如果已完成二級認證,則開始執行業務邏輯
	    // ... 
	
	    // 第3步,返回結果 
	    return SaResult.ok("倉庫刪除成功"); 
	}
	
	// 提供密碼進行二級認證    ---- http://localhost:8081/safe/openSafe?password=123456
	@RequestMapping("openSafe")
	public SaResult openSafe(String password) {
	    // 比對密碼(此處只是舉例,真實專案時可拿其它引數進行校驗)
	    if("123456".equals(password)) {
	
	        // 比對成功,為當前對談開啟二級認證,有效期為120秒,意為在120秒內再呼叫 deleteProject 介面都無需提供密碼 
	        StpUtil.openSafe(120);
	        return SaResult.ok("二級認證成功");
	    }
	
	    // 如果密碼校驗失敗,則二級認證也會失敗
	    return SaResult.error("二級認證失敗"); 
	}

	// 手動關閉二級認證    ---- http://localhost:8081/safe/closeSafe
	@RequestMapping("closeSafe")
	public SaResult closeSafe() {
		StpUtil.closeSafe();
	    return SaResult.ok();
	}

}

全域性異常攔截器,統一返回給前端的格式,參考:

@RestControllerAdvice
public class GlobalExceptionHandler {
    // 全域性異常攔截 
    @ExceptionHandler
    public SaResult handlerException(Exception e) {
        e.printStackTrace(); 
        return SaResult.error(e.getMessage());
    }
}

前提:二級認證時我們必須處於登入狀態(可參考之前的登入認證章節程式碼),完成登入後,呼叫介面進行二級認證校驗:

  1. 前端呼叫 deleteProject 介面,嘗試刪除倉庫。 ---- http://localhost:8081/safe/deleteProject
  2. 後端校驗對談尚未完成二級認證,返回: 倉庫刪除失敗,請完成二級認證後再次存取介面。
  3. 前端將資訊提示給使用者,使用者輸入密碼,呼叫 openSafe 介面。 ---- http://localhost:8081/safe/openSafe?password=123456
  4. 後端比對使用者輸入的密碼,完成二級認證,有效期為:120秒。
  5. 前端在 120 秒內再次呼叫 deleteProject 介面,嘗試刪除倉庫。 ---- http://localhost:8081/safe/deleteProject
  6. 後端校驗對談已完成二級認證,返回:倉庫刪除成功。

四、指定業務標識進行二級認證

如果專案有多條業務線都需要敏感操作驗證,則 StpUtil.openSafe() 無法提供細粒度的認證操作,
此時我們可以指定一個業務標識來分辨不同的業務線:

// 在當前對談 開啟二級認證,業務標識為client,時間為600秒
StpUtil.openSafe("client", 600); 

// 獲取:當前對談是否已完成指定業務的二級認證 
StpUtil.isSafe("client"); 

// 校驗:當前對談是否已完成指定業務的二級認證 ,如未認證則丟擲異常
StpUtil.checkSafe("client"); 

// 獲取當前對談指定業務二級認證剩餘有效時間 (單位: 秒, 返回-2代表尚未通過二級認證)
StpUtil.getSafeTime("client"); 

// 在當前對談 結束指定業務標識的二級認證
StpUtil.closeSafe("client"); 

業務標識可以填寫任意字串,不同業務標識之間的認證互不影響,比如:

// 開啟了業務標識為 client 的二級認證 
StpUtil.openSafe("client"); 

// 判斷是否處於 shop 的二級認證,會返回 false 
StpUtil.isSafe("shop");  // 返回 false 

// 也不會通過校驗,會丟擲異常 
StpUtil.checkSafe("shop"); 

程式碼範例:

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-16 
 */
@RestController
@RequestMapping("/safe/")
public class SafeAuthController {

	// ------------------ 指定業務型別進行二級認證 

	// 獲取應用祕鑰    ---- http://localhost:8081/safe/getClientSecret
	@RequestMapping("getClientSecret")
	public SaResult getClientSecret() {
	    // 第1步,先檢查當前對談是否已完成 client業務 的二級認證 
		StpUtil.checkSafe("client");
	
	    // 第2步,如果已完成二級認證,則返回資料 
	    return SaResult.data("aaaa-bbbb-cccc-dddd-eeee");
	}
	
	// 提供手勢密碼進行二級認證    ---- http://localhost:8081/safe/openClientSafe?gesture=35789
	@RequestMapping("openClientSafe")
	public SaResult openClientSafe(String gesture) {
	    // 比對手勢密碼(此處只是舉例,真實專案時可拿其它引數進行校驗)
	    if("35789".equals(gesture)) {
	
	        // 比對成功,為當前對談開啟二級認證:
	    	// 業務型別為:client 
	    	// 有效期為600秒==10分鐘,意為在10分鐘內,呼叫 getClientSecret 時都無需再提供手勢密碼 
	        StpUtil.openSafe("client", 600);
	        return SaResult.ok("二級認證成功");
	    }
	
	    // 如果密碼校驗失敗,則二級認證也會失敗
	    return SaResult.error("二級認證失敗"); 
	}

	// 查詢當前對談是否已完成指定的二級認證    ---- http://localhost:8081/safe/isClientSafe
	@RequestMapping("isClientSafe")
	public SaResult isClientSafe() {
	    return SaResult.ok("當前是否已完成 client 二級認證:" + StpUtil.isSafe("client")); 
	}

}

五、使用註解進行二級認證

在一個方法上使用 @SaCheckSafe 註解,可以在程式碼進入此方法之前進行一次二級認證校驗

// 二級認證:必須二級認證之後才能進入該方法 
@SaCheckSafe      
@RequestMapping("add")
public String add() {
    return "使用者增加";
}

// 指定業務型別,進行二級認證校驗
@SaCheckSafe("art")
@RequestMapping("add2")
public String add2() {
    return "文章增加";
}

標註 @SaCheckSafe 的方法必須在二級認證後才能存取成功,其效果與程式碼 StpUtil.checkSafe() 等同。


參考資料