You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3验证Azure AD OAuth2令牌失败求助

Azure AD令牌验证失败:Spring Boot 3后端无法验证Angular MSAL 2.0获取的access_token

问题背景

我维护的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 10:07:33