ASP.NET Core JWT/Windows Authentication HTTP 400 Error

落花浮王杯 提交于 2021-02-18 18:52:09

问题


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?


回答1:


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"]


来源:https://stackoverflow.com/questions/53327905/asp-net-core-jwt-windows-authentication-http-400-error

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!