ASP.NET 核心 JWT/Windows 身份验证 HTTP 400 错误
ASP.NET Core JWT/Windows Authentication HTTP 400 Error
我有一个 ASP.NET Core 2.1 Web API,目前需要用户输入 username/password 才能获得 JWT 进行授权。我还想添加使用 Windows 身份验证来接收 JWT 的选项。最终,我打算有两个授权控制器,一个用于username/password,另一个用于Windows Auth.
为了对此进行测试,我首先在 IIS Express 中启用了 Windows 身份验证,方法是右键单击我的项目并转到“属性”。
然后,我制作了一个简单的测试控制器,看看我是否可以使用我的 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 身份验证并在其他控制器上实施承载身份验证?
How can I enforce Windows Authentication on some controllers and Bearer Authentication on others?
首先,我不得不说在处理 JWT 和 Windows 身份验证的混合方案时有点奇怪。我的意思是,当未通过 JwtBearer
身份验证的用户尝试访问受 JwtBearer
方案保护的那些 url 资源时,将受到 Windows 身份验证的挑战。
其次,关于您的问题,我们可以 配置 JwtBearer
身份验证以使用未用作 HTTP
header 的自定义令牌(即Authorization: Bearer xxxx_yyyy_zzz
)。例如,通过查询字符串或自定义 header.
发送 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;
}
}
以及受Windows
或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" };
}
}
测试用例:
使用 Windows 身份验证进行测试
访问/api/ping
时的截图
使用 Jwt Bearer 认证测试
首先,在服务器端生成一个JwtToken
:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Iml0bWludXMiLCJuYmYiOjE1NDIzNDMxNzMsImV4cCI6MTU0MjQxNTE3MywiaWF0IjoxNTQyMzQzMTczLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM4NSIsImF1ZCI6IndlYmNsaWVudCJ9.iMnq8UBRQforNeRBehrULAScD8D2-ta4nmdQt1rTZ3s
然后使用 bearer=xxx_yyy_zzz
的查询字符串向 /api/foo
的端点发送 HTTP GET 请求:
GET https://localhost:44385/api/foo?bearer=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Iml0bWludXMiLCJuYmYiOjE1NDIzNDMxNzMsImV4cCI6MTU0MjQxNTE3MywiaWF0IjoxNTQyMzQzMTczLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM4NSIsImF1ZCI6IndlYmNsaWVudCJ9.iMnq8UBRQforNeRBehrULAScD8D2-ta4nmdQt1rTZ3s HTTP/1.1
它将 return [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"]
我有一个 ASP.NET Core 2.1 Web API,目前需要用户输入 username/password 才能获得 JWT 进行授权。我还想添加使用 Windows 身份验证来接收 JWT 的选项。最终,我打算有两个授权控制器,一个用于username/password,另一个用于Windows Auth.
为了对此进行测试,我首先在 IIS Express 中启用了 Windows 身份验证,方法是右键单击我的项目并转到“属性”。
然后,我制作了一个简单的测试控制器,看看我是否可以使用我的 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 身份验证并在其他控制器上实施承载身份验证?
How can I enforce Windows Authentication on some controllers and Bearer Authentication on others?
首先,我不得不说在处理 JWT 和 Windows 身份验证的混合方案时有点奇怪。我的意思是,当未通过 JwtBearer
身份验证的用户尝试访问受 JwtBearer
方案保护的那些 url 资源时,将受到 Windows 身份验证的挑战。
其次,关于您的问题,我们可以 配置 JwtBearer
身份验证以使用未用作 HTTP
header 的自定义令牌(即Authorization: Bearer xxxx_yyyy_zzz
)。例如,通过查询字符串或自定义 header.
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;
}
}
以及受Windows
或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" };
}
}
测试用例:
使用 Windows 身份验证进行测试
访问/api/ping
使用 Jwt Bearer 认证测试
首先,在服务器端生成一个JwtToken
:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Iml0bWludXMiLCJuYmYiOjE1NDIzNDMxNzMsImV4cCI6MTU0MjQxNTE3MywiaWF0IjoxNTQyMzQzMTczLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM4NSIsImF1ZCI6IndlYmNsaWVudCJ9.iMnq8UBRQforNeRBehrULAScD8D2-ta4nmdQt1rTZ3s
然后使用 bearer=xxx_yyy_zzz
的查询字符串向 /api/foo
的端点发送 HTTP GET 请求:
GET https://localhost:44385/api/foo?bearer=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Iml0bWludXMiLCJuYmYiOjE1NDIzNDMxNzMsImV4cCI6MTU0MjQxNTE3MywiaWF0IjoxNTQyMzQzMTczLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM4NSIsImF1ZCI6IndlYmNsaWVudCJ9.iMnq8UBRQforNeRBehrULAScD8D2-ta4nmdQt1rTZ3s HTTP/1.1
它将 return [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"]