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

Angular 16+MSAL3获取的AccessToken验证失败求助

Angular 16 + MSAL 3 AccessToken验证失败问题排查与规范实现

问题背景

在Angular 16项目中使用MSAL 3做身份认证时,能成功获取AccessToken,但该令牌既无法通过Spring Boot后端验证,在jwt.io上也显示无效;但idToken验证完全正常。目前临时通过自定义HTTP拦截器将idToken放入请求头作为Bearer令牌来调用API,虽然可行但不符合规范。

已尝试的排查动作:

  • 在jwt.io上校验AccessToken
  • 在Azure AD中正确暴露API并配置scope
  • 调整Spring Boot后端的密钥、发行者(issuer)配置

核心原因分析

1. AccessToken的受众(aud)与API不匹配

当前拦截器中请求的scope是user.read,这是微软Graph API的内置scope,获取到的AccessToken受众是Graph API的客户端ID(00000003-0000-0000-c000-000000000000),而非你自己的Spring Boot API对应的Azure AD应用ID。后端验证时会检查令牌的aud字段是否与自身API的标识符匹配,自然会验证失败。

2. AccessToken的scope未包含API所需权限

即使Azure AD中暴露了API,若请求AccessToken时未指定对应API的scope(格式通常为api://{你的API应用ClientID}/{自定义scope名}),MSAL返回的令牌不会包含该API的访问权限,后端验证时会因权限不足或scope不匹配拒绝。

3. Spring Boot后端配置细节问题

  • 发行者(issuer)需与Azure AD的v2.0端点一致:https://login.microsoftonline.com/{你的租户ID}/v2.0
  • 需明确配置audience为你的API应用ClientID或暴露的scope标识符
  • JWK密钥集URI需正确指向Azure AD的公钥端点:https://login.microsoftonline.com/{你的租户ID}/discovery/v2.0/keys

规范的HTTP拦截器实现

替换为针对自身API的scope请求AccessToken,并使用正确的accessToken而非idToken:

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpErrorResponse,
  HttpStatusCode,
} from '@angular/common/http';
import { Observable, catchError, from, mergeMap, throwError } from 'rxjs';
import { MsalService } from '@azure/msal-angular';
import { SilentRequest } from '@azure/msal-browser';

@Injectable()
export class ApiAuthInterceptor implements HttpInterceptor {
  // 替换为你的Azure AD API应用暴露的scope
  private readonly apiScopes = ['api://your-api-client-id/access_as_user'];

  constructor(private authService: MsalService) { }

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // 仅拦截目标API请求
    if (request.url.includes('localhost:8080')) {
      const silentRequest: SilentRequest = {
        scopes: this.apiScopes,
        // 可选:指定账户,避免多账户场景下的问题
        account: this.authService.instance.getActiveAccount()
      };

      return from(this.authService.instance.acquireTokenSilent(silentRequest)).pipe(
        mergeMap((tokenResponse) => {
          // 使用accessToken而非idToken
          const authorizedReq = request.clone({
            setHeaders: {
              Authorization: `Bearer ${tokenResponse.accessToken}`,
              'Content-Type': 'application/json',
              Accept: 'application/json'
            }
          });
          return next.handle(authorizedReq);
        }),
        catchError((err) => {
          if (err instanceof HttpErrorResponse && err.status === HttpStatusCode.Forbidden) {
            console.error("API请求权限验证失败", err);
            // 可选:触发交互式登录获取令牌
            this.authService.loginRedirect({ scopes: this.apiScopes });
          }
          return throwError(() => err);
        })
      );
    }
    return next.handle(request);
  }
}

额外验证步骤

  1. 获取AccessToken后,在jwt.io上检查aud字段是否为你的API应用ClientID,scp字段是否包含你配置的API scope
  2. 确认Spring Boot的OAuth2资源服务器配置示例(Spring Boot 3+版本):
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuerUri;
    @Value("${spring.security.oauth2.resourceserver.jwt.audience}")
    private String audience;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt
                                .issuerUri(issuerUri)
                                .audience(audience)
                        )
                );
        return http.build();
    }
}

对应的application.yml配置:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://login.microsoftonline.com/你的租户ID/v2.0
          audience: api://你的API应用ClientID

内容的提问来源于stack exchange,提问作者Heiße Marone

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:30:32