每当我发送包含 Bearer 令牌的请求时,ASP.Net Core API 总是返回 401 未授权

Posted

技术标签:

【中文标题】每当我发送包含 Bearer 令牌的请求时,ASP.Net Core API 总是返回 401 未授权【英文标题】:ASP.Net Core API always returns 401 but Bearer token is included 【发布时间】:2019-10-04 18:02:41 【问题描述】:

我有一个 ASP .NET Core Web api,我生成了一个 JWT 令牌用于授权目的,但是每当我向 Postman 发出带有 Bearer 令牌标头的请求时,我都会得到 401 Unauthorized。当我从使用 API 的前端尝试时也是如此。当我删除授权一切正常

尝试将我的标头中的 Authorize 更改为 //[Authorize(AuthenticationSchemes = "Bearer")] 也转到 jwt.io 以确保 JWT 令牌是有效的。

//function where I generate JWT
  public   User AuthenticateAdmin(string username, string password)
        
            var user =  _context.User.FirstOrDefault(x => x.UserName == username && x.Password == password);

            //return null if user is not found 
            if  (user == null) return null;

            //authentication successful so generate jwt token
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
            var tokenDescriptor = new SecurityTokenDescriptor
            
                Subject= new ClaimsIdentity(new Claim[]
                
                    new Claim(ClaimTypes.Name, user.Id.ToString()),
                    new Claim(ClaimTypes.Role,user.Role)
                ),
                Expires=DateTime.UtcNow.AddDays(7),
                SigningCredentials= new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            ;
            var token = tokenHandler.CreateToken(tokenDescriptor);
            user.Token = tokenHandler.WriteToken(token);

            user.Password = null;
            return user;
        
//my startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using TheBackend.Models;
using TheBackend.Helpers;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Text;
using TheBackend.Services;
using Microsoft.AspNetCore.Identity.UI.Services;
using Newtonsoft.Json.Serialization;
using Microsoft.AspNetCore.Authorization;

namespace TheBackend

    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.
        public void ConfigureServices(IServiceCollection services)
        
            services.Configure<AuthMessengerOptions>(Configuration);
            var connection = @"Host=localhost;Database=PayArenaMock;Username=postgres;Password=tim";
            services.AddDbContext<PayArenaMockContext>(options => options.UseNpgsql(connection));
            services.AddTransient<IEmailSender, EmailSender>();

            //services.AddAuthorization(auth =>
            //
            //    auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
            //        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
            //        .RequireAuthenticatedUser().Build());
            //);
            services.AddCors();
            //services.AddMvcCore()
            // .AddAuthorization() // Note - this is on the IMvcBuilder, not the service collection
            // .AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
            //services.AddMvcCore().AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            //configure strongly typed settings objects
            var appSettingsSection = Configuration.GetSection("AppSettings");
            services.Configure<AppSettings>(appSettingsSection);
            //configure JWT authentication
            var appSettings = appSettingsSection.Get<AppSettings>();
            var key = Encoding.ASCII.GetBytes(appSettings.Secret);
            services.AddAuthentication(x =>
            
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            )
            .AddJwtBearer(x=>
            
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                
                    ValidateIssuerSigningKey=true,
                    IssuerSigningKey= new  SymmetricSecurityKey(key),
                    ValidateIssuer=false,
                    ValidateAudience=false
                ;
            );

            services.AddScoped<IUserService, UserService>();
        

        // 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();
            
            else
            
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            
            app.UseCors(x => x
             .AllowAnyOrigin()
             .AllowAnyMethod()
             .AllowAnyHeader());
            app.UseAuthentication();
            app.UseHttpsRedirection();
            app.UseMvc();
        
    


//controller
//[Authorize(AuthenticationSchemes = "Bearer")]
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class BusinessListingsController : ControllerBase
    
        private readonly PayArenaMockContext _context;

        public BusinessListingsController(PayArenaMockContext context)
        
            _context = context;
        

        // GET: api/BusinessListings
        [HttpGet]
        //[AllowAnonymous]
        //[Authorize(Roles = Role.Admin)]
        public async Task<ActionResult<IEnumerable<BusinessListing>>> GetBusinessListing()
        

            //var businesslisting = _context.BusinessListing.Include(b => b.CategoryNameNav);
            var businesslisting = await _context.BusinessListing.ToListAsync()
           ;
            return Ok( businesslisting);
        

【问题讨论】:

控制器顶部是否有 [Authorize] 包含令牌生成方法? 您是否设置了 > [Authorization] 标头,例如“Bearer your token”? @Jamil 我在反应中做了,在 Postman 中做同样的事情,它不起作用 检查您是否选择了正确的方法(GET 或 POST)、url、其他标题和正文。在邮递员中也是如此,您选择授权标头并输入“Bearer”+您的令牌,并且不要忘记选中复选框。 您是否将app.UseAuthorization(); 添加到您的创业公司? 【参考方案1】:

它终于起作用了,不是 100% 确定为什么,但我在我的 react 应用程序中做了headers: Authorization: "Bearer" + " " + Bearer 。我在 Postman 中做了同样的事情,但它不起作用。

【讨论】:

【参考方案2】:

从 HTTP 切换到 HTTPS 后,我遇到了同样的问题。它在使用 HTTP 的 POSTMAN 中运行良好,但是当我切换到 HTTPS 时,它开始返回 401 未经授权的

我一添加问题就解决了

        app.UseAuthentication();

里面

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    
    

public class Startup


【讨论】:

【参考方案3】:

我遇到了同样的问题,这就是我在邮递员上所做的,在标题部分使用批量编辑。

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbmllbEBtZ2ljLmNvbToxMTExMTE6IiwibmJmIjoxNTcwNjIxMDE5LCJleHAiOjE1NzMyOTk0MTksImlhdCI6MTU3MDYyMTAxOX0.cTjhhHqf75VN0RwQvly6nppeNUkKbzQ5_ZVPFyruuKPBQ

还记得在端点顶部包含身份验证过滤器,在您的情况下为 [Authorize]。

【讨论】:

【参考方案4】:

我也有同样的问题,但是在升级之后

app.UseAuthentication();

到前行

app.UseAuthorization();

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

    ..
    app.UseAuthentication();
    ..
    app.UseAuthorization();
    ...

成功了。

【讨论】:

这正是我的问题! 感谢您的回复,它也解决了我的问题! 或者在我的情况下,注意不要把 app.UseAuthorization 两次。大声笑感谢您引起我的注意!【参考方案5】:

我在使用 dotnet core 3.1 时遇到了这个问题,我正在翻转每个开关以试图让它工作。最终让它运行的是特莱尔的回答。在app.UseAuthorization() 之前执行app.UseAuthentication()。为了详细说明tletle的答案,下面是相关代码。

Startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

    if (env.IsDevelopment())
    
        app.UseDeveloperExceptionPage();
    
    else
    
        // ...
        app.UseHsts();
    
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication(); // this one first
    app.UseAuthorization(); 
    app.UseEndpoints(endpoints =>
    
        endpoints.MapControllers();
    );

我的身份验证中间件位于我编写的扩展方法中,该方法是从Startup.cs 中的ConfigureServices() 方法调用的:

        public static void ConfigureAuthentication(this IServiceCollection services, IConfiguration configuration)
        
            string issuer = configuration.GetValue<string>("Jwt:Issuer");
            string signingKey = configuration.GetValue<string>("Jwt:Key");
            byte[] signingKeyBytes = System.Text.Encoding.UTF8.GetBytes(signingKey);

            services.AddAuthentication(opt=>
            
                opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            )
            .AddJwtBearer(options=>
            
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;
                options.TokenValidationParameters = new TokenValidationParameters()
                
                    ValidateIssuer = true,
                    ValidIssuer = issuer,
                    ValidateAudience = true,
                    ValidAudience = issuer,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ClockSkew = System.TimeSpan.Zero,
                    IssuerSigningKey = new SymmetricSecurityKey(signingKeyBytes)
                ;
            );
        

并且令牌是使用此扩展方法生成的:

public static string GenerateApiUserToken(this ApiUser user, IConfiguration configuration)

    string signingKey = configuration.GetValue<string>("Jwt:Key");
    string issuer = configuration.GetValue<string>("Jwt:Issuer");
    int hours = configuration.GetValue<int>("Jwt:HoursValid");
    System.DateTime expireDateTime = System.DateTime.UtcNow.AddHours(hours);

    byte[] signingKeyBytes = System.Text.Encoding.UTF8.GetBytes(signingKey);
    SymmetricSecurityKey secKey = new SymmetricSecurityKey(signingKeyBytes);
    SigningCredentials creds = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256);

    var authClaims = new List<Claim>
    
        new Claim(ClaimTypes.Name, user.UserName),
        new Claim(ClaimTypes.Role, user.RoleName)
    ;

    JwtSecurityToken token = new JwtSecurityToken(
        issuer:issuer,
        audience: issuer,
        claims: authClaims,
        expires: System.DateTime.UtcNow.AddHours(hours),
        signingCredentials:creds
    );
    JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
    string writtenToken = handler.WriteToken(token);

    return writtenToken;

我的控制器类:

[Authorize]
[ApiController]
[Microsoft.AspNetCore.Mvc.Produces("application/json")]
[Microsoft.AspNetCore.Mvc.Route("/[controller]/values", Name="MyController")]
public class MyController : Microsoft.AspNetCore.Mvc.Controller

如果[Authorize]标签在控制器上,你应该删除任何在成员方法上的;我在我正在测试的方法上留下了一个,直到我删除它才能修复。

【讨论】:

啊...花了一天半的时间,当我完成您的修复时,它起作用了。有时我觉得我花更多的时间来解决像这样的愚蠢问题,而不是实际编程。 我分享你的痛苦,对我来说也差不多哈哈。很高兴听到我正在帮助其他开发者 - 谢谢!

以上是关于每当我发送包含 Bearer 令牌的请求时,ASP.Net Core API 总是返回 401 未授权的主要内容,如果未能解决你的问题,请参考以下文章

JWT中令牌之前的不记名

如何正确使用 Bearer 代币?

从 ASP.NET Core 中的不同 HTTP 标头读取 JWT 令牌

我可以在 Authorization 标头中同时使用 Basic 和 Bearer 吗?

Asp.Net Web API JWT 身份验证

使用NodeJS验证包含“Bearer:”的JWT标记字符串