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

Angular 11+.NET 5项目中将Azure AD token替换为自定义JWT token方案咨询

实现方案

整个需求分为后端.NET 5 API 适配、前端Angular 11适配两部分完成,无需改动原有基础认证的逻辑:


一、后端.NET 5 API 实现

1. 多认证方案配置

同时支持Azure AD JWT和自定义JWT校验,通过策略模式自动识别当前请求携带的token类型,在Startup.cs的ConfigureServices方法中添加如下配置:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = "CustomOrAzureAD";
    })
    .AddPolicyScheme("CustomOrAzureAD", "Custom or Azure AD JWT", options =>
    {
        options.ForwardDefaultSelector = context =>
        {
            var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
            if (authHeader?.StartsWith("Bearer ") == true)
            {
                var token = authHeader["Bearer ".Length..].Trim();
                // 解析token header判断是否为Azure AD签发,也可通过issuer前缀判断
                var tokenHeader = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(
                    Encoding.UTF8.GetString(Convert.FromBase64String(token.Split('.')[0]))
                );
                if (tokenHeader.TryGetValue("iss", out var issuer) && issuer.ToString().Contains("login.microsoftonline.com"))
                {
                    return "AzureADJwt";
                }
            }
            return "CustomJwt";
        };
    })
    .AddJwtBearer("AzureADJwt", options =>
    {
        // 替换为你的Azure AD租户配置
        options.Authority = "https://login.microsoftonline.com/你的租户ID";
        options.Audience = "你的Azure AD应用客户端ID";
    })
    .AddJwtBearer("CustomJwt", options =>
    {
        // 沿用原有基础认证的自定义JWT校验配置,保持完全一致
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = "自定义签发方",
            ValidateAudience = true,
            ValidAudience = "自定义受众",
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("你的自定义JWT签名密钥"))
        };
    });

    // 其他服务配置...
    services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 其他中间件...
    app.UseAuthentication();
    app.UseAuthorization();
    // 其他配置...
}

2. 自定义JWT生成与返回逻辑

添加全局行动作过滤器,校验如果当前请求使用Azure AD token认证通过,且用户已在本地系统存在匹配记录,生成与基础认证结构完全一致的自定义JWT,放入响应头返回给前端:

public class AzureAdTokenExchangeFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context) {}

    public void OnActionExecuted(ActionExecutedContext context)
    {
        var user = context.HttpContext.User;
        // 判断当前认证方案是Azure AD,且用户已匹配本地账号
        if (user.Identity.AuthenticationType == "AzureADJwt" && user.Identity.IsAuthenticated)
        {
            // 从Azure AD的Claims中获取用户唯一标识,匹配本地用户信息
            var aadUserId = user.Claims.FirstOrDefault(c => c.Type == "oid")?.Value;
            var localUser = _userRepository.FindByAadUserId(aadUserId);
            if (localUser != null)
            {
                // 调用原有生成自定义JWT的逻辑,和基础认证生成逻辑完全一致
                var customJwt = _jwtService.GenerateToken(localUser);
                // 自定义JWT放入响应头
                context.HttpContext.Response.Headers.Add("x-custom-jwt", customJwt);
            }
        }
    }
}

将过滤器注册到全局过滤器集合即可生效。


二、前端Angular 11 实现

核心逻辑通过Angular HTTP拦截器实现,无需改动业务接口调用逻辑

1. 前置配置

完成原有MSAL Angular的Azure AD登录逻辑,登录成功后将获取到的Azure AD access_token存入sessionStorage,key设置为azure_ad_token。

2. 认证拦截器实现

新增HTTP拦截器,统一处理请求头token替换、响应头自定义JWT存储逻辑:

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpResponse,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, tap, catchError, throwError } from 'rxjs';
import { MsalService } from '@azure/msal-angular';
import { Router } from '@angular/router';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private msalService: MsalService, private router: Router) {}

  intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    let token = '';
    // 优先使用本地存储的自定义JWT
    const customJwt = localStorage.getItem('custom_jwt');
    if (customJwt) {
      token = customJwt;
    } else {
      // 无自定义JWT时使用Azure AD token
      token = sessionStorage.getItem('azure_ad_token') || '';
    }

    // 挂载Authorization请求头
    if (token) {
      req = req.clone({
        setHeaders: { Authorization: `Bearer ${token}` }
      });
    }

    return next.handle(req).pipe(
      tap((event: HttpEvent<unknown>) => {
        if (event instanceof HttpResponse) {
          // 从响应头获取后端返回的自定义JWT
          const returnedCustomJwt = event.headers.get('x-custom-jwt');
          if (returnedCustomJwt) {
            // 存储自定义JWT,删除无用的Azure AD token
            localStorage.setItem('custom_jwt', returnedCustomJwt);
            sessionStorage.removeItem('azure_ad_token');
          }
        }
      }),
      catchError((error: HttpErrorResponse) => {
        if (error.status === 401) {
          const customJwt = localStorage.getItem('custom_jwt');
          if (!customJwt) {
            // Azure AD token无效,触发重新登录
            this.msalService.loginRedirect();
          } else {
            // 自定义JWT过期,走原有基础认证的过期逻辑
            localStorage.removeItem('custom_jwt');
            this.router.navigate(['/login']);
          }
        }
        return throwError(() => error);
      })
    );
  }
}

将拦截器注册到AppModule的HTTP_INTERCEPTORS提供者数组中即可生效。


三、流程校验点

  • 首次Azure AD登录后,本地仅存储Azure AD token,无自定义JWT
  • 首次API请求携带Azure AD token,后端校验通过后返回自定义JWT
  • 前端获取自定义JWT后存储,删除Azure AD token,后续所有请求自动携带自定义JWT

内容的提问来源于stack exchange,提问作者amol jadhav

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 21:18:02