以下是用 Java 從 Azure AD 獲取用戶信息的完整實現方案,使用 Spring Boot 框架和 Microsoft 身份驗證庫 (MSAL):
?
1. 添加 Maven 依賴
<dependencies>
? ? <!-- Spring Boot Web -->
? ? <dependency>
? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? <artifactId>spring-boot-starter-web</artifactId>
? ? </dependency>
? ??
? ? <!-- Azure AD MSAL -->
? ? <dependency>
? ? ? ? <groupId>com.microsoft.azure</groupId>
? ? ? ? <artifactId>msal4j</artifactId>
? ? ? ? <version>1.13.3</version>
? ? </dependency>
? ??
? ? <!-- JWT 處理 -->
? ? <dependency>
? ? ? ? <groupId>com.nimbusds</groupId>
? ? ? ? <artifactId>nimbus-jose-jwt</artifactId>
? ? ? ? <version>9.25</version>
? ? </dependency>
? ??
? ? <!-- HTTP 客戶端 -->
? ? <dependency>
? ? ? ? <groupId>org.apache.httpcomponents</groupId>
? ? ? ? <artifactId>httpclient</artifactId>
? ? ? ? <version>4.5.13</version>
? ? </dependency>
</dependencies>
?
2. 配置 Azure AD 參數
在 ?application.properties??中:
# Azure AD 配置
azure.client-id=your_client_id
azure.client-secret=your_client_secret
azure.tenant-id=your_tenant_id
azure.redirect-uri=https://your-app.com/auth/redirect
azure.scope=openid profile User.Read
?
3. 控制器實現
import com.microsoft.aad.msal4j.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
@RestController
public class AuthController {
? ??
? ? @Value("${azure.client-id}")
? ? private String clientId;
? ??
? ? @Value("${azure.client-secret}")
? ? private String clientSecret;
? ??
? ? @Value("${azure.tenant-id}")
? ? private String tenantId;
? ??
? ? @Value("${azure.redirect-uri}")
? ? private String redirectUri;
? ??
? ? @Value("${azure.scope}")
? ? private String scope;
? ??
? ? // 第一步:生成登錄URL
? ? @GetMapping("/login")
? ? public Map<String, String> login() throws MalformedURLException {
? ? ? ? String authUrl = getAuthUrl();
? ? ? ? return Collections.singletonMap("loginUrl", authUrl);
? ? }
? ??
? ? // 第二步:處理回調
? ? @GetMapping("/auth/redirect")
? ? public Map<String, Object> handleRedirect(
? ? ? ? ? ? @RequestParam("code") String authCode,
? ? ? ? ? ? @RequestParam("state") String state
? ? ) throws Exception {
? ? ? ??
? ? ? ? // 使用授權碼獲取令牌
? ? ? ? IAuthenticationResult result = acquireToken(authCode);
? ? ? ??
? ? ? ? // 獲取用戶信息
? ? ? ? Map<String, Object> userInfo = getUserInfo(result.accessToken());
? ? ? ??
? ? ? ? // 返回用戶信息
? ? ? ? Map<String, Object> response = new HashMap<>();
? ? ? ? response.put("id_token_claims", result.idToken());
? ? ? ? response.put("user_info", userInfo);
? ? ? ??
? ? ? ? return response;
? ? }
? ??
? ? // 生成認證URL
? ? private String getAuthUrl() throws MalformedURLException {
? ? ? ? ConfidentialClientApplication app = ConfidentialClientApplication.builder(
? ? ? ? ? ? ? ? clientId, ClientCredentialFactory.createFromSecret(clientSecret))
? ? ? ? ? ? ? ? .authority("https://login.microsoftonline.com/" + tenantId)
? ? ? ? ? ? ? ? .build();
? ? ? ??
? ? ? ? AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder(
? ? ? ? ? ? ? ? authCode,?
? ? ? ? ? ? ? ? new URI(redirectUri)
? ? ? ? ? ? ).scopes(Collections.singleton(scope))
? ? ? ? ? ? ?.build();
? ? ? ??
? ? ? ? String authorizationUrl = app.getAuthorizationRequestUrl(
? ? ? ? ? ? ? ? AuthorizationRequestUrlParameters
? ? ? ? ? ? ? ? ? ? .builder(redirectUri, Collections.singleton(scope))
? ? ? ? ? ? ? ? ? ? .build()
? ? ? ? ? ? ).toString();
? ? ? ??
? ? ? ? return authorizationUrl;
? ? }
? ??
? ? // 使用授權碼獲取令牌
? ? private IAuthenticationResult acquireToken(String authCode)?
? ? ? ? throws MalformedURLException, ExecutionException, InterruptedException {
? ? ? ??
? ? ? ? ConfidentialClientApplication app = ConfidentialClientApplication.builder(
? ? ? ? ? ? ? ? clientId, ClientCredentialFactory.createFromSecret(clientSecret))
? ? ? ? ? ? ? ? .authority("https://login.microsoftonline.com/" + tenantId)
? ? ? ? ? ? ? ? .build();
? ? ? ??
? ? ? ? AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder(
? ? ? ? ? ? ? ? authCode,?
? ? ? ? ? ? ? ? new URI(redirectUri)
? ? ? ? ? ? ).scopes(Collections.singleton(scope))
? ? ? ? ? ? ?.build();
? ? ? ??
? ? ? ? return app.acquireToken(parameters).get();
? ? }
? ??
? ? // 使用訪問令牌獲取用戶信息
? ? private Map<String, Object> getUserInfo(String accessToken) {
? ? ? ? String graphEndpoint = "https://graph.microsoft.com/v1.0/me";
? ? ? ??
? ? ? ? // 使用HttpClient調用Graph API
? ? ? ? // 實際實現需要添加錯誤處理和JSON解析
? ? ? ? return fetchUserDataFromGraph(graphEndpoint, accessToken);
? ? }
? ??
? ? // 調用Microsoft Graph API的實現
? ? private Map<String, Object> fetchUserDataFromGraph(String endpoint, String accessToken) {
? ? ? ? // 這里使用HttpClient簡化實現
? ? ? ? // 實際項目使用RestTemplate或WebClient
? ? ? ??
? ? ? ? HttpGet request = new HttpGet(endpoint);
? ? ? ? request.setHeader("Authorization", "Bearer " + accessToken);
? ? ? ??
? ? ? ? try (CloseableHttpClient httpClient = HttpClients.createDefault();
? ? ? ? ? ? ?CloseableHttpResponse response = httpClient.execute(request)) {
? ? ? ? ? ??
? ? ? ? ? ? String json = EntityUtils.toString(response.getEntity());
? ? ? ? ? ? return new Gson().fromJson(json, new TypeToken<Map<String, Object>>(){}.getType());
? ? ? ? ? ??
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? throw new RuntimeException("Failed to fetch user info", e);
? ? ? ? }
? ? }
}
?
4. 安全配置類 (可選)
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.proc.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.MalformedURLException;
import java.net.URL;
@Configuration
public class SecurityConfig {
? ? @Value("${azure.tenant-id}")
? ? private String tenantId;
? ??
? ? // 配置JWT驗證器
? ? @Bean
? ? public ConfigurableJWTProcessor<SecurityContext> jwtProcessor()?
? ? ? ? throws MalformedURLException {
? ? ? ??
? ? ? ? // Azure AD JWKS端點
? ? ? ? String jwksUrl = String.format(
? ? ? ? ? ? "https://login.microsoftonline.com/%s/discovery/v2.0/keys",?
? ? ? ? ? ? tenantId
? ? ? ? );
? ? ? ??
? ? ? ? // 設置JWT處理器
? ? ? ? DefaultJWTProcessor<SecurityContext> jwtProcessor =?
? ? ? ? ? ? new DefaultJWTProcessor<>();
? ? ? ??
? ? ? ? // 配置JWK來源
? ? ? ? JWKSource<SecurityContext> keySource =?
? ? ? ? ? ? new RemoteJWKSet<>(new URL(jwksUrl));
? ? ? ??
? ? ? ? // 配置簽名驗證
? ? ? ? JWSAlgorithm expectedJWSAlg = JWSAlgorithm.RS256;
? ? ? ? JWSVerificationKeySelector<SecurityContext> keySelector =?
? ? ? ? ? ? new JWSVerificationKeySelector<>(expectedJWSAlg, keySource);
? ? ? ??
? ? ? ? jwtProcessor.setJWSKeySelector(keySelector);
? ? ? ??
? ? ? ? // 設置JWT聲明驗證器
? ? ? ? jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>(
? ? ? ? ? ? new JWTClaimsSet.Builder().build(),
? ? ? ? ? ? new HashSet<>(Arrays.asList("sub", "aud", "exp", "iat"))));
? ? ? ??
? ? ? ? return jwtProcessor;
? ? }
}
?
?