三年探索,登錄注冊之路仍漫漫
不出意外的話,我猜在座的各位同學,剛剛學編程的時候,80%以上的人寫的第一個項目不是學生管理系統(tǒng),就是用戶的登錄、注冊;需求都是源于生活而高于生活,因為學生對這些場景最為熟悉,功能也相對簡單,拿來練手最適合不過了;但是,一個看似小小的登錄、注冊功能,要想把他真正的做好,并不是一件容易的事情;本文通過圖示及代碼的方式介紹用戶登錄流程及技術實現(xiàn),內(nèi)容包括用戶登錄,用戶驗證,如何獲取操作用戶的信息以及一些黑名單及匿名接口如何免驗證相關的實現(xiàn)。
業(yè)務圖解
對于用戶登錄來說、涉及到了用戶注冊、登錄驗證幾個方面,通過流程圖演示如何處理(新用戶/老用戶)登錄。
圖片
流程解讀
客戶端-登錄界面(通常手機驗證碼登錄)
- 填寫手機號
- 發(fā)送驗證碼
- 填寫驗證碼
- 勾選新用戶自動注冊
服務端-用戶驗證
- 驗證賬號驗證碼是否正確
- 驗證用戶是否存在(不存在出初始化用戶信息)
- 完成驗證生成 token
- 將 token 返回給客戶端
用戶信息設計:
圖片
驗證流程圖解
圖片
登錄驗證流程涉及到了兩個接口,兩個緩存:
- 獲取驗證碼接口,給手機號發(fā)送驗證碼并設置驗證碼緩存,設置過期時間。
- 登錄接口,提交手機號及驗證碼,讀取緩存進行匹配驗證,成功則生成 token 返回給客戶端,客戶端登錄成功,登錄后請求頭攜帶 token 進行業(yè)務請求即可。
關于 token 過期時間
通常我們 token 的過期時間是根據(jù)客戶端的類型來定義的,app 的過期時間會更長一些(通常一個星期)。
web 端過期時間以小時為單位,如果控制過期時間可以將 web 登錄和 app 登錄拆分為兩個接口(能夠分流,接口壓力更?。?,或者是根據(jù)請求頭信息進行判斷即可,是移動端就設置 7 天,是 web 端就設置兩小時。
關于業(yè)務請求 token 驗證
登錄成功后,客戶端每次請求都會攜帶 token,通常我們會有一個網(wǎng)關來進行 token 驗證,網(wǎng)關用于登錄驗證的核心就是登錄成功后寫入的 token 作為 key,值為用戶基礎信息的緩存。
圖解如下:
圖片
驗證成功后,重寫內(nèi)部請求頭,將用戶的的 id,賬號,昵稱信息放入請求頭中,這樣可以方便業(yè)務系統(tǒng)獲取當前操作用戶信息以及權限控制等等。
關于登出操作
用戶攜帶 token 請求登出接口,登出接口對 token 對應的緩存進行刪除操作,返回 401 即可,客戶端獲取到 401 就會跳轉(zhuǎn)到登錄頁面。
關于匿名請求(免登錄)
通常匿名請求放行有兩種方案:
- 授權 token,為 token 設置單位時間內(nèi)請求次數(shù)
- 配置路徑放行規(guī)則,對請求接口路徑進行正則匹配,符合正則規(guī)則的進行放行
方案 1:授權 token,限制單位時間請求次數(shù)
優(yōu)點就是雖然是免登錄接口,但是接口的操作對象可以追溯,請求次數(shù)可控,避免被非法利用;缺點就是需要更多的編碼及配置工作。
技術實現(xiàn):
- 提供一個授權 token 管理頁面,主要管理 token 使用者,token 的值,單位時間訪問次數(shù)(如每分鐘 60 次)
- 增刪改查,將授權 token 存放到緩存中,使用 map 進行存儲,key 為 token,值為每分鐘訪問次數(shù)
- 單位時間計數(shù)緩存,過期時間為 1 分鐘
這時候我們需要在上面的驗證流程圖基礎上進行升級:
圖片
請求次數(shù)檢查代碼實現(xiàn):
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 class AuthTokenRequestLimitCache {
@Autowired
private RedisTemplate<String, Integer> redisTemplate;
private static final String AUTH_TOKEN_LIMIT_KEY_PREFIX = "auth_token_limit";
/**
* 請求次數(shù)+1并檢查是否超限
*
* @param token
* @return 是否放行
*/
public boolean incrementWithCheck(String token) {
// 1.獲取token請求次數(shù)限制,獲取為null代表授權配置已被修改,此token已經(jīng)不具備權限
Integer limit = getLimit(token);
if (limit == null) {
return false;
}
// 2.組裝緩存key,讀取緩存
String key = String.join(":", AUTH_TOKEN_LIMIT_KEY_PREFIX, token);
Integer count = redisTemplate.opsForValue().get(key);
// 3.沒有值代表一分鐘內(nèi)沒有請求產(chǎn)生了
if (count == null) {
// 初始化值
redisTemplate.opsForValue().increment(key);
// 設置過期時間
redisTemplate.expire(key, 1L, TimeUnit.MINUTES);
return true;
}
// 自增并獲取當前值 大于限制的話 返回false 網(wǎng)關過濾器返回提示信息(如請求過于頻繁)
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 操作,對數(shù)據(jù)進行提交或者更新是不被允許的,當然這個是業(yè)務層面的,最終取決于系統(tǒng)設計。
方案 2:請求路徑正則校驗
我們在網(wǎng)關的配置文件中增加匿名接口規(guī)則,請求到網(wǎng)關時,檢查請求的路徑是否符合匿名接口規(guī)則,是則放行,不是則進行 token 校驗,方案比較簡單,只需要對網(wǎng)關進行處理即可。
關于黑名單
對于一個系統(tǒng)來說,黑名單是最后一道關卡,所以為了安全我們需要對問題用戶進行黑名單操作。
具體實現(xiàn)也比較簡單:
- 用戶管理頁面提供一個拉黑的按鈕,拉黑后,這些用戶的 id 會存儲到一個 set 集合中去
- 登錄時候檢查用戶是否在黑名單中,是則拒絕登錄并提示
- 如果用戶已經(jīng)登錄后進行拉黑操作,網(wǎng)關會在鑒權通過后檢查用戶是否在黑名單中,是則刪除 token 對應緩存,返回 401,401 就會跳到登錄頁,步驟 2 就會進行攔截
總結(jié)
用戶系統(tǒng)是非常基礎的系統(tǒng),但是很多程序員工作中可能并沒有真正的參與到用戶系統(tǒng)的開發(fā),通過此文可以對用戶登錄流程及配套功能有一個全面的了解。