如何在不开启允许公共客户端流的情况下实现Azure AD委托权限认证
问题描述
我有一个名为Azure_AD_Delegated的Azure AD应用,已配置User.Read.All和GroupMember.Read.All的委托权限,采用用户名密码(ROPC)认证方式,核心代码如下:
new UsernamePasswordCredentialBuilder() .clientId(clientId) .username(username) .password(password) .tenantId(tenantId) .build() .getTokenSync(tokenRequestContext.addScopes(".default"));
未开启应用的「允许公共客户端流」选项时,执行代码会抛出异常:
com.microsoft.aad.msal4j.MsalServiceException: AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'. Trace ID: 32eeb109-2202-443c-9a5d-56606bba0e00 Correlation ID: ab8a7cea-ee9c-4ba6-8b3d-23faaef98e94 Timestamp: 2024-05-17 13:00:05Z
开启「允许公共客户端流」后认证成功,可正常调用Graph API,但微软文档提示该选项存在安全风险,且客户不愿使用应用权限+客户端凭证流。需要实现不开启公共客户端流的前提下,用用户名密码认证调用Graph API。
完整的可运行代码如下:
private void generateAccessTokenByUserNameAndPassword() { try { TokenRequestContext tokenRequestContext = new TokenRequestContext(); accessToken = new UsernamePasswordCredentialBuilder() .clientId(clientId) .username(username) .password(password) .tenantId(tenantId) .build() .getTokenSync(tokenRequestContext.addScopes(".default")); graphClient = getGraphClient(); } catch (Exception e) { throw new RuntimeException(e); } } @SuppressWarnings("rawtypes") private GraphServiceClient getGraphClient() { IAuthenticationProvider provider = new IAuthenticationProvider() { @Override public CompletableFuture<String> getAuthorizationTokenAsync(URL requestUrl) { return CompletableFuture.completedFuture(accessToken.getToken()); } }; return GraphServiceClient.builder().authenticationProvider(provider).buildClient(); }
解决方案
原理说明
当应用未开启「允许公共客户端流」时,Azure AD将其视为机密客户端,要求所有认证请求必须携带客户端身份凭证(client_secret或客户端证书)以验证应用合法性,这是规避公共客户端安全风险的核心机制。用户名密码(ROPC)流同样支持机密客户端模式,只需在请求中添加客户端凭证即可。
具体步骤
- 生成客户端密钥:在Azure AD应用的「证书和密码」页面,新建客户端密码(client secret),记录密钥值(仅生成时可见,需妥善保存)。
- 修改认证代码:在
UsernamePasswordCredentialBuilder中添加.clientSecret(clientSecret)配置,将客户端密钥传入认证流程。
修改后的代码示例
private void generateAccessTokenByUserNameAndPassword() { try { // 替换为你的客户端密钥 String clientSecret = "YOUR_CLIENT_SECRET_VALUE"; TokenRequestContext tokenRequestContext = new TokenRequestContext(); accessToken = new UsernamePasswordCredentialBuilder() .clientId(clientId) .username(username) .password(password) .tenantId(tenantId) // 添加客户端密钥,满足机密客户端的身份验证要求 .clientSecret(clientSecret) .build() .getTokenSync(tokenRequestContext.addScopes(".default")); graphClient = getGraphClient(); } catch (Exception e) { throw new RuntimeException(e); } } // getGraphClient方法保持不变 @SuppressWarnings("rawtypes") private GraphServiceClient getGraphClient() { IAuthenticationProvider provider = new IAuthenticationProvider() { @Override public CompletableFuture<String> getAuthorizationTokenAsync(URL requestUrl) { return CompletableFuture.completedFuture(accessToken.getToken()); } }; return GraphServiceClient.builder().authenticationProvider(provider).buildClient(); }
注意事项
- 客户端密钥属于敏感信息,需通过安全方式存储(如Azure Key Vault),禁止硬编码在代码中。
- 若需更高安全性,可使用客户端证书替代客户端密钥,只需将
.clientSecret(clientSecret)替换为.clientCertificate(clientCertificate)(需提前在Azure AD应用中配置证书)。
内容的提问来源于stack exchange,提问作者Avinash Reddy
相关产品推荐
相关产品推荐

