ASP.NET Core JWT / Windows身份验证HTTP 400错误

时间:2018-11-15 21:07:11

标签: c# authentication asp.net-core jwt asp.net-core-2.0

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

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

Hey look, windows auth!

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

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

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

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

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

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

HTTP Error 400. The request is badly formed.

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

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

1 个答案:

答案 0 :(得分:1)

  

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

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

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

详细信息:

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

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;
                    }
                },
            };
        });

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

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

MyPolicyRequirementHandler是:

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;
    }
}

两个受WindowsJwtBearer身份验证保护的控制器:

[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" };
    }
}

测试用例:

使用Windows身份验证进行测试

访问/api/ping

时的屏幕截图

enter image description here

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

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

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Iml0bWludXMiLCJuYmYiOjE1NDIzNDMxNzMsImV4cCI6MTU0MjQxNTE3MywiaWF0IjoxNTQyMzQzMTczLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM4NSIsImF1ZCI6IndlYmNsaWVudCJ9.iMnq8UBRQforNeRBehrULAScD8D2-ta4nmdQt1rTZ3s

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

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

它将按预期返回[foo]

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