大家好,我是一航!
不出意外的話,我猜在座的各位同學,剛剛學編程的時候,80%以上的人寫的第一個計畫不是學生管理系統,就是使用者的登入、註冊;需求都是源於生活而高於生活,因為學生對這些場景最為熟悉,功能也相對簡單,拿來練手最適合不過了;但是,一個看似小小的登入、註冊功能,要想把他真正的做好,並不是一件容易的事情;本文透過圖示及程式碼的方式介紹使用者登入流程及技術實作,內容包括使用者登入,使用者驗證,如何獲取操作使用者的資訊以及一些黑名單及匿名介面如何免驗證相關的實作。
業務圖解
對於使用者登入來說、涉及到了使用者註冊、登入驗證幾個方面,透過流程圖演示如何處理(新使用者/老使用者)登入。
流程解讀
客戶端-登入界面(通常手機驗證碼登入)
填寫手機號
發送驗證碼
填寫驗證碼
勾選新使用者自動註冊
伺服端-使用者驗證
驗證帳號驗證碼是否正確
驗證使用者是否存在(不存在出初始化使用者資訊)
完成驗證生成 token
將 token 返回給客戶端
使用者資訊設計:
驗證流程圖解
登入驗證流程涉及到了兩個介面,兩個緩存:
獲取驗證碼介面,給手機號發送驗證碼並設定驗證碼緩存,設定過期時間。
登入介面,送出手機號及驗證碼,讀取緩存進行匹配驗證,成功則生成 token 返回給客戶端,客戶端登入成功,登入後請求頭攜帶 token 進行業務請求即可。
關於 token 過期時間
通常我們 token 的過期時間是根據客戶端的型別來定義的,app 的過期時間會更長一些(通常一個星期)。
web 端過期時間以小時為單位,如果控制過期時間可以將 web 登入和 app 登入拆分為兩個介面(能夠分流,介面壓力更小),或者是根據請求頭資訊進行判斷即可,是移動端就設定 7 天,是 web 端就設定兩小時。
關於業務請求 token 驗證
登入成功後,客戶端每次請求都會攜帶 token,通常我們會有一個閘道器來進行 token 驗證,閘道器用於登入驗證的核心就是登入成功後寫入的 token 作為 key,值為使用者基礎資訊的緩存。
圖解如下:
驗證成功後,重寫內部請求頭,將使用者的的 id,帳號,昵稱資訊放入請求頭中,這樣可以方便業務系統獲取當前操作使用者資訊以及許可權控制等等。
關於登出操作
使用者攜帶 token 請求登出介面,登出介面對 token 對應的緩存進行刪除操作,返回 401 即可,客戶端獲取到 401 就會跳轉到登入頁面。
關於匿名請求(免登入)
通常匿名請求放行有兩種方案:
授權 token,為 token 設定單位時間內請求次數
配置路徑放行規則,對請求介面路徑進行正則匹配,符合正則規則的進行放行
方案 1:授權 token,限制單位時間請求次數
優點就是雖然是免登入介面,但是介面的操作物件可以追溯,請求次數可控,避免被非法利用;缺點就是需要更多的編碼及配置工作。
技術實作:
提供一個授權 token 管理頁面,主要管理 token 使用者,token 的值,單位時間存取次數(如每分鐘 60 次)
增刪改查,將授權 token 存放到緩存中,使用 map 進行儲存,key 為 token,值為每分鐘存取次數
單位時間計數緩存,過期時間為 1 分鐘
這時候我們需要在上面的驗證流程圖基礎上進行升級:
請求次數檢查程式碼實作:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 授權token請求限制緩存
*/
@Component
public classAuthTokenRequestLimitCache{
@Autowired
private RedisTemplate<String, Integer> redisTemplate;
privatestaticfinal String AUTH_TOKEN_LIMIT_KEY_PREFIX = "auth_token_limit";
/**
* 請求次數+1並檢查是否超限
*
* @param token
* @return 是否放行
*/
publicbooleanincrementWithCheck(String token){
// 1.獲取token請求次數限制,獲取為null代表授權配置已被修改,此token已經不具備許可權
Integer limit = getLimit(token);
if (limit == null) {
returnfalse;
}
// 2.組裝緩存key,讀取緩存
String key = String.join(":", AUTH_TOKEN_LIMIT_KEY_PREFIX, token);
Integer count = redisTemplate.opsForValue().get(key);
// 3.沒有值代表一分鐘內沒有請求產生了
if (count == null) {
// 初始化值
redisTemplate.opsForValue().increment(key);
// 設定過期時間
redisTemplate.expire(key, 1L, TimeUnit.MINUTES);
returntrue;
}
// 自增並獲取當前值 大於限制的話 返回false 閘道器過濾器返回提示資訊(如請求過於頻繁)
Long inc = redisTemplate.opsForValue().increment(key);
return inc <= limit;
}
/**
* 獲取限值
*
* @param token
* @return
*/
public Integer getLimit(String token){
Object limit = redisTemplate.opsForHash().get("auth_token_limit", token);
return limit == null ? null : (Integer) limit;
}
}
對於授權介面,通常是只允許 get 操作,對數據進行送出或者更新是不被允許的,當然這個是業務層面的,最終取決於系統設計。
方案 2:請求路徑正則校驗
我們在閘道器的配置檔中增加匿名介面規則,請求到閘道器時,檢查請求的路徑是否符合匿名介面規則,是則放行,不是則進行 token 校驗,方案比較簡單,只需要對閘道器進行處理即可。
關於黑名單
對於一個系統來說,黑名單是最後一道關卡,所以為了安全我們需要對問題使用者進行黑名單操作。
具體實作也比較簡單:
使用者管理頁面提供一個拉黑的按鈕,拉黑後,這些使用者的 id 會儲存到一個 set 集合中去
登入時候檢查使用者是否在黑名單中,是則拒絕登入並提示
如果使用者已經登入後進行拉黑操作,閘道器會在鑒權透過後檢查使用者是否在黑名單中,是則刪除 token 對應緩存,返回 401,401 就會跳到登入頁,步驟 2 就會進行攔截
總結
使用者系統是非常基礎的系統,但是很多程式設計師工作中可能並沒有真正的參與到使用者系統的開發,透過此文可以對使用者登入流程及配套功能有一個全面的了解。
來源:https://juejin.cn/post/7025768845075808286
>>
END
精品資料,超贊福利,免費領
微信掃碼/長按辨識 添加【技術交流群】
群內每天分享精品學習資料
最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎、並行、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、訊息佇列等多個型別),歡迎您的使用。
👇👇
👇點選"閱讀原文",獲取更多資料(持續更新中)