本文介绍了ASP.NET Core JWT/Windows身份验证HTTP 400错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个ASP.NET Core 2.1 Web API,当前需要用户输入用户名/密码来接收JWT授权.我想添加使用Windows身份验证来接收JWT的选项.最终,我计划有两个授权控制器,一个用于用户名/密码,另一个用于Windows Auth.

要对此进行测试,我首先在IIS express中启用了Windows身份验证,方法是右键单击我的项目,然后转到属性".

然后,我制作了一个简单的测试控制器,以查看是否可以使用Windows凭据进行身份验证.

  [Authorize(AuthenticationSchemes ="Windows")][Route("api/ping")]公共类PingController:控制器{//获取api/values[HttpGet]公共IEnumerable< string>得到(){返回新的字符串[] {"Pong"};}} 

这似乎可行,因为当我在浏览器中导航到该端点时,屏幕按预期显示了 Pong .

但是,当我尝试访问使用承载身份验证方案的其他任何控制器时,都会遇到问题.控制器以如下方式声明其身份验证方案:

  [Authorize(策略="MyPolicy",AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 

每当我向这些控制器之一发出请求时,都会出现以下错误:

  HTTP错误400.请求的格式错误. 

在关闭Windows身份验证的情况下,相同的请求也可以正常工作.

如何在某些控制器上强制执行Windows身份验证,而在其他控制器上强制执行Bearer身份验证?

解决方案

首先,我不得不说在处理JWT和Windows身份验证的混合方案时有些奇怪.我的意思是,当未通过 JwtBearer 进行身份验证的用户尝试访问受 JwtBearer 方案保护的那些url资源时,将受到Windows身份验证的挑战.

第二,关于您的问题,我们可以配置 JwtBearer 身份验证以使用自定义令牌,该令牌不会用作 HTTP 标头(即授权:不记名xxxx_yyyy_zzz ).例如,通过查询字符串或自定义标头发送 JWT 令牌.

详细信息:

配置 JwtBearer 身份验证以从查询字符串读取令牌:

  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {options.TokenValidationParameters =新的TokenValidationParameters{ValidateIssuer = true,ValidateAudience = true,ValidateLifetime = true,ValidateIssuerSigningKey = true,ValidIssuer = Configuration ["Jwt:Issuer"],ValidAudience =配置["Jwt:Audience"],IssuerSigningKey =新的SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration ["Jwt:Key"]))};options.Events = new JwtBearerEvents(){OnMessageReceived =异步(上下文)=> {var bearer = context.HttpContext.Request.Query ["bearer"].FirstOrDefault();if(!String.IsNullOrEmpty(bearer)){context.Token =承载;}},};}); 

出于测试目的,我为您的 MyPolicy 添加了一个虚拟策略处理程序:

  services.AddAuthorization(o => {o.AddPolicy("MyPolicy",p => {p.Requirements.Add(new MyPolicyRequirement());});});services.AddSingleton< IAuthorizationHandler,MyPolicyRequirementHandler>();.services.AddHttpContextAccessor(); 

这里 MyPolicyRequirementHandler 是:

 公共类MyPolicyRequirementHandler:AuthorizationHandler< MyPolicyRequirement>{公共MyPolicyRequirementHandler(){}受保护的重写Task HandleRequirementAsync(AuthorizationHandlerContext上下文,MyPolicyRequirement要求){var user = context.User;如果(user.Identity.IsAuthenticated){context.Succeed(要求);}返回Task.CompletedTask;}} 

以及受 Windows JwtBearer 的身份验证保护的两个控制器:

  [Authorize(AuthenticationSchemes ="Windows")][Route("api/[controller]")][ApiController]公共类PingController:ControllerBase{//获取api/values[HttpGet]公共IEnumerable< string>得到(){返回新的字符串[] {"Pong"};}}[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme,Policy ="MyPolicy")][Route("api/[controller]")][ApiController]公共类FooController:ControllerBase{//获取api/values[HttpGet]公共IEnumerable< string>得到(){返回新的字符串[] {"Bar"};}} 

测试用例:

使用Windows身份验证进行测试

访问/api/ping

时的屏幕截图

使用Jwt Bearer身份验证进行测试

首先,在服务器端生成一个 JwtToken :

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Iml0bWludXMiLCJuYmYiOjE1NDIzNDMxNzMsImV4cCI6MTU0MjQxNTE3MywiaWF0IjoxNTQyMzQzMTczLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM4NSIsImF1ZCI6IndlYmNsaWVudCJ9.iMnq8UBRQforNeRBehrULAScD8D2-ta4nmdQt1rTZ3s

然后使用 bearer = xxx_yyy_zzz 的查询字符串将HTTP GET请求发送到/api/foo 的端点:

  GET的https://本地主机:44385/API/富承载= eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Iml0bWludXMiLCJuYmYiOjE1NDIzNDMxNzMsImV4cCI6MTU0MjQxNTE3MywiaWF0IjoxNTQyMzQzMTczLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM4NSIsImF1ZCI6IndlYmNsaWVudCJ9.iMnq8UBRQforNeRBehrULAScD8D2-ta4nmdQt1rTZ3s HTTP/1.1 

它将按预期返回 [foo] :

  HTTP/1.1 200 OK传输编码:分块内容类型:application/json;字符集= utf-8服务器:红estX源文件:=?UTF-8?X-Powered-by:ASP.NET[酒吧"] 

I have an ASP.NET Core 2.1 Web API which currently requires users to enter a username/password to recieve a JWT for authorization. I want to add the option of using Windows Authentication to recieve a JWT as well. Ultimately, I plan to have two authorization controllers, one for username/password, the other for Windows Auth.

To test this, I first enabled Windows Authentication in IIS express, by right clicking on my project and going to Properties.

Then, I made a simple test controller to see if I could authenticate with my Windows Credentials.

[Authorize(AuthenticationSchemes = "Windows")]
[Route("api/ping")]
public class PingController : Controller
{
    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "Pong" };
    }
}

