
在开发与Microsoft Graph或其他Azure AD保护资源交互的Web应用程序时,用户通常会通过OAuth 2.0流程进行认证,获取到访问令牌(Access Token)和刷新令牌(Refresh Token)。访问令牌用于授权对受保护资源的访问,但它们具有有限的生命周期(通常为1小时)。当访问令牌过期后,应用程序需要一种机制来获取新的访问令牌,以维持用户会话并继续访问资源,而无需用户重新登录。
例如,在使用Azure SDK for Java或Microsoft Graph SDK时,我们可能会通过TokenCredential来提供访问令牌:
final TokenCredential tokenCredential = request -> {
// account.getTokenExpiry() 和 account.getAccessToken() 应该动态更新
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();上述代码片段展示了如何使用一个TokenCredential来为GraphServiceClient提供访问令牌。然而,当account.getAccessToken()中的令牌过期时,TokenCredentialAuthProvider本身并不会自动触发令牌刷新。此时,直接使用已过期的访问令牌进行API调用将导致认证失败。因此,我们需要主动地使用刷新令牌来获取新的访问令牌。
OAuth 2.0协议提供了“刷新令牌(refresh_token)”授权类型,允许客户端在访问令牌过期后,使用刷新令牌向授权服务器请求新的访问令牌。这个过程通常在后台进行,对用户透明。
为了获取新的访问令牌,我们需要向Azure AD的OAuth 2.0令牌端点发起一个POST请求。这个端点是:https://login.microsoftonline.com/common/oauth2/v2.0/token。
请求需要包含以下参数:
以下是一个使用Spring RestTemplate在Java中执行此操作的示例:
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 包含新访问令牌、刷新令牌(如果返回)和过期时间等信息的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: 这些值应从配置文件或环境变量中安全读取
MultiValueMap<String, String> map= new LinkedMultiValueMap<>();
map.add("client_id", "your_client_id"); // 替换为您的应用客户端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"); // 替换为您的权限范围
map.add("refresh_token", refreshToken);
map.add("client_secret", "your_client_secret"); // 替换为您的应用客户端密钥
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();
// 计算新的过期时间
OffsetDateTime expiryTime = OffsetDateTime.now(ZoneId.systemDefault()).plusSeconds(expiresInSeconds);
return new TokenResponse(newAccessToken, newRefreshToken, expiryTime);
} else {
// 处理错误响应
throw new RuntimeException("Failed to refresh token: " + response.getStatusCode() + " - " + response.getBody());
}
}
// 辅助类用于封装令牌响应
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; }
}
}此方法将返回一个JSON字符串,其中包含新的access_token、expires_in(访问令牌的有效期,以秒为单位),以及可能更新的refresh_token(某些授权服务器会在每次刷新时颁发新的刷新令牌,旧的刷新令牌会失效)。
获取到新的访问令牌后,需要更新应用程序中存储的令牌信息,并确保GraphServiceClient使用这个新令牌。由于原始的TokenCredential是通过Lambda表达式动态获取account对象的令牌,我们只需要更新account对象中存储的访问令牌和其过期时间即可。
假设您的account对象是一个自定义的数据结构,用于存储用户的认证信息:
// 假设您的 Account 类有以下方法
public class UserAccount {
private String accessToken;
private String refreshToken;
private OffsetDateTime tokenExpiry;
// ... 构造函数,getter和setter ...
public void updateTokens(String newAccessToken, String newRefreshToken, OffsetDateTime newExpiry) {
this.accessToken = newAccessToken;
this.refreshToken = newRefreshToken;
this.tokenExpiry = newExpiry;
}
}当您需要刷新令牌时:
// 假设 currentAccount 是当前用户的 UserAccount 实例
UserAccount currentAccount = // ... 从存储中加载 ...
// 检查令牌是否即将过期或已过期
if (currentAccount.getTokenExpiry().isBefore(OffsetDateTime.now(ZoneId.systemDefault()).plusMinutes(5))) { // 提前5分钟刷新
try {
AzureAdTokenRefresher refresher = new AzureAdTokenRefresher();
AzureAdTokenRefresher.TokenResponse tokenResponse = refresher.refreshAccessToken(currentAccount.getRefreshToken());
// 更新 account 对象中的令牌信息
currentAccount.updateTokens(
tokenResponse.getAccessToken(),
tokenResponse.getRefreshToken(), // 使用新的刷新令牌,如果返回了的话
tokenResponse.getExpiryTime()
);
// ... 将更新后的 currentAccount 保存回存储 ...
// GraphServiceClient 的 TokenCredential 会在下次请求时自动获取更新后的令牌
// 因为它的实现是每次请求时从 account 对象中获取最新令牌。
// 如果 GraphServiceClient 需要重新构建,则在此处重新构建。
// 对于上述 lambda 表达式实现的 TokenCredential,通常不需要重新构建 GraphServiceClient。
} catch (IOException | RuntimeException e) {
// 处理令牌刷新失败的情况,可能需要用户重新登录
System.err.println("Failed to refresh access token: " + e.getMessage());
// 标记用户需要重新认证
}
}在与Azure AD集成的Java Web应用程序中,实现访问令牌刷新是维护用户会话和提供无缝用户体验的关键。虽然Graph SDK或Azure SDK的TokenCredential机制本身不直接处理刷新,但通过直接调用Azure AD的OAuth 2.0令牌端点,我们可以利用刷新令牌获取新的访问令牌。正确地实现这一机制,并遵循安全最佳实践,可以确保应用程序能够稳定、安全地访问Microsoft Graph和其他Azure AD保护的资源。
以上就是Azure AD 访问令牌刷新机制详解的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号