使用带有 Angular 13 和 .NET 核心 Web api 的 Azure Active Directory 进行身份验证

Authenticating using Azure Active Directory with Angular 13 and .NET core web api

我有一个用于后端的 .NET CORE 6 Api 和一个用于前端的 Angular 13。目前我正在尝试使用 msal 通过 Angular 进行身份验证,然后调用受保护的 .net 核心 API。我想我错过了什么。 Api 是 weatherforcast Api 模板。它通过 AAD 成功验证,这让我可以获取用户信息。但是,当我尝试通过调用 http.get 方法通过 api 请求数据时,它没有授权。受保护的API如何从前端获取授权?这两个应用程序都在 Azure 活动目录中注册。根据我的理解,HTTP_INTERCEPTORS 应该拦截访问令牌,并且应该授予 api 的授权。 ProfileComponentMsalGuard.

app.module.ts

import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Configuration } from 'msal';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { BrowserCacheLocation, InteractionType, IPublicClientApplication, LogLevel, PublicClientApplication } from '@azure/msal-browser';
import { MsalGuard, MsalInterceptor, MsalBroadcastService, MsalInterceptorConfiguration, MsalModule, MsalService, MSAL_GUARD_CONFIG, MSAL_INSTANCE, MSAL_INTERCEPTOR_CONFIG, MsalGuardConfiguration, MsalRedirectComponent } from '@azure/msal-angular';
import { ProfileComponent } from './profile/profile.component';
import { environment } from '../environments/environment';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { MatFormFieldModule } from '@angular/material/form-field'
import { MatCheckboxModule } from '@angular/material/checkbox'
import { MatInputModule } from '@angular/material/input'
import { MatButtonModule } from '@angular/material/button'
import { MatDividerModule } from '@angular/material/divider'




const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1;

export function loggerCallback(logLevel: LogLevel, message: string) {
  console.log(message);
}

export function MSALInstanceFactory(): IPublicClientApplication {
  return new PublicClientApplication({
    auth: {
      clientId: environment.clientId,
      authority: environment.authority,
      redirectUri: environment.redirectUrl,
      postLogoutRedirectUri: environment.redirectUrl,
      navigateToLoginRequestUrl: true
    },
    cache: {
      cacheLocation: BrowserCacheLocation.LocalStorage,
      storeAuthStateInCookie: isIE, // set to true for IE 11
    },
    system: {
      loggerOptions: {
        loggerCallback,
        logLevel: LogLevel.Info,
        piiLoggingEnabled: false
      }
    }
  });
}
export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
  const protectedResourceMap = new Map<string, Array<string>>();
  protectedResourceMap.set('api://{uri id}', ['name.read']);

  return {
    interactionType: InteractionType.Redirect,
    protectedResourceMap
  };
}

export function MSALGuardConfigFactory(): MsalGuardConfiguration {
  return { interactionType: InteractionType.Redirect };
}

@NgModule({
  declarations: [
    AppComponent,
    ProfileComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule,
    MsalModule,
    BrowserAnimationsModule,
    MatCheckboxModule,
    MatInputModule,
    MatButtonModule,
    MatFormFieldModule,
    MatDividerModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MsalInterceptor,
      multi: true
    },
    {
      provide: MSAL_INSTANCE,
      useFactory: MSALInstanceFactory
    },
    {
      provide: MSAL_GUARD_CONFIG,
      useFactory: MSALGuardConfigFactory
    },
    {
      provide: MSAL_INTERCEPTOR_CONFIG,
      useFactory: MSALInterceptorConfigFactory
    },
    MsalService,
    MsalGuard,
    MsalBroadcastService
  ],
  bootstrap: [AppComponent, MsalRedirectComponent]
})
export class AppModule { }

profile.component.ts

import { Component, OnInit } from '@angular/core';
import { MsalService } from '@azure/msal-angular';
import { ApiService } from '../services/api.service';

@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {

  forecasts: any;
  name: any;
  username: any;



  constructor(private _apiService: ApiService, private _msalService: MsalService) { }

  ngOnInit(): void {

    const account = this._msalService.instance.getAllAccounts()[0];
    this.name = account?.name;
    this.username = account?.username;

    this._apiService.getForcast().subscribe(result => {
      this.forecasts = result;
    }, error => console.error(error));
  }

}

appsettings.json

"AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "example.com",
    "TenantId": "{TenantId}",
    "ClientId": "{ClientId}",
    "Scopes": "name.write name.read",
    "CallbackPath": "/signin-oidc",
    "Audience": "api://{uri id}"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"

您可能想要检查受保护的资源地图。您当前拥有“api://{uri id}”。尝试将实际的 API url 放在这里只是为了测试一下。类似于...“http://localhost:1448/api/weatherforecasts”,或任何端点。拨打电话时,请查看授权 header 是否在请求中。