不记名令牌出现 401 错误 - asp.net core 3.1

Posted

技术标签:

【中文标题】不记名令牌出现 401 错误 - asp.net core 3.1【英文标题】:401 error with bearer token - asp.net core 3.1 【发布时间】:2020-11-16 18:52:29 【问题描述】:

我无法使用由 Asp.net Core 生成的令牌授权访问受保护的方法。

Startup.cs

    public void ConfigureServices(IServiceCollection services)
    
        services.Configure<ApplicationSettings>
            (Configuration.GetSection("ApplicationSettings"));
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddDefaultIdentity<AppUser>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();
        services.AddControllersWithViews();
        services.AddScoped<IRepository<Course>, GenericRepository<Course>>();
        services.AddRazorPages();
        // CORS - Configuration
        services.AddCors(opt =>
            opt.AddPolicy("CorsPolicy", policy =>
                         policy.AllowAnyHeader()
                               .AllowAnyMethod()
                               .AllowAnyOrigin()));
        services.AddSwaggerGen(c =>
        
            c.SwaggerDoc("v1", new OpenApiInfo
            
                Version = "v1",
                Title = "LMS Basic API",
                Description = "LMS Diamond for api",

            );
            //c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            //
            //    Name = "Authorization",
            //    Type = SecuritySchemeType.ApiKey,
            //    Scheme = "Bearer",
            //    BearerFormat = "JWT",
            //    In = ParameterLocation.Header,
            //    Description = "JWT Authorization header using the Bearer scheme."
            //);
            //c.AddSecurityRequirement(new OpenApiSecurityRequirement
            //
            //    
            //          new OpenApiSecurityScheme
            //            
            //                Reference = new OpenApiReference
            //                
            //                    Type = ReferenceType.SecurityScheme,
            //                    Id = "Bearer"
            //                
            //            ,
            //            new string[] 
            //    
            //);
        );

        // JWT Auth
        var key = Encoding.UTF8.GetBytes(Configuration["ApplicationSettings:JWT_Secret"].ToString());
        services.AddAuthentication(x =>
        
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        ).AddJwtBearer(x =>
        
            x.RequireHttpsMetadata = false;
            x.SaveToken = false;
            x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
            
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false,
            ;
        );

    

    // 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();
            app.UseDatabaseErrorPage();
        
        else
        
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        
        app.UseHttpsRedirection();
        //app.UseRewriter(new RewriteOptions()
        //    .AddRedirectToHttpsPermanent());

        app.UseCors("CorsPolicy");

        app.UseSwagger();
        app.UseSwaggerUI(c =>
        
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "LMS Basic API");
            c.RoutePrefix = string.Empty;
        );
        app.UseStaticFiles();

        app.UseRouting();

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

登录操作中的令牌生成器

                    var tokenDescripter = new SecurityTokenDescriptor
                
                    Subject = new System.Security.Claims.ClaimsIdentity(new Claim[] 
                    new Claim("UserID", user.Id.ToString())
                ),
                    Expires = DateTime.UtcNow.AddSeconds(20),
                    SigningCredentials = new SigningCredentials(new
                            SymmetricSecurityKey(Encoding.UTF8.GetBytes(applicationSettings.JWT_Secret)),
                            SecurityAlgorithms.HmacSha256Signature)
                ;
                var tokenHandler = new JwtSecurityTokenHandler();
                var securityToken = tokenHandler.CreateToken(tokenDescripter);
                var token = tokenHandler.WriteToken(securityToken);

动作

       [HttpGet]
    [Authorize(AuthenticationSchemes = "Bearer")]
    [Route("GetUserData")]
    public async Task<IActionResult> GetUserData()
    
        string userId = User.Claims.First(c => c.Type == "UserID").Value;
        var user = await _userManager.FindByIdAsync(userId);
        if (user == null)
        
            return BadRequest();
        
        return Ok(new
        
            user.Id,
            user.FullName,
            user.Email,
        );
    

我可以在 Postman 的 HTTP 标头中添加或不添加自动化,我收到 "Unauthorized Exception - 401"

我已经查看了其他一些 Stack 帖子和 GitHub 帖子,看来我的配置还可以。

如果需要我可以添加配置文件。

提前致谢。

【问题讨论】:

愚蠢的问题,您的令牌将在 20 秒后过期。你确定那不是问题?你能增加那个时间再试一次吗? 是的,改了还是一样的错误 【参考方案1】:

您的令牌声明需要包含ClaimsIdentity.UserIdClaimType 和/或ClaimsIdentity.UserNameClaimType(不确定是哪个,老实说),才能将用户识别为经过身份验证。实现这一点的最简单方法是使用SignInManager.CreateUserPrincipalAsync(),它最终调用UserClaimsPrincipalFactory.GenerateClaimsAsync(),例如我的一个项目中的这个助手:

    public async Task<string> GetToken(ApplicationUser user, TimeSpan? expiresIn)
    
        var principal = await signInManager.CreateUserPrincipalAsync(user);
        if (principal == null)
            return null;

        var signingKey = GetSecurityKey(configuration);

        var token = new JwtSecurityToken(
            expires: DateTime.UtcNow + expiresIn,
            claims: principal.Claims,
            signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
            );

        return new JwtSecurityTokenHandler().WriteToken(token);
    

另一个选项似乎是您可以在配置中指定 IdentityOptions.ClaimsIdentity 来教导 Identity UserID 是它应该用来识别您的用户的声明类型(基于 this code)。

【讨论】:

【参考方案2】:

您需要确保正确发送不记名身份验证

Authorization: Bearer <token>

查看示例 - Add Bearer Token to an API request

【讨论】:

当您使用类型为不记名令牌的授权时,无需在 Postman 中为您的令牌添加“不记名”。您必须手动添加授权标头。

以上是关于不记名令牌出现 401 错误 - asp.net core 3.1的主要内容,如果未能解决你的问题,请参考以下文章

如何诊断尝试在 c# .NET Core 中获取 OAuth2 不记名令牌的 401 错误?

即使在 asp.net core 5.0 中提供了不记名令牌,也会返回 401 [关闭]

JWT不记名令牌授权不起作用asp net core web api

ASP.NET Core API 使用 JWT 不记名令牌进行身份验证

使用不记名令牌授权 ASP.net mvc 操作方法

在 asp.net vnext 上使用不记名令牌身份验证刷新令牌