ASP.NET Core 5.0 JWT 认证抛出 401 代码
ASP.NET Core 5.0 JWT authentication is throws 401 code
我有一个 ASP.NET Core 5.0 API 使用 JWT 身份验证。
现在我想让它做的就是读取按钮中的令牌
@Html.ActionLink("Test","Oper","Home")
它是 [Authorize]
header 并根据我的标准验证它们。我不知道错过了什么,但它总是返回 HTTP 401 代码。
测试添加此代码
app.UseCors(x => x.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins("https://localhost:4200"));
错误:
System.InvalidOperationException: The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the CORS policy by listing individual origins if credentials needs to be supported.
The connection to the terminal's pty host process is unresponsive, the terminals may stop working
这不是一个 Angular 项目。
这是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.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.FileProviders;
using System.IO;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace JWTtokenMVC
{
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.AddControllersWithViews();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials().Build());
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType ="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
RoleClaimType ="http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])
)
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// 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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "Test_modules")),
RequestPath = "/" + "Test_modules"
});
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors(x => x.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins("https://localhost:4200"));
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
这是HomeController.cs
- 登录获取Jwt Token OK:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using JWTtokenMVC.Models;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.Extensions.Configuration;
using JWTtokenMVC.Models.Test;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace JWTtokenMVC.Controllers
{
public class HomeController : Controller
{
private IConfiguration _config;
public HomeController(IConfiguration config)
{
_config = config;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
private string GenerateJSONWebToken(UserPaul userinfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,userinfo.Username),
new Claim(JwtRegisteredClaimNames.Email,userinfo.Email),
new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()),
};
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(10),
signingCredentials: credentials
);
var encodetoken = new JwtSecurityTokenHandler().WriteToken(token);
var cookieOptions = new CookieOptions();
//cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(12);//you can set this to a suitable timeframe for your situation
cookieOptions.HttpOnly = true;
cookieOptions.Expires = DateTime.Now.AddMinutes(1);
//cookieOptions.Domain = Request.Host.Value;
cookieOptions.Path = "/";
Response.Cookies.Append("jwt", encodetoken, cookieOptions);
return encodetoken;
}
[HttpPost]
public IActionResult Login()
{
string AccountNumber="TestUser";
JWTtokenMVC.Models.TestContext userQuery = new JWTtokenMVC.Models.TestContext();
var query = userQuery.Testxxxx.Where(N => N.UserId ==AccountNumber).FirstOrDefault();
IActionResult response = Unauthorized();
if (query != null)
{
var tokenStr = GenerateJSONWebToken(query);
response = Ok(new { token = tokenStr });
}
return response;
}
[Authorize]
[HttpGet("Home/Oper")]
public IActionResult Oper()
{
var authenticationCookieName = "jwt";
var cookie = HttpContext.Request.Cookies[authenticationCookieName];
List<Test_SHOW> sHOWs = new List<Test_SHOW>();
JWTtokenMVC.Models.Test.TestContext userQuery= new JWTtokenMVC.Models.Test.TestContext();
var query = userQuery.Test.Select(T => new Test_SHOW
{
number= T.number,
name= T.name,
mail= T.mail
}).OrderBy(o => o.Iid);
sHOWs.AddRange(query);
return View("Views/Home/Oper.cshtml", sHOWs);
}
}
}
这是Test.cshtml
:
@{
ViewBag.Title = "Home Page";
}
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}
<input type="hidden" id="RequestVerificationToken"
name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">
<form method="post" asp-antiforgery="false">
<!--form -->
<div>
<span style="color:red">@ViewBag.Msg</span>
</div>
<div class="col-md-4 select-outline">
</div>
<button type="button" class="btn btn-light">@Html.ActionLink("Test","Oper","Home")</button>
<button type="button" class="btn btn-light">@Html.ActionLink("TestLogin","Login","Home")</button>
</form>
最后是 Oper.cshtml
:
@using JWTtokenMVC.Models.Test
@model List<Test_SHOW>
@{
ViewBag.Title = "test";
}
<h2>Test List</h2>
<table class="table table-hover">
<tr>
<th>
number
</th>
<th>
name
</th>
<th>
mail
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.number)
</td>
<td>
<span class='text-danger'>@Html.DisplayFor(modelItem => item.name)</span>
</td>
<td>
<span class='text-danger'>@Html.DisplayFor(modelItem => item.mail)</span>
</td>
</tr>
}
</table>
这是我的 appsettings.json
文件:
{
"Logging": {
"LogLevel": {
"Default": "TestInformation",
"Microsoft": "TestWarning",
"Microsoft.Hosting.Lifetime": "TestInformation"
}
},
"AllowedHosts": "*",
"Jwt": {
"Key": "TestProdigy",
"Issuer": "Test.mail.com"
}
}
正如@Yinkiu 提到的。你必须使用 app.UseCors("CorsPolicy")
因为它不是你提到的 angular 项目 already.Install NuGet 包
Microsoft.AspNetCore.Cors
我已经回答了你的一个相关问题。这是另一个 process.You 实际上不能同时使用 AllowAnyOrigin()
和 AllowCredentials()
time.But 如果你想 AllowCredentials()
和 AllowAnyOrigin()
一起使用这个 SetIsOriginAllowed(Func<string,bool> predicate)
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
);
options.AddPolicy("signalr",
builder => builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.SetIsOriginAllowed(hostName => true));
});
@Patriom Sarkar 的回答解决了您的 CORS issues/Error
关于您的 401 未经授权的响应
这些可能与 CORS 无关。
您在这里遇到的问题是您已将 JwtBearer JSON Web 令牌配置为出现在对 Authorize
端点的请求中。默认情况下,它将使用您的授权请求中存在的 Bearer 令牌 header.
这意味着,为了 navigate/call Oper()
,您需要确保“Authorization: Bearer {token}”具有有效令牌(作为请求 header) .
目前,在您的 Login
处理中,您正在生成令牌并执行 Set-Cookie
以便 user-agent/client 使用令牌作为值创建 'jwt' cookie ;但是,user-agent 不会自动将该 jwt cookie 添加到后续请求的 header 中作为授权:不记名令牌。因此 Authorize
属性端点上的 JwtBearer
配置将无效。
同样值得注意的是,在 Xhr 或 Fetch 操作中设置 headers 在 SPA 框架中是支持和正常的(包括存储的 Cookie jwt_tokens)。但是,您不是在此处执行 Xhr 或 Fetch 请求,而是在执行 html 表单 post workflow/mechanic - 导航 pages/views。在这方面,设置授权 Header client-side 是 (AFAIK) 不可能的。
要在此处支持 page/view 导航,您需要在 server-side 上实施解决方案,使用传递的 jwt
cookie 设置令牌。
@Kirk Larkin
在 In ASP.NET Core read JWT token from Cookie instead of Headers 中介绍了该解决方案
.AddJwtBearer(options => {
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["jwt"];
return Task.CompletedTask;
}
};
});
另外
context.Token
在此范围内总是 null
或为空。此声明和赋值不会发生任何预处理或 post 处理。如果您打算支持授权 Header 和 Cookie,您应该在这个 OnMessageReceived
分配的委托中实现该条件。
您可以查看 JwtBearerHandler (aspnetcore 5.0) on GitHub 的默认处理方式。
再次感谢@Kirk Larkin 在对链接问题的回复评论中提供此附加信息。
我有一个 ASP.NET Core 5.0 API 使用 JWT 身份验证。
现在我想让它做的就是读取按钮中的令牌
@Html.ActionLink("Test","Oper","Home")
它是 [Authorize]
header 并根据我的标准验证它们。我不知道错过了什么,但它总是返回 HTTP 401 代码。
测试添加此代码
app.UseCors(x => x.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins("https://localhost:4200"));
错误:
System.InvalidOperationException: The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the CORS policy by listing individual origins if credentials needs to be supported.
The connection to the terminal's pty host process is unresponsive, the terminals may stop working
这不是一个 Angular 项目。
这是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.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.FileProviders;
using System.IO;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace JWTtokenMVC
{
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.AddControllersWithViews();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials().Build());
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType ="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
RoleClaimType ="http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])
)
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// 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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "Test_modules")),
RequestPath = "/" + "Test_modules"
});
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors(x => x.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins("https://localhost:4200"));
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
这是HomeController.cs
- 登录获取Jwt Token OK:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using JWTtokenMVC.Models;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.Extensions.Configuration;
using JWTtokenMVC.Models.Test;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace JWTtokenMVC.Controllers
{
public class HomeController : Controller
{
private IConfiguration _config;
public HomeController(IConfiguration config)
{
_config = config;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
private string GenerateJSONWebToken(UserPaul userinfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,userinfo.Username),
new Claim(JwtRegisteredClaimNames.Email,userinfo.Email),
new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()),
};
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(10),
signingCredentials: credentials
);
var encodetoken = new JwtSecurityTokenHandler().WriteToken(token);
var cookieOptions = new CookieOptions();
//cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(12);//you can set this to a suitable timeframe for your situation
cookieOptions.HttpOnly = true;
cookieOptions.Expires = DateTime.Now.AddMinutes(1);
//cookieOptions.Domain = Request.Host.Value;
cookieOptions.Path = "/";
Response.Cookies.Append("jwt", encodetoken, cookieOptions);
return encodetoken;
}
[HttpPost]
public IActionResult Login()
{
string AccountNumber="TestUser";
JWTtokenMVC.Models.TestContext userQuery = new JWTtokenMVC.Models.TestContext();
var query = userQuery.Testxxxx.Where(N => N.UserId ==AccountNumber).FirstOrDefault();
IActionResult response = Unauthorized();
if (query != null)
{
var tokenStr = GenerateJSONWebToken(query);
response = Ok(new { token = tokenStr });
}
return response;
}
[Authorize]
[HttpGet("Home/Oper")]
public IActionResult Oper()
{
var authenticationCookieName = "jwt";
var cookie = HttpContext.Request.Cookies[authenticationCookieName];
List<Test_SHOW> sHOWs = new List<Test_SHOW>();
JWTtokenMVC.Models.Test.TestContext userQuery= new JWTtokenMVC.Models.Test.TestContext();
var query = userQuery.Test.Select(T => new Test_SHOW
{
number= T.number,
name= T.name,
mail= T.mail
}).OrderBy(o => o.Iid);
sHOWs.AddRange(query);
return View("Views/Home/Oper.cshtml", sHOWs);
}
}
}
这是Test.cshtml
:
@{
ViewBag.Title = "Home Page";
}
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}
<input type="hidden" id="RequestVerificationToken"
name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">
<form method="post" asp-antiforgery="false">
<!--form -->
<div>
<span style="color:red">@ViewBag.Msg</span>
</div>
<div class="col-md-4 select-outline">
</div>
<button type="button" class="btn btn-light">@Html.ActionLink("Test","Oper","Home")</button>
<button type="button" class="btn btn-light">@Html.ActionLink("TestLogin","Login","Home")</button>
</form>
最后是 Oper.cshtml
:
@using JWTtokenMVC.Models.Test
@model List<Test_SHOW>
@{
ViewBag.Title = "test";
}
<h2>Test List</h2>
<table class="table table-hover">
<tr>
<th>
number
</th>
<th>
name
</th>
<th>
mail
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.number)
</td>
<td>
<span class='text-danger'>@Html.DisplayFor(modelItem => item.name)</span>
</td>
<td>
<span class='text-danger'>@Html.DisplayFor(modelItem => item.mail)</span>
</td>
</tr>
}
</table>
这是我的 appsettings.json
文件:
{
"Logging": {
"LogLevel": {
"Default": "TestInformation",
"Microsoft": "TestWarning",
"Microsoft.Hosting.Lifetime": "TestInformation"
}
},
"AllowedHosts": "*",
"Jwt": {
"Key": "TestProdigy",
"Issuer": "Test.mail.com"
}
}
正如@Yinkiu 提到的。你必须使用 app.UseCors("CorsPolicy")
因为它不是你提到的 angular 项目 already.Install NuGet 包
Microsoft.AspNetCore.Cors
我已经回答了你的一个相关问题。这是另一个 process.You 实际上不能同时使用 AllowAnyOrigin()
和 AllowCredentials()
time.But 如果你想 AllowCredentials()
和 AllowAnyOrigin()
一起使用这个 SetIsOriginAllowed(Func<string,bool> predicate)
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
);
options.AddPolicy("signalr",
builder => builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.SetIsOriginAllowed(hostName => true));
});
@Patriom Sarkar 的回答解决了您的 CORS issues/Error
关于您的 401 未经授权的响应
这些可能与 CORS 无关。
您在这里遇到的问题是您已将 JwtBearer JSON Web 令牌配置为出现在对 Authorize
端点的请求中。默认情况下,它将使用您的授权请求中存在的 Bearer 令牌 header.
这意味着,为了 navigate/call Oper()
,您需要确保“Authorization: Bearer {token}”具有有效令牌(作为请求 header) .
目前,在您的 Login
处理中,您正在生成令牌并执行 Set-Cookie
以便 user-agent/client 使用令牌作为值创建 'jwt' cookie ;但是,user-agent 不会自动将该 jwt cookie 添加到后续请求的 header 中作为授权:不记名令牌。因此 Authorize
属性端点上的 JwtBearer
配置将无效。
同样值得注意的是,在 Xhr 或 Fetch 操作中设置 headers 在 SPA 框架中是支持和正常的(包括存储的 Cookie jwt_tokens)。但是,您不是在此处执行 Xhr 或 Fetch 请求,而是在执行 html 表单 post workflow/mechanic - 导航 pages/views。在这方面,设置授权 Header client-side 是 (AFAIK) 不可能的。
要在此处支持 page/view 导航,您需要在 server-side 上实施解决方案,使用传递的 jwt
cookie 设置令牌。
@Kirk Larkin
在 In ASP.NET Core read JWT token from Cookie instead of Headers 中介绍了该解决方案 .AddJwtBearer(options => {
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["jwt"];
return Task.CompletedTask;
}
};
});
另外
context.Token
在此范围内总是 null
或为空。此声明和赋值不会发生任何预处理或 post 处理。如果您打算支持授权 Header 和 Cookie,您应该在这个 OnMessageReceived
分配的委托中实现该条件。
您可以查看 JwtBearerHandler (aspnetcore 5.0) on GitHub 的默认处理方式。
再次感谢@Kirk Larkin 在对链接问题的回复评论中提供此附加信息。