This seemed to work, as when I navigated to this endpoint in my browser, the screen displayed Pong as expected.

However, I run into problems when I try and access any of my other controllers that use Bearer Authentication Schemes. The controllers declare their Authentication Scheme like so:

[Authorize(Policy = "MyPolicy", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Whenever I make a request to one of these controllers I get the following error:

HTTP Error 400. The request is badly formed.

This same request works just fine with Windows Authentication turned off.

How can I enforce Windows Authentication on some controllers and Bearer Authentication on others?

解决方案

Firstly, I have to say it is somehow weird when dealing with the mixed schemes of JWT and Windows Authentication. I mean when an user who is not authenticated by JwtBearer, tries to access those url resources protected by JwtBearer Scheme, will be challenged by Windows authentication.

Secondly, as for your question, we can configure the JwtBearer authentication to use a custom token which is not used as the HTTP header (i.e. Authorization: Bearer xxxx_yyyy_zzz) . For example, send the JWT token by querystring or a custom header.

How to In Details :

Configure the JwtBearer authentication to read token from querystring:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options=> {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = Configuration["Jwt:Issuer"],
                ValidAudience = Configuration["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
            };
            options.Events = new JwtBearerEvents() {
                OnMessageReceived = async (context) =>{
                    var bearer=context.HttpContext.Request.Query["bearer"].FirstOrDefault();
                    if(!String.IsNullOrEmpty(bearer)){
                        context.Token = bearer;
                    }
                },
            };
        });

For a testing purpose, I add a dummy policy handler for your MyPolicy:

services.AddAuthorization(o => {
    o.AddPolicy("MyPolicy",p => {
        p.Requirements.Add(new MyPolicyRequirement());
    });
});
services.AddSingleton<IAuthorizationHandler,MyPolicyRequirementHandler>();
services.AddHttpContextAccessor();

Here the MyPolicyRequirementHandler is :

public class MyPolicyRequirementHandler : AuthorizationHandler<MyPolicyRequirement>
{

    public MyPolicyRequirementHandler()
    {
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyPolicyRequirement requirement)
    {
        var user= context.User;
        if (user.Identity.IsAuthenticated)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

And the two controllers protected by the Authentication of Windows or JwtBearer :

[Authorize(AuthenticationSchemes = "Windows")]
[Route("api/[controller]")]
[ApiController]
public class PingController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "Pong" };
    }
}

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Policy ="MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class FooController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "Bar" };
    }
}

Test Case :

Test with Windows Authentication

A Screenshot when accessing the /api/ping

Test with Jwt Bearer Authentication

Firstly, generate a JwtToken on server side :

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Iml0bWludXMiLCJuYmYiOjE1NDIzNDMxNzMsImV4cCI6MTU0MjQxNTE3MywiaWF0IjoxNTQyMzQzMTczLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM4NSIsImF1ZCI6IndlYmNsaWVudCJ9.iMnq8UBRQforNeRBehrULAScD8D2-ta4nmdQt1rTZ3s

And then send a HTTP GET request to the endpoint of /api/foo with a querystring of bearer=xxx_yyy_zzz :

GET https://localhost:44385/api/foo?bearer=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Iml0bWludXMiLCJuYmYiOjE1NDIzNDMxNzMsImV4cCI6MTU0MjQxNTE3MywiaWF0IjoxNTQyMzQzMTczLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM4NSIsImF1ZCI6IndlYmNsaWVudCJ9.iMnq8UBRQforNeRBehrULAScD8D2-ta4nmdQt1rTZ3s HTTP/1.1

it will return [foo] as expected :

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcMTZcV2luZG93c0F1dGh0ZW50aWNhdGlvbkFuZEp3dEF1dGhlbnRpY2F0aW9uXEFwcFxhcGlcZm9v?=
X-Powered-By: ASP.NET

["Bar"]

这篇关于ASP.NET Core JWT/Windows身份验证HTTP 400错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-24 00:44