Spring Boot 3验证Azure AD OAuth2令牌失败求助
问题背景
我维护的Spring Boot 3后端(基于Java 17),需要验证Angular前端通过MSAL 2.0库(@azure/msal-browser + @azure/msal-react)获取的Azure AD OAuth2 access_token。
Azure AD中注册的是SPA类型应用,无凭据配置,启用了隐式和混合流的ID Tokens颁发;重定向URI包含本地和云端部署的前端首页路径;令牌权限仅设置了User.Read用于获取用户详情,未配置暴露的API。
前端登录流程正常:跳转Azure登录页面后,/token接口返回access_token、refresh_token、id_token等令牌,但将access_token携带到后端API请求时,后端验证失败,Spring Security的DEBUG日志输出:
AuthenticationWebFilter : Authentication failed: Failed to validate the token
我参考了微软官方文档、Baeldung教程等资料,尝试多种配置变体均无效,疑似遗漏关键配置项。
后端配置
Gradle依赖
// Spring核心依赖 implementation "org.springframework.boot:spring-boot-starter-actuator" implementation "org.springframework.boot:spring-boot-starter-security" ..... implementation "org.springframework.security:spring-security-oauth2-client" implementation "org.springframework.security:spring-security-oauth2-jose" implementation "org.springframework.security:spring-security-oauth2-resource-server" implementation "org.springframework.session:spring-session-core" ..... // Azure AD相关依赖 implementation 'com.azure.spring:spring-cloud-azure-starter-active-directory:5.4.0' implementation 'com.azure.spring:spring-cloud-azure-starter:5.4.0'
曾尝试单独或组合使用上述两个Azure依赖,未发现差异。
application.yml配置
spring: security: oauth2: resourceserver: jwt: issuer-uri: https://sts.windows.net/<TENANT-ID>/ jwk-set-uri: https://login.microsoftonline.com/<TENANT-ID>/discovery/v2.0/keys cloud: azure: profile: tenant-id: <TENANT-ID> cloud-type: Azure credential: managed-identity-enabled: true client-id: <APPLICATION-ID>
最初将issuer-uri设置为https://login.microsoftonline.com/<TENANT-ID>/v2.0,解码access_token后发现iss字段的主机为sts.windows.net,因此修改了该配置。
Spring安全配置
API安全过滤器链
@Bean @Order(2) public SecurityWebFilterChain apiSecurity( ServerHttpSecurity http, OAuth2ResourceServerConfigurer oAuth2ResourceServerConfigurer) { http.securityMatcher(API_MATCHER) .csrf().disable() .authorizeExchange() .anyExchange().authenticated(); oAuth2ResourceServerConfigurer.configure(http); return http.build(); }
OAuth2ResourceServerConfigurer片段
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") private String jwksUrl; public void configure(ServerHttpSecurity http) { http.oauth2ResourceServer( oauth2ResourceServer -> oauth2ResourceServer.jwt( jwt -> jwt.jwkSetUri(jwksUrl))); }
安全配置在集成测试中表现符合预期:未携带mock JWT时返回401 Unauthorized,携带有效mock JWT时返回200 OK。
前端配置
MSAL核心配置(AuthConfig组件)
export const msalConfig: Configuration = { auth: { clientId: authCredentials.clientId, authority: 'https://login.microsoftonline.com/<TENANT-ID>/', redirectUri: window.location.origin + process.env.PUBLIC_URL, postLogoutRedirectUri: window.location.origin + process.env.PUBLIC_URL, }, system: { allowNativeBroker: false, // 禁用WAM Broker }, }; // ID Token使用的权限范围 export const loginRequest: PopupRequest = { scopes: ['User.Read'], }; // MS Graph API端点配置(未实际使用) export const graphConfig = { graphMeEndpoint: 'https://graph.microsoft.com/v1.0/me', };
实例初始化(Index.tsx)
export const msalInstance = new PublicClientApplication(msalConfig);
注:graphConfig仅定义未在项目中使用,前端非本人开发,仅负责后续迭代工作。
测试验证代码(Debug按钮逻辑)
async function handleSubmit(event: any) { const account = msalInstance.getActiveAccount(); if (!account) { throw Error( 'No active account! Verify a user has been signed in and setActiveAccount has been called.' ); } const response = await msalInstance.acquireTokenSilent({ ...loginRequest, account: account, }); const headers = new Headers(); const bearer = `Bearer ${response.accessToken}`; headers.append('Authorization', bearer); const options = { method: 'GET', headers: headers, }; return fetch(`${backendBaseUrl}/v2/debug/user`, options) .then(response => response) .catch(error => { console.log(error); }); }
排查限制
无法查看Azure AD日志,仅能控制前后端代码及配置;解码后的access_token显示iss字段为https://sts.windows.net/<TENANT-ID>/。
TL;DR
前端完成Azure AD认证后,将获取的access_token发送至后端API,后端通过oauth2ResourceServer配置指向Azure AD租户和应用进行令牌验证,但所有请求均被拒绝并返回401 Unauthorized,验证失败。
内容的提问来源于stack exchange,提问作者Nico

