JWT 身份验证令牌在 Angular 和 .Net Core 中具有无效的 Audience 和 Issuer 以及缺少范围

问题描述

我一直致力于将我的 Angular 和 .Net Core 应用程序发布到 OpenShift Container Platform。我已将 JWT 授权令牌配置为对用户进行身份验证并获取 User.Identity.Name 信息。

我使用 Angular 作为前端,使用 .Net Core 3.1 作为后端。在本地一切正常,但是当我尝试将我的应用程序发布到 OpenShift 时,它给了我错误的受众和发行者信息。它也没有给我任何令牌范围。

我对它进行了故障排除,发现我从部署到 OpenShift 的应用程序获得的令牌是 v2.0,而它在本地为我提供了 v1.0 令牌。

任何帮助将不胜感激。

这是我的 app.componenet.ts 文件。

import { Component,OnInit,OnDestroy } from '@angular/core';
import { BroadcastService,MsalService } from '@azure/msal-angular';
import { Subscription } from 'rxjs/Subscription';

@Component
({
selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit,OnDestroy
{
loggedIn: boolean;
private subscription: Subscription;
public isIframe: boolean;
token: string;
// --------------------------------------------------------------------

constructor(private broadcastService: BroadcastService,private authService: MsalService) { 

}

// --------------------------------------------------------------------

ngOnInit() 
{

    this.broadcastService.subscribe("msal:loginSuccess",(payload) => {      
        localStorage.setItem('token',JSON.stringify(payload));      
        this.token = JSON.parse(localStorage.getItem('token'))['_token'];
        this.loggedIn = true;
        this.checkoutAccount();
      }); 
}

// --------------------------------------------------------------------

checkoutAccount() {
    this.loggedIn = !!this.authService.getAccount();
    if (this.m_router.url == "/")
    {
        if (!this.m_dbService.isAuthorizedUser())
        {
            this.setUserAndRoles();
        }
    }
}

// --------------------------------------------------------------------

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

    if (isIE) {
        this.authService.loginRedirect();
    } else {
        this.authService.loginPopup();
    }
}

// --------------------------------------------------------------------

logout() {
    this.authService.logout();
}

// --------------------------------------------------------------------

ngOnDestroy() {
    this.broadcastService.getMSALSubject().next(1);
    if (this.subscription) {
        this.subscription.unsubscribe();
    }
}

}

这是我的 app.module.ts 文件。

// Modules
import { NgModule,ErrorHandler,forwardRef } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; // new
import { HttpModule } from '@angular/http';
import { HttpClientModule,HTTP_INTERCEPTORS } from '@angular/common/http';

// Routing
import { HashLocationStrategy,LocationStrategy,Location } from '@angular/common';

// Components
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

// Authentication
import { MsalModule,MsalInterceptor,MsalAngularConfiguration,MSAL_CONFIG,MSAL_CONFIG_ANGULAR,MsalService } from '@azure/msal-angular';
import { Logger,Configuration } from 'msal';
import { environment } from '../environments/environment';

export function loggerCallback(logLevel,message,piiEnabled) {
  console.log('client logging' + message);
}

export const protectedResourceMap: Map<string,string[]> = new Map([
  [environment.apiUrl,['api://azure-app-client-id/api_consume']]
]);

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

@NgModule
({
  imports: 
  [
     BrowserModule,BrowserAnimationsModule,ToastrModule.forRoot(),AppRoutingModule,SharedModule,CoreModule.forRoot(),FwModule,HttpModule,HttpClientModule,MsalModule.forRoot({
      auth: {
        clientId: environment.clientId,authority: environment.authority,redirectUri: environment.redirectUrl
      },framework: {
        protectedResourceMap: protectedResourceMap
      }
    },{
      consentScopes: ['openid','profile','api://azure-app-client-id/api_consume'],}
    )
  ],declarations: 
  [
     AppComponent
  ],exports: 
  [
  ],providers: 
  [    
     Globals,CookieService,AcErrorService,{provide: ErrorHandler,useClass: GlobalErrorHandlerService},Location,{provide: LocationStrategy,useClass: HashLocationStrategy},{
      provide: HTTP_INTERCEPTORS,useClass: MsalInterceptor,multi: true
    } 
  ],bootstrap: [AppComponent]
})
export class AppModule { }

这是我的 environment.ts 文件。

export const environment = {
  production: false,clientId: 'my-app-registration-client-id',authority: 'https://login.microsoftonline.com/my-app-registration-tenant-id',redirectUrl: 'http://localhost:4200/',apiUrl: 'http://localhost:5000/'
};

这是我的 environment.prod.ts 文件。

export const environment = {
  production: true,redirectUrl: 'my-openshift-app-url',apiUrl: 'my-openshift-app-url'
};

这是我的 database.service.ts 文件。

export class DatabaseService
{

constructor
    (
        public m_httpClient: HttpClient
    )
    {}

public get(NetCoreApiPath): Observable<any>
    {
        var strUrl;

        try
        {
            strUrl = this.m_globals.getBaseUrl() + p_strApiPath;
            return this.m_httpClient.get<any>(strUrl,{ withCredentials: true })
            .pipe(map(data => data),catchError(this.handleError));
        }
        catch(err)
        {
            console.error(err);
            this.sendErrorEmail(err.message,"1");
        }
    }

}

这是我的 .Net Core appsettings.json 文件。

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=my-db-server,1433;Initial Catalog=my-db;Integrated Security=true;"
  },"Logging": {
    "LogLevel": {
      "Default": "Information","Microsoft": "Warning","Microsoft.Hosting.Lifetime": "Information"
    }
  },"AllowedHosts": "*","AzureAd": {
    "Instance": "https://login.microsoftonline.com/","Domain": "https://MyOrg.onmicrosoft.com/","TenantId": "my-app-registration-tenant-id","Authority": "https://sts.windows.net/my-app-registration-tenant-id/","Audience": "api://my-app-registration-client-id","ClientId": "my-app-registration-client-id",}

}

这是我的 .Net Core startup.cs 文件。

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using API.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

namespace API
{
    public class Startup
    {
        private readonly IWebHostEnvironment _environment;

        public Startup(IConfiguration configuration,IWebHostEnvironment env)
        {
            Configuration = configuration;
            _environment = env;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();

            services.AddAuthentication(sharedOptions =>
            {
                sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                Configuration.Bind("AzureAd",options);
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,ValidateAudience = true
                };
            });

            services.AddCors(options =>
            {
                options.AddPolicy("AllowCors",builder => builder
                                .WithOrigins("http://localhost:4200","my-openshift-app-url")
                                .AllowAnyHeader()
                                .AllowAnyMethod()
                                .AllowCredentials()
                    );
            });

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

            services
                .AddMvc(options =>
                {
                    var policy = new AuthorizationPolicyBuilder()
                                     .RequireAuthenticatedUser()
                                     .Build();
                    options.Filters.Add(new AuthorizeFilter(policy));
                    options.MaxIAsyncEnumerableBufferLimit = int.MaxValue;
                })
                .AddNewtonsoftJson(options =>
                    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                );

            var connectionString = Configuration["ConnectionStrings:DefaultConnection"];

            if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production")
                services.AddDbContext<AppDB>(options =>
                        options.UseSqlServer(connectionString));
            else
                services.AddDbContext<AppDB>(options =>
                        options.UseSqlServer(connectionString));

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/error");
            }

            app.UseDefaultFiles();

            app.UseStaticFiles();       

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseCors("AllowCors");

            app.UseAuthentication();        

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                name: "default",pattern: "{controller=Home}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });

        }
    }

}

这是我的控制器类。

using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Web.Http;
using Microsoft.AspNetCore.Http.Extensions;
using System.Net;
using Microsoft.AspNetCore.Authorization;
using API.Helpers;

namespace API.Controllers
{

    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeStatusController : ControllerBase
    {
        private string m_strUserName;
        private readonly AppDB db;

        public EmployeeStatusController(AppDB context)
        {
            db = context;
        }

        [Authorize]
        [HttpGet]
        public IQueryable<EmployeeStatus> GetEmployeeStatus()
        {
            try
            {
                this.m_strUserName = UserHelper.getUserNameOrAlias(User);

                IQueryable<EmployeeStatus> data =

                    from tbl in db.EmployeeStatus

                    select tbl;
                    // ----- /get -----

                    return data;

        }
            catch (Exception ex)
            {
                if (m_strError.Length == 0)
                {
                    if (this.showException())
                    {
                        m_strError = ex.ToString();
                    }
                }

                sendEmail(this.m_strError + ":  " + ex.ToString());

                throw new System.Web.Http.HttpResponseException(HttpStatusCode.Forbidden);
            }
        }

}
}

这是我的 UserHelper.cs 类。

namespace API.Helpers
{
    public static class UserHelper
    {
        public static string getUserName(IPrincipal user)
        {

            return user.Identity.IsAuthenticated ? user.Identity.Name.ToLower() : "";

        }


    }
}

这是我在本地得到的令牌结果。

{
  "aud": "api://client-id","iss": "https://sts.windows.net/tenant-id/","iat":,"nbf":,"exp":,"acr": "1","aio": "","amr": [
    ""
  ],"appid": "client-id","appidacr": "0","family_name": "","given_name": "","in_corp": "true","ipaddr": "","name": "","oid": "","onprem_sid": "","rh": "","scp": "api_consume","sub": "","tid": "tenant-id","unique_name": "","upn": "","uti": "","ver": "1.0"
}

这是我将应用发布到 OpenShift 后获得的令牌。

{
  "aud": "client-id","iss": "https://login.microsoftonline.com/tenant-id/v2.0","acct": 0,"auth_time": 
  "ctry": "","email": "","nonce": "","preferred_username": "","sid": "","tenant_ctry": "","tenant_region_scope": "","ver": "2.0","verified_primary_email": [
    ""
  ],"verified_secondary_email": [
    "",""
  ],"xms_tpl": "en"
}

您可以看到我获得的两个令牌具有不同的受众、发行者和版本。可能是我缺少一些小配置,但在此阶段的任何帮助将不胜感激。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)