亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

搜索

Azure AD 訪問令牌刷新機(jī)制詳解

霞舞
發(fā)布: 2025-09-18 14:28:02
原創(chuàng)
991人瀏覽過

azure ad 訪問令牌刷新機(jī)制詳解

本文詳細(xì)闡述了在Java Web應(yīng)用中集成Microsoft賬戶登錄后,如何處理Azure AD訪問令牌過期的問題。當(dāng)現(xiàn)有訪問令牌失效時(shí),不能直接使用刷新令牌進(jìn)行API調(diào)用。文章提供了一種通過直接調(diào)用Azure AD OAuth2.0令牌端點(diǎn),利用刷新令牌獲取新訪問令牌的解決方案,并指導(dǎo)如何將新令牌集成到GraphServiceClient中,同時(shí)強(qiáng)調(diào)了相關(guān)的安全和實(shí)現(xiàn)注意事項(xiàng)。

訪問令牌的生命周期與挑戰(zhàn)

在開發(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();
登錄后復(fù)制

上述代碼片段展示了如何使用一個(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ì)用戶透明。

刷新令牌的API調(diào)用

為了獲取新的訪問令牌,我們需要向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ù):

  • client_id: 您的應(yīng)用程序注冊(cè)時(shí)獲得的客戶端ID。
  • grant_type: 必須設(shè)置為refresh_token。
  • redirect_uri: 您的應(yīng)用程序注冊(cè)時(shí)配置的重定向URI。
  • scope: 您希望請(qǐng)求的權(quán)限范圍。通常與原始授權(quán)請(qǐng)求中的范圍相同或更小。
  • refresh_token: 用戶登錄時(shí)獲取到的刷新令牌。
  • client_secret: 您的應(yīng)用程序注冊(cè)時(shí)生成的客戶端密鑰。

以下是一個(gè)使用Spring RestTemplate在Java中執(zhí)行此操作的示例:

AI建筑知識(shí)問答
AI建筑知識(shí)問答

用人工智能ChatGPT幫你解答所有建筑問題

AI建筑知識(shí)問答22
查看詳情 AI建筑知識(shí)問答
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; }
    }
}
登錄后復(fù)制

此方法將返回一個(gè)JSON字符串,其中包含新的access_token、expires_in(訪問令牌的有效期,以秒為單位),以及可能更新的refresh_token(某些授權(quán)服務(wù)器會(huì)在每次刷新時(shí)頒發(fā)新的刷新令牌,舊的刷新令牌會(huì)失效)。

將新令牌集成到GraphServiceClient

獲取到新的訪問令牌后,需要更新應(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;
    }
}
登錄后復(fù)制

當(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)證
    }
}
登錄后復(fù)制

注意事項(xiàng)

  1. 安全性: client_id和client_secret是敏感信息,絕不應(yīng)硬編碼在代碼中或直接暴露給客戶端。它們應(yīng)該從安全配置(如環(huán)境變量、Azure Key Vault、配置文件)中讀取。client_secret應(yīng)嚴(yán)格保密。
  2. 刷新令牌的存儲(chǔ): 刷新令牌具有較長(zhǎng)的生命周期,并且可以用于獲取新的訪問令牌。因此,它們必須像密碼一樣安全地存儲(chǔ)(例如,加密存儲(chǔ)在數(shù)據(jù)庫(kù)中,或使用安全的會(huì)話管理)。
  3. 錯(cuò)誤處理: 令牌刷新請(qǐng)求可能會(huì)失敗,例如網(wǎng)絡(luò)問題、刷新令牌過期或被吊銷等。您的應(yīng)用程序應(yīng)妥善處理這些錯(cuò)誤,并可能需要提示用戶重新登錄。
  4. 刷新令牌的生命周期: 雖然刷新令牌的生命周期通常比訪問令牌長(zhǎng)得多,但它們也可能過期或被吊銷(例如,用戶更改密碼、管理員撤銷權(quán)限)。因此,應(yīng)用程序需要準(zhǔn)備好在刷新令牌失效時(shí)引導(dǎo)用戶重新進(jìn)行完整的認(rèn)證流程。
  5. 并發(fā)刷新: 如果應(yīng)用程序是多線程或分布式部署的,并且多個(gè)請(qǐng)求可能同時(shí)嘗試刷新同一個(gè)用戶的令牌,需要實(shí)現(xiàn)適當(dāng)?shù)耐綑C(jī)制(如分布式鎖)來避免不必要的重復(fù)刷新或競(jìng)態(tài)條件。
  6. redirect_uri和scope: 在刷新令牌請(qǐng)求中使用的redirect_uri和scope應(yīng)與最初獲取刷新令牌時(shí)使用的值保持一致。
  7. 響應(yīng)解析: 令牌端點(diǎn)返回的JSON響應(yīng)可能包含除了access_token和expires_in之外的其他字段,如token_type、scope和新的refresh_token。務(wù)必解析并利用這些信息。特別是,如果返回了新的refresh_token,您應(yīng)該用它替換舊的刷新令牌。

總結(jié)

在與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)文章!

最佳 Windows 性能的頂級(jí)免費(fèi)優(yōu)化軟件
最佳 Windows 性能的頂級(jí)免費(fèi)優(yōu)化軟件

每個(gè)人都需要一臺(tái)速度更快、更穩(wěn)定的 PC。隨著時(shí)間的推移,垃圾文件、舊注冊(cè)表數(shù)據(jù)和不必要的后臺(tái)進(jìn)程會(huì)占用資源并降低性能。幸運(yùn)的是,許多工具可以讓 Windows 保持平穩(wěn)運(yùn)行。

下載
來源:php中文網(wǎng)
本文內(nèi)容由網(wǎng)友自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,本站不承擔(dān)相應(yīng)法律責(zé)任。如您發(fā)現(xiàn)有涉嫌抄襲侵權(quán)的內(nèi)容,請(qǐng)聯(lián)系admin@php.cn
最新問題
開源免費(fèi)商場(chǎng)系統(tǒng)廣告
最新下載
更多>
網(wǎng)站特效
網(wǎng)站源碼
網(wǎng)站素材
前端模板
關(guān)于我們 免責(zé)申明 意見反饋 講師合作 廣告合作 最新更新
php中文網(wǎng):公益在線php培訓(xùn),幫助PHP學(xué)習(xí)者快速成長(zhǎng)!
關(guān)注服務(wù)號(hào) 技術(shù)交流群
PHP中文網(wǎng)訂閱號(hào)
每天精選資源文章推送
PHP中文網(wǎng)APP
隨時(shí)隨地碎片化學(xué)習(xí)
PHP中文網(wǎng)抖音號(hào)
發(fā)現(xiàn)有趣的

Copyright 2014-2025 http://ipnx.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號(hào)