日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

为什么 退出登录 或 修改密码 无法使 token 失效

發布時間:2025/3/8 编程问答 20 如意码农
生活随笔 收集整理的這篇文章主要介紹了 为什么 退出登录 或 修改密码 无法使 token 失效 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
前文說過 token 由 3 個部分組成:分別是 token metadata,payload,signature,
其中 signature 部分是對 payload 的加密,而 payload 當中會包含 token 的過期時間。
 
這樣做有個好處:服務端不需要另外去存儲這個 session 的狀態,只要校驗過 signature,就可以知道過期時間,簡化后端邏輯,做到 “無狀態化”。
但這樣做也會有一個缺點,只要 token 簽發了,就無法更改這個 token 的內容。
 

問題場景

一個用戶登錄,獲得一個有效的 token 之后,他點擊退出登錄。
此時理想狀態下,我們希望這個 token 不再生效,但實際是,只要拿著這個 token 去訪問服務端 Authenticated 的資源,token 仍然會校驗通過。
因為 [退出登錄] 的操作本身不能改變 token。
 
 

解決方案

一、把 token 的有效期縮短,例如半小時或者五分鐘,但有個明顯的缺陷:用戶需要頻繁重新登錄。

 

二、把 退出登錄 的用戶添加到 token black list 當中。

簡單地,在調 sign out (退出登錄) 的 api 時,把用戶的 access token 添加到 token black list 當中;后端校驗 jwt 時,添加校驗 token 是否存在于 token black list 當中。
下面展開設計過程:
 

1. token black list 的設計

是否持久化?

第一個問題是:這種 sign out 的 token 要不要持久化?
首先,token 本身就是會過期的;
其次,這個新的校驗方法會作用到每一個通過了 token 有效驗證的請求,這個方法一定是高頻訪問的;
所以,此處選擇通過 redis 緩存 tokenBlackList 。
(Redis 是內存數據庫,支持高并發讀寫和自動過期(TTL),適合存儲臨時性黑名單數據。即使服務重啟,黑名單數據可能丟失,但 Token 本身有過期時間,因此不影響最終一致性。)
每次 sign out,都將 set 到 redis 中;每次校驗 token,都查詢這個 redis value。
 

數據結構設計

REDIS 是 key,value 的鍵值對方式,value 可能是 string,list,hash..
對于 value,可以直接粗暴的存儲整個 token json;
那么 key 應該如何設計?使用 userId,那大概率會和其他業務的 redis key 重疊,在這里最好加上業務場景,形如,“TOKEN_BLACK_LIST_userId”。
 
a. 多設備登錄場景
假設:用戶 A 在設備 D1 上登錄后,在設備 D2 上同時登陸(這種場景當前是允許的);
此時用戶 A 在設備 D1 上點擊 退出登錄,服務端會把 TOKEN_BLACK_LIST_AId : tokenJson 寫進了redis。
此時用戶 A 再于設備 D2 上操作,校驗 token 時會去 redis 撈取數據,找到了 TOKEN_BLACK_LIST_AId,此時認為用戶 A 的 token 無效。
 
如果這不是我們期望的場景,那應該如何讓同個賬號的多個 token 互不影響呢?此處 userId 就不適合作為 redis key。
是否每個 token 有自己特有的唯一的 id 呢?這又到 token payload 的組成,它確實存在唯一標識的 id,jti
{
"jti": "a1b2c3d4-5678-90ef-ghij-klmnopqrstuv",
"iss": "the issuer",
"aud": "the audience",
"exp": 1630003600,
"iat": 1630000000,
..
}
此故,這里把 key 設計成 [TOKEN_BLACK_LIST_tokenId],形如 "TOKEN_BLACK_LIST_a1b2c3d4-5678-90ef-ghij-klmnopqrstuv"。
 
 
b. 修改密碼場景
假設:用戶 A 在設備 D1、D2、D3 .. 多設備上均操作登錄,此時每個設備都持有一個獨立的 token;
如果此時用戶在設備 D1 上 “修改密碼”,如何讓 D2、D3 等所有設備的登錄失效?
 
后端可以把提出 “修改密碼” 的設備 D1 的 token 加入到 token_black_list 當中,但是如何知道這個用戶當前持有多少 token 呢?
是否需要每次登錄都把 token 存儲起來?但這樣顯然會增加復雜度。
 
我們可以重新審視一下 token 的結構,是否能找到一些屬性來使用?
{
"jti": "a1b2c3d4-5678-90ef-ghij-klmnopqrstuv",
"iss": "the issuer",
"aud": "the audience",
"exp": 1630003600,
"iat": 1630000000,
..
}
這里有一個非常巧妙而簡單的方式:
每一個 token 上都會持有 iat 簽發時間,假設 用戶 A 在設備 D1 上確定 “修改密碼” 的時間是 changedPasswordDate
服務端處理完 “修改密碼” 之后,可以把 changedPasswordDate 這個時間存儲到 redis 上,設其 key 為 TOKEN_INVALIDATION_userId,value 為 changedPasswordDate
 
那么在服務端校驗 token 需要多添加這兩項校驗:
    查詢當前 token 是否存在于 TOKEN_BLACK_LIST 中
    查詢是否存在 TOKEN_INVALIDATION_userId,如果存在,
        比較當前 token 的 iat 時間是否早于 changedPasswordDate,如果是,該 token 無效
 
 

2. code implement

sign-out / change-password

redis key-value 的過期時間取 token 的有效周期。本文設定 token 有效期為24小時,也即 1440 分鐘。
    public async Task<GlobalSignOutResponse> SignOutAsync(string accessToken)
{
var response = await _authService.SignOutAsync(accessToken);
await _redisCacheService.SetCache(TokenHelper.GetRedisKeyForBlackAccessToken(accessToken), accessToken, 1440); // 分鐘單位
return response;
}
"修改密碼" 的處理同理。
 

jwt authentication

startUp.cs

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
..
options.Events = new JwtBearerEvents
{
..
OnTokenValidated = async context =>
{
if (await IsAccessTokenExpired(context, services))
{
Log.Information($"The access token is expired as user already signed out or changed password.");
context.Fail(GetTokenExpiredResponse(context.Response));
}
await Task.CompletedTask;
}
};
});
    private string GetTokenExpiredResponse(HttpResponse response)
{
if (ApiResponseCodes.AccessTokenExpired.BuildHttpResponse() is ObjectResult result)
{
var payload = JObject.FromObject(result.Value);
response.ContentType = "application/json";
response.StatusCode = 401; return payload.ToString();
}
return string.Empty;
} private async Task<bool> IsAccessTokenExpired(TokenValidatedContext context, IServiceCollection services)
{
try
{
var requestHeader = context.Request.Headers["Authorization"];
var accessToken = requestHeader.Count > 0 ? requestHeader[0].Split(" ")[1] : String.Empty;
var redisService = context.HttpContext.RequestServices.GetRequiredService<IRedisCacheService>()
var blackToken = await redisService.GetCache(TokenHelper.GetRedisKeyForBlackAccessToken(accessToken));
return blackToken == accessToken;
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to validate access token: {ex.Message}");
return true;
}
}
 
 
* 由 [退出登錄] 無效化 token,還可以衍生出非常多的問題,此處暫且不表。
 
 
..
 

總結

以上是生活随笔為你收集整理的为什么 退出登录 或 修改密码 无法使 token 失效的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

歡迎分享!

轉載請說明來源于"生活随笔",并保留原作者的名字。

本文地址:为什么 退出登录 或 修改密码 无法使 token 失效