在開發(fā)與Microsoft Graph或其他Azure AD保護(hù)資源交互的Web應(yīng)用程序時(shí),用戶通常會(huì)通過OAuth 2.0流程進(jìn)行認(rèn)證,獲取到訪問令牌(Access Token)和刷新令牌(Refresh Token)。訪問令牌用于授權(quán)對(duì)受保護(hù)資源的訪問,但它們具有有限的生命周期(通常為1小時(shí))。當(dāng)訪問令牌過期后,應(yīng)用程序需要一種機(jī)制來獲取新的訪問令牌,以維持用戶會(huì)話并繼續(xù)訪問資源,而無需用戶重新登錄。
例如,在使用Azure SDK for Java或Microsoft Graph SDK時(shí),我們可能會(huì)通過TokenCredential來提供訪問令牌:
final TokenCredential tokenCredential = request -> { // account.getTokenExpiry() 和 account.getAccessToken() 應(yīng)該動(dòng)態(tài)更新 final OffsetDateTime offset = OffsetDateTime.ofInstant(account.getTokenExpiry().toInstant(), ZoneId.systemDefault()); final AccessToken token = new AccessToken(account.getAccessToken(), offset); return Mono.create(sink -> sink.success(token)); }; final TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(tokenCredential); this.graphServiceClient = GraphServiceClient .builder() .authenticationProvider(tokenCredentialAuthProvider) .buildClient();
上述代碼片段展示了如何使用一個(gè)TokenCredential來為GraphServiceClient提供訪問令牌。然而,當(dāng)account.getAccessToken()中的令牌過期時(shí),TokenCredentialAuthProvider本身并不會(huì)自動(dòng)觸發(fā)令牌刷新。此時(shí),直接使用已過期的訪問令牌進(jìn)行API調(diào)用將導(dǎo)致認(rèn)證失敗。因此,我們需要主動(dòng)地使用刷新令牌來獲取新的訪問令牌。
OAuth 2.0協(xié)議提供了“刷新令牌(refresh_token)”授權(quán)類型,允許客戶端在訪問令牌過期后,使用刷新令牌向授權(quán)服務(wù)器請(qǐng)求新的訪問令牌。這個(gè)過程通常在后臺(tái)進(jìn)行,對(duì)用戶透明。
為了獲取新的訪問令牌,我們需要向Azure AD的OAuth 2.0令牌端點(diǎn)發(fā)起一個(gè)POST請(qǐng)求。這個(gè)端點(diǎn)是:https://login.microsoftonline.com/common/oauth2/v2.0/token。
請(qǐng)求需要包含以下參數(shù):
以下是一個(gè)使用Spring RestTemplate在Java中執(zhí)行此操作的示例:
import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.time.OffsetDateTime; import java.time.ZoneId; public class AzureAdTokenRefresher { private final RestTemplate restTemplate; private final ObjectMapper objectMapper; public AzureAdTokenRefresher() { this.restTemplate = new RestTemplate(); this.objectMapper = new ObjectMapper(); } /** * 使用刷新令牌獲取新的訪問令牌。 * @param refreshToken 用戶的刷新令牌 * @return 包含新訪問令牌、刷新令牌(如果返回)和過期時(shí)間等信息的JSON字符串 * @throws IOException 如果JSON解析失敗 */ public TokenResponse refreshAccessToken(String refreshToken) throws IOException { String url = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); // TODO: 這些值應(yīng)從配置文件或環(huán)境變量中安全讀取 MultiValueMap<String, String> map= new LinkedMultiValueMap<>(); map.add("client_id", "your_client_id"); // 替換為您的應(yīng)用客戶端ID map.add("grant_type", "refresh_token"); map.add("redirect_uri", "http://localhost:5000/login/oauth2/code/microsoft"); // 替換為您的重定向URI map.add("scope", "openid profile offline_access User.Read Mail.Read"); // 替換為您的權(quán)限范圍 map.add("refresh_token", refreshToken); map.add("client_secret", "your_client_secret"); // 替換為您的應(yīng)用客戶端密鑰 HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers); ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class); if (response.getStatusCode().is2xxSuccessful()) { String responseBody = response.getBody(); JsonNode rootNode = objectMapper.readTree(responseBody); String newAccessToken = rootNode.get("access_token").asText(); String newRefreshToken = rootNode.has("refresh_token") ? rootNode.get("refresh_token").asText() : refreshToken; long expiresInSeconds = rootNode.get("expires_in").asLong(); // 計(jì)算新的過期時(shí)間 OffsetDateTime expiryTime = OffsetDateTime.now(ZoneId.systemDefault()).plusSeconds(expiresInSeconds); return new TokenResponse(newAccessToken, newRefreshToken, expiryTime); } else { // 處理錯(cuò)誤響應(yīng) throw new RuntimeException("Failed to refresh token: " + response.getStatusCode() + " - " + response.getBody()); } } // 輔助類用于封裝令牌響應(yīng) public static class TokenResponse { private final String accessToken; private final String refreshToken; private final OffsetDateTime expiryTime; public TokenResponse(String accessToken, String refreshToken, OffsetDateTime expiryTime) { this.accessToken = accessToken; this.refreshToken = refreshToken; this.expiryTime = expiryTime; } public String getAccessToken() { return accessToken; } public String getRefreshToken() { return refreshToken; } public OffsetDateTime getExpiryTime() { return expiryTime; } } }
此方法將返回一個(gè)JSON字符串,其中包含新的access_token、expires_in(訪問令牌的有效期,以秒為單位),以及可能更新的refresh_token(某些授權(quán)服務(wù)器會(huì)在每次刷新時(shí)頒發(fā)新的刷新令牌,舊的刷新令牌會(huì)失效)。
獲取到新的訪問令牌后,需要更新應(yīng)用程序中存儲(chǔ)的令牌信息,并確保GraphServiceClient使用這個(gè)新令牌。由于原始的TokenCredential是通過Lambda表達(dá)式動(dòng)態(tài)獲取account對(duì)象的令牌,我們只需要更新account對(duì)象中存儲(chǔ)的訪問令牌和其過期時(shí)間即可。
假設(shè)您的account對(duì)象是一個(gè)自定義的數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)用戶的認(rèn)證信息:
// 假設(shè)您的 Account 類有以下方法 public class UserAccount { private String accessToken; private String refreshToken; private OffsetDateTime tokenExpiry; // ... 構(gòu)造函數(shù),getter和setter ... public void updateTokens(String newAccessToken, String newRefreshToken, OffsetDateTime newExpiry) { this.accessToken = newAccessToken; this.refreshToken = newRefreshToken; this.tokenExpiry = newExpiry; } }
當(dāng)您需要刷新令牌時(shí):
// 假設(shè) currentAccount 是當(dāng)前用戶的 UserAccount 實(shí)例 UserAccount currentAccount = // ... 從存儲(chǔ)中加載 ... // 檢查令牌是否即將過期或已過期 if (currentAccount.getTokenExpiry().isBefore(OffsetDateTime.now(ZoneId.systemDefault()).plusMinutes(5))) { // 提前5分鐘刷新 try { AzureAdTokenRefresher refresher = new AzureAdTokenRefresher(); AzureAdTokenRefresher.TokenResponse tokenResponse = refresher.refreshAccessToken(currentAccount.getRefreshToken()); // 更新 account 對(duì)象中的令牌信息 currentAccount.updateTokens( tokenResponse.getAccessToken(), tokenResponse.getRefreshToken(), // 使用新的刷新令牌,如果返回了的話 tokenResponse.getExpiryTime() ); // ... 將更新后的 currentAccount 保存回存儲(chǔ) ... // GraphServiceClient 的 TokenCredential 會(huì)在下次請(qǐng)求時(shí)自動(dòng)獲取更新后的令牌 // 因?yàn)樗膶?shí)現(xiàn)是每次請(qǐng)求時(shí)從 account 對(duì)象中獲取最新令牌。 // 如果 GraphServiceClient 需要重新構(gòu)建,則在此處重新構(gòu)建。 // 對(duì)于上述 lambda 表達(dá)式實(shí)現(xiàn)的 TokenCredential,通常不需要重新構(gòu)建 GraphServiceClient。 } catch (IOException | RuntimeException e) { // 處理令牌刷新失敗的情況,可能需要用戶重新登錄 System.err.println("Failed to refresh access token: " + e.getMessage()); // 標(biāo)記用戶需要重新認(rèn)證 } }
在與Azure AD集成的Java Web應(yīng)用程序中,實(shí)現(xiàn)訪問令牌刷新是維護(hù)用戶會(huì)話和提供無縫用戶體驗(yàn)的關(guān)鍵。雖然Graph SDK或Azure SDK的TokenCredential機(jī)制本身不直接處理刷新,但通過直接調(diào)用Azure AD的OAuth 2.0令牌端點(diǎn),我們可以利用刷新令牌獲取新的訪問令牌。正確地實(shí)現(xiàn)這一機(jī)制,并遵循安全最佳實(shí)踐,可以確保應(yīng)用程序能夠穩(wěn)定、安全地訪問Microsoft Graph和其他Azure AD保護(hù)的資源。
以上就是Azure AD 訪問令牌刷新機(jī)制詳解的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注php中文網(wǎng)其它相關(guān)文章!
每個(gè)人都需要一臺(tái)速度更快、更穩(wěn)定的 PC。隨著時(shí)間的推移,垃圾文件、舊注冊(cè)表數(shù)據(jù)和不必要的后臺(tái)進(jìn)程會(huì)占用資源并降低性能。幸運(yùn)的是,許多工具可以讓 Windows 保持平穩(wěn)運(yùn)行。
微信掃碼
關(guān)注PHP中文網(wǎng)服務(wù)號(hào)
QQ掃碼
加入技術(shù)交流群
Copyright 2014-2025 http://ipnx.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號(hào)