JWT(JSON Web Token)通常是在用戶登錄后簽發(fā)的,用于驗證用戶身份和授權(quán)。JWT 的有效期限(或稱“過期時間”)通常是一段時間(例如1小時),過期后用戶需要重新登錄以獲取新的JWT。然而,在某些情況下,用戶可能會在JWT到期之前使用應(yīng)用程序,這可能會導(dǎo)致應(yīng)用程序不可用或需要用戶重新登錄。為了避免這種情況,通常有兩種解決方案來處理JWT續(xù)期問題:
①、刷新令牌(Refresh Token)
刷新令牌是一種機(jī)制,它允許應(yīng)用程序獲取一個新的JWT,而無需用戶進(jìn)行身份驗證。當(dāng)JWT過期時,應(yīng)用程序使用刷新令牌向身份驗證服務(wù)器請求一個新的JWT,而無需提示用戶輸入其憑據(jù)。這樣,用戶可以繼續(xù)使用應(yīng)用程序,而不必重新登錄。
②、自動延長JWT有效期
在某些情況下,JWT可以自動延長其有效期。例如,當(dāng)用戶在JWT過期前繼續(xù)使用應(yīng)用程序時,應(yīng)用
代碼演示如何使用Refresh Token來更新JWT
public String refreshAccessToken(String refreshToken) {
// validate the refresh token (check expiration, signature, etc.)
boolean isValid = validateRefreshToken(refreshToken);
if (isValid) {
// retrieve the user information associated with the refresh token (e.g. user ID)
String userId = getUserIdFromRefreshToken(refreshToken);
// generate a new JWT access token
String newAccessToken = generateAccessToken(userId);
return newAccessToken;
} else {
throw new RuntimeException("Invalid refresh token.");
}
}
在這個示例中,refreshAccessToken方法接收一個刷新令牌作為參數(shù),并使用validateRefreshToken方法驗證該令牌是否有效。如果令牌有效,方法將使用getUserIdFromRefreshToken方法獲取與令牌關(guān)聯(lián)的用戶信息,然后使用generateAccessToken方法生成一個新的JWT訪問令牌,并將其返回。如果令牌無效,則拋出異常。
那怎么自動延長JWT有效期呢?
要自動延長JWT有效期,您可以在每次請求時檢查JWT的過期時間,并在必要時更新JWT的過期時間。以下是一個示例Java代碼,演示如何自動延長JWT有效期:
public String getAccessToken(HttpServletRequest request) {
String accessToken = extractAccessTokenFromRequest(request);
if (isAccessTokenExpired(accessToken)) {
String userId = extractUserIdFromAccessToken(accessToken);
accessToken = generateNewAccessToken(userId);
} else if (shouldRefreshAccessToken(accessToken)) {
String userId = extractUserIdFromAccessToken(accessToken);
accessToken = generateNewAccessToken(userId);
}
return accessToken;
}
private boolean isAccessTokenExpired(String accessToken) {
// extract expiration time from the access token
Date expirationTime = extractExpirationTimeFromAccessToken(accessToken);
// check if the expiration time is in the past
return expirationTime.before(new Date());
}
private boolean shouldRefreshAccessToken(String accessToken) {
// extract expiration time and current time
Date expirationTime = extractExpirationTimeFromAccessToken(accessToken);
Date currentTime = new Date();
// calculate the remaining time until expiration
long remainingTime = expirationTime.getTime() - currentTime.getTime();
// refresh the token if it expires within the next 5 minutes
return remainingTime < 5 * 60 * 1000;
}
private String generateNewAccessToken(String userId) {
// generate a new access token with a new expiration time
Date expirationTime = new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TIME);
String accessToken = generateAccessToken(userId, expirationTime);
return accessToken;
}
在這個示例中,getAccessToken方法接收HttpServletRequest對象作為參數(shù),并使用extractAccessTokenFromRequest方法從請求中提取JWT訪問令牌。然后,它使用isAccessTokenExpired方法檢查JWT的過期時間是否已過期。如果過期,它使用extractUserIdFromAccessToken方法從JWT中提取用戶ID,并使用generateNewAccessToken方法生成一個新的JWT訪問令牌。如果JWT尚未過期,但即將到期,則使用shouldRefreshAccessToken方法檢查JWT是否需要更新。如果是這樣,它使用相同的流程生成一個新的JWT訪問令牌。
那新生成的token怎么返回給前端?
在生成新的JWT訪問令牌之后,您需要將其返回給前端,以便前端可以在下一次請求中使用它。以下是一個示例Java代碼,演示如何將新的JWT訪問令牌返回給前端:
public ResponseEntity<?> authenticateUser(LoginRequest loginRequest) {
// authenticate the user and generate a new JWT access token
String userId = authenticate(loginRequest.getUsername(), loginRequest.getPassword());
String accessToken = generateAccessToken(userId);
// generate a new refresh token and save it to the database
String refreshToken = generateRefreshToken(userId);
saveRefreshToken(refreshToken);
// create a response object containing the access token and refresh token
AuthResponse authResponse = new AuthResponse(accessToken, refreshToken);
// return the response object in a HTTP response
return ResponseEntity.ok(authResponse);
}
public ResponseEntity<?> refreshAccessToken(HttpServletRequest request) {
// extract the refresh token from the request
String refreshToken = extractRefreshTokenFromRequest(request);
// validate the refresh token and retrieve the associated user ID
String userId = validateAndRetrieveUserId(refreshToken);
// generate a new access token with a new expiration time
Date expirationTime = new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TIME);
String accessToken = generateAccessToken(userId, expirationTime);
// create a response object containing the new access token
AccessTokenResponse accessTokenResponse = new AccessTokenResponse(accessToken);
// return the response object in a HTTP response
return ResponseEntity.ok(accessTokenResponse);
}
在這個示例中,authenticateUser方法返回一個HTTP響應(yīng),其中包含新的JWT訪問令牌和Refresh Token。這個響應(yīng)可以使用Spring的ResponseEntity類來構(gòu)造。在refreshAccessToken方法中,新的JWT訪問令牌也被包裝在一個響應(yīng)對象中,并返回到前端。
一個界面多個請求并發(fā)執(zhí)行,第一個請求更新了token,后續(xù)請求還是攜帶舊token,怎么保證后續(xù)請求正常執(zhí)行?
當(dāng)多個請求并發(fā)執(zhí)行時,可能會出現(xiàn)一個請求更新了JWT訪問令牌,但是后續(xù)請求仍然攜帶舊的JWT訪問令牌的情況。為了保證后續(xù)請求正常執(zhí)行,您可以采取以下幾種方法:
使用同步機(jī)制
在每個請求處理程序中使用同步機(jī)制(例如,Java的synchronized關(guān)鍵字),以確保在更新JWT訪問令牌期間沒有其他請求正在使用舊的JWT訪問令牌。
使用無狀態(tài)JWT
無狀態(tài)JWT不需要在服務(wù)端存儲任何狀態(tài)信息,因此可以避免因為更新JWT訪問令牌而導(dǎo)致后續(xù)請求使用舊的JWT訪問令牌的問題。但是,無狀態(tài)JWT可能會在JWT令牌過期或JWT令牌被篡改的情況下失效。
使用JWT黑名單
在服務(wù)端維護(hù)一個JWT黑名單,記錄已經(jīng)失效的JWT訪問令牌。當(dāng)一個請求到達(dá)時,首先檢查JWT黑名單,如果發(fā)現(xiàn)JWT訪問令牌已經(jīng)失效,則拒絕該請求。這種方法可以在一定程度上防止后續(xù)請求使用舊的JWT訪問令牌,但是需要在服務(wù)端維護(hù)JWT黑名單,增加了一些開銷和復(fù)雜性。