【问题标题】:How to validate JWT token in other project when you have separate project for Identity using asp.net identity当您使用 asp.net 身份有单独的身份项目时如何在其他项目中验证 JWT 令牌
【发布时间】:2019-12-12 17:43:38
【问题描述】:

我有两个正在解决的项目。一种是使用 asp.net 身份生成 JWT 令牌进行身份管理。在第二个项目中,有一些 API 是安全的,并且可以验证身份项目生成的令牌,但令牌验证不起作用。

在邮递员中调用 api store route 时出现此错误。我在授权标头中传递令牌。

处理请求时发生未处理的异常。 HttpRequestException:响应状态码不表示成功: 404(未找到)。 System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()

IOException: IDX20804: Unable to retrieve document from: '[PII is 隐]'。 Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(字符串 地址,CancellationToken 取消)

InvalidOperationException:IDX20803:无法获取配置 来自:'[PII 被隐藏]'。 Microsoft.IdentityModel.Protocols.ConfigurationManager.GetConfigurationAsync(CancellationToken 取消)

堆栈查询 Cookie 标头 HttpRequestException:响应状态代码 不表示成功:404(未找到)。 System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(字符串 地址,CancellationToken 取消)

显示原始异常详细信息 System.Net.Http.HttpRequestException: 响应状态码不表示成功:404(未找到)。在 System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() 在 Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(字符串 地址,CancellationToken 取消)IOException:IDX20804:无法 从以下位置检索文档:“[PII 已隐藏]”。 Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(字符串 地址,CancellationToken 取消) Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(字符串 地址,IDocumentRetriever 检索器,CancellationToken 取消) Microsoft.IdentityModel.Protocols.ConfigurationManager.GetConfigurationAsync(CancellationToken 取消)

显示原始异常详细信息 System.IO.IOException: IDX20804: Unable to 从以下位置检索文档:“[PII 已隐藏]”。 ---> System.Net.Http.HttpRequestException:响应状态代码不 表示成功:404(未找到)。在 System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() 在 Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(字符串 address, CancellationToken cancel) --- 内部异常堆栈结束 跟踪---在 Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(字符串 地址,CancellationToken 取消)在 Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(字符串 地址,IDocumentRetriever 检索器,CancellationToken 取消)在 Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken 取消)InvalidOperationException:IDX20803:无法获取 配置来自:'[PII 被隐藏]'。 Microsoft.IdentityModel.Protocols.ConfigurationManager.GetConfigurationAsync(CancellationToken 取消) Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync() Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync() Microsoft.AspNetCore.Authentication.AuthenticationHandler.AuthenticateAsync() Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext 上下文,字符串方案) Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext 语境) Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext 上下文)

显示原始异常详细信息 System.InvalidOperationException: IDX20803: 无法从以下位置获取配置:“[PII 已隐藏]”。 ---> System.IO.IOException:IDX20804:无法从以下位置检索文档: '[PII 被隐藏]'。 ---> System.Net.Http.HttpRequestException:响应 状态码不表示成功:404(未找到)。在 System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() 在 Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(字符串 address, CancellationToken cancel) --- 内部异常堆栈结束 跟踪---在 Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(字符串 地址,CancellationToken 取消)在 Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(字符串 地址,IDocumentRetriever 检索器,CancellationToken 取消)在 Microsoft.IdentityModel.Protocols.ConfigurationManager1.GetConfigurationAsync(CancellationToken cancel) --- End of inner exception stack trace --- at Microsoft.IdentityModel.Protocols.ConfigurationManager1.GetConfigurationAsync(CancellationToken 取消)在 Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync() 在 Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync() 在 Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync() 在 Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext 上下文,字符串方案)在 Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext 上下文)在 Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext 上下文)

这是我在 Identity 项目中的 Startup.cs 类。

using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Cosmonaut;
using Cosmonaut.Extensions.Microsoft.DependencyInjection;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Triverse.Identity.Models;
using Triverse.Identity.Services;
using IdentityRole = Microsoft.AspNetCore.Identity.DocumentDB.IdentityRole;

namespace Triverse.Identity
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            var endPointUri = Configuration.GetValue<string>("AppSettings:EndpointUri");
            var primaryKey = Configuration.GetValue<string>("AppSettings:PrimaryKey");
            var databaseId = Configuration.GetValue<string>("AppSettings:DatabaseId");
            var collectionId = Configuration.GetValue<string>("AppSettings:CollectionId");

            var client = new DocumentClient(new Uri(endPointUri), primaryKey);
            services.AddSingleton<IDocumentClient>(client);

            // make sure the database exists!
            var db = client.CreateDatabaseQuery().Where(d => d.Id == databaseId).AsEnumerable().FirstOrDefault()
                     ?? client.CreateDatabaseAsync(new Database { Id = databaseId }).Result;

            var databaseLink = db.SelfLink;

            services.AddIdentityWithDocumentDBStores<ApplicationUser, IdentityRole>(
                dbOptions =>
                {
                    dbOptions.DocumentUrl = endPointUri;
                    dbOptions.DocumentKey = primaryKey;
                    dbOptions.DatabaseId = databaseId;
                    dbOptions.CollectionId = collectionId;
                },
                identityOptions =>
                {
                    identityOptions.User.RequireUniqueEmail = true;
                });

            var cosmosSettings = new CosmosStoreSettings(databaseId, endPointUri, primaryKey);
            services.AddCosmosStore<ApplicationUser>(cosmosSettings);

            services.AddScoped<IAccountRepository, AccountRepository>();
            services.AddTransient<ITokenService, TokenService>();

            services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(cfg =>
                {
                    cfg.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidIssuer = Configuration["Tokens:Issuer"],
                        ValidAudience = Configuration["Tokens:Audience"],
                        IssuerSigningKey =
                            new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"])),
                        ValidateLifetime = true
                    };

                    cfg.Events = new JwtBearerEvents
                    {
                        OnAuthenticationFailed = context =>
                        {
                            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                            {
                                context.Response.Headers.Add("Token-Expired", "true");
                                context.Response.Headers.Add("access-control-expose-headers", "Token-Expired");
                            }

                            return Task.CompletedTask;
                        }
                    };
                });
        }

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

            app.UseAuthentication();

            app.UseMvc();
        }
    }
}

这是我在 Identity 项目中的 appsettings.json。

"Tokens": {
    "Key": "4343@!#ewewq",
    "Issuer": "http://localhost:44376/",
    "Audience": "http://localhost:44385/",
    "ExpiryMinutes": "55",
    "ValidateLifetime": true
  }

这是我在 Store 项目中的 Startup.cs 类。

using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Cosmonaut;
using Cosmonaut.Extensions.Microsoft.DependencyInjection;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Store.API.Services;

namespace Store.API
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            var endPointUri = Configuration.GetValue<string>("AppSettings:EndpointUri");
            var primaryKey = Configuration.GetValue<string>("AppSettings:PrimaryKey");
            var databaseId = Configuration.GetValue<string>("AppSettings:DatabaseId");

            var client = new DocumentClient(new Uri(endPointUri), primaryKey);
            services.AddSingleton<IDocumentClient>(client);

            var db = client.CreateDatabaseQuery().Where(d => d.Id == databaseId).AsEnumerable().FirstOrDefault()
                     ?? client.CreateDatabaseAsync(new Database {Id = databaseId}).Result;

            var databaseLink = db.SelfLink;

            var cosmosSettings = new CosmosStoreSettings(databaseId, endPointUri, primaryKey);
            services.AddCosmosStore<Models.Store>(cosmosSettings);

            services.AddScoped<IStoreRepository, StoreRepository>();

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

            }).AddJwtBearer(options =>
            {
                options.Authority = "http://localhost:44376/";
                options.RequireHttpsMetadata = false;
                options.Audience = "http://localhost:44385/";
            });
        }

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

            app.UseAuthentication();

            app.UseMvc();
        }
    }
}

这是我在 Store 项目中的控制器方法。

[HttpGet]
[Authorize]
public async Task<IActionResult> GetStores()
{
  var stores = new
  {
     Id = 1,
     Name = "T-Shirt",
     Price = "120.00"
  };

  return Ok(stores);
}

这是我从 Identity 项目生成的 JWT 令牌。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzaGFAZG9tYWluLmNvbSIsImp0aSI6IjFjYjNkNjA2LWI4MGQtNGNlZC1hMWFjLThlYmUzNzc1ZGViOSIsIlVuaXF1ZUlkIjoiZjU1ZTM2MWQtYjFkYy00MDg4LTlmYjQtMDg3ZTI4OTFjNWI1IiwidW5pcXVlX25hbWUiOiJzaGFAZG9tYWluLmNvbSIsImZpcnN0TmFtZSI6IlNoYXduIiwibmJmIjoxNTY0OTkwMjA1LCJleHAiOjE1NjQ5OTM1MDUsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDQzNzYvIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo0NDM4NS8ifQ.ZBK8Fi14QUc9ObZx7ojg7LPcl8Qs2vrQyhZi7Dbk4Gg

【问题讨论】:

  • 您的“Store”API 正在尝试内部调用 http://localhost:44376/.well-known/openid-configuration,但调用失败。您可以使用此 IdentityModelEventSource.ShowPII = true; 启用 PII 日志 - 将其添加到您的启动中(将其删除以用于生产)。
  • 我该如何解决这个问题?我正在使用 asp.net 身份。
  • 您是在本地托管吗?首先启用 PII,然后您必须验证该 URL 至少可以在本地访问。
  • 我已启用 IdentityModelEventSource.ShowPII = true;这在启动课上。 URL 仍然无法访问。我没有使用身份服务器,我使用的是 asp.net 身份。
  • 那么我的代码到底有什么问题?

标签: c# asp.net-core .net-core asp.net-identity jwt-auth


【解决方案1】:

Asp.NET Identity 本身并不适用于这个用例。您可能希望看起来像 Identity Server,它扩展了 Identity 的功能。它允许您以多种方式验证令牌,

  1. 它有一个用于验证令牌的端点
  2. 它公开了一个众所周知的配置端点,该端点提供(以及许多其他东西)用于签署令牌的 RSA 证书的公钥。因此,如果您愿意,您可以自己验证签名。

【讨论】:

  • Asp.Net Core 3 中是否默认支持 Identity Server?
  • Identity Server 4 是一个可以添加到 .net 核心的组件。我相信它已经在使用 3.0 预览版,但我自己没有尝试过。你可以在这里查看,github.com/IdentityServer/IdentityServer4
  • IS 4 的替代品现在开始收费了吗?或者你知道 net 6 身份现在是否支持这个?
猜你喜欢
  • 2020-11-01
  • 2019-11-24
  • 2017-05-24
  • 2021-12-17
  • 2019-09-03
  • 2020-01-23
  • 1970-01-01
  • 2018-03-10
  • 1970-01-01
相关资源
最近更新 更多