C# 如何验证通过cookies传递的JWT?
ASP.NET Core中的C# 如何验证通过cookies传递的JWT?,c#,asp.net-core,cookies,jwt,C#,Asp.net Core,Cookies,Jwt,ASP.NET Core中的UseJWTBeareAuthentication中间件使得在Authorization头中验证传入的JSON Web令牌变得容易 如何验证通过Cookie传递的JWT,而不是标头?类似于UseCookieAuthentication,但对于只包含JWT的cookie。我建议您查看以下链接 它们将JWT令牌存储在仅http的cookie中,以防止XSS攻击 然后,他们通过在Startup.cs中添加以下代码来验证cookie中的JWT令牌: app.UseCooki
UseJWTBeareAuthentication
中间件使得在Authorization
头中验证传入的JSON Web令牌变得容易
如何验证通过Cookie传递的JWT,而不是标头?类似于
UseCookieAuthentication
,但对于只包含JWT的cookie。我建议您查看以下链接
它们将JWT令牌存储在仅http的cookie中,以防止XSS攻击
然后,他们通过在Startup.cs中添加以下代码来验证cookie中的JWT令牌:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookie",
CookieName = "access_token",
TicketDataFormat = new CustomJwtDataFormat(
SecurityAlgorithms.HmacSha256,
tokenValidationParameters)
});
// use the AuthorizationHeader middleware
app.UseMiddleware<AuthorizationHeader>();
// Add a new middleware validating access tokens.
app.UseOAuthValidation();
其中CustomJwtDataFormat()是此处定义的自定义格式:
public class CustomJwtDataFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string algorithm;
private readonly TokenValidationParameters validationParameters;
public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)
{
this.algorithm = algorithm;
this.validationParameters = validationParameters;
}
public AuthenticationTicket Unprotect(string protectedText)
=> Unprotect(protectedText, null);
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
var handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
try
{
principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null)
{
throw new ArgumentException("Invalid JWT");
}
if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))
{
throw new ArgumentException($"Algorithm must be '{algorithm}'");
}
// Additional custom validation of JWT claims here (if any)
}
catch (SecurityTokenValidationException)
{
return null;
}
catch (ArgumentException)
{
return null;
}
// Validation passed. Return a valid AuthenticationTicket:
return new AuthenticationTicket(principal, new AuthenticationProperties(), "Cookie");
}
// This ISecureDataFormat implementation is decode-only
public string Protect(AuthenticationTicket data)
{
throw new NotImplementedException();
}
public string Protect(AuthenticationTicket data, string purpose)
{
throw new NotImplementedException();
}
}
希望这有帮助
注意:同样需要注意的是,这种方法(仅http cookie中的令牌)将有助于防止XSS攻击,但不能免疫跨站点请求伪造(CSRF)攻击,因此您还必须使用防伪令牌或设置自定义头来防止这些攻击
此外,如果您不进行任何内容清理,攻击者仍然可以运行XSS脚本代表用户发出请求,即使启用了仅http cookie和CRSF保护。但是,攻击者将无法窃取包含令牌的仅http cookie,也无法从第三方网站发出请求
因此,您仍然应该对用户生成的内容(如评论等)执行严格的清理
编辑:这是写在博客帖子链接的评论中的,代码是OP几天前在问了这个问题后自己写的
对于那些对减少XSS暴露的另一种“cookie中的令牌”方法感兴趣的人,他们可以使用oAuth中间件,例如ASP.NET核心中的OpenId Connect服务器
在被调用以将令牌发送回客户端(ApplyTokenResponse())的令牌提供程序的方法中,您可以序列化令牌并将其存储到仅为http的cookie中:
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using Newtonsoft.Json;
namespace Shared.Providers
{
public class AuthenticationProvider : OpenIdConnectServerProvider
{
private readonly IApplicationService _applicationservice;
private readonly IUserService _userService;
public AuthenticationProvider(IUserService userService,
IApplicationService applicationservice)
{
_applicationservice = applicationservice;
_userService = userService;
}
public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
{
if (string.IsNullOrEmpty(context.ClientId))
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Missing credentials: ensure that your credentials were correctly " +
"flowed in the request body or in the authorization header");
return Task.FromResult(0);
}
#region Validate Client
var application = _applicationservice.GetByClientId(context.ClientId);
if (applicationResult == null)
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Application not found in the database: ensure that your client_id is correct");
return Task.FromResult(0);
}
else
{
var application = applicationResult.Data;
if (application.ApplicationType == (int)ApplicationTypes.JavaScript)
{
// Note: the context is marked as skipped instead of validated because the client
// is not trusted (JavaScript applications cannot keep their credentials secret).
context.Skip();
}
else
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Authorization server only handles Javascript application.");
return Task.FromResult(0);
}
}
#endregion Validate Client
return Task.FromResult(0);
}
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
var username = context.Request.Username.ToLowerInvariant();
var user = await _userService.GetUserLoginDtoAsync(
// filter
u => u.UserName == username
);
if (user == null)
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid username or password.");
return;
}
var password = context.Request.Password;
var passWordCheckResult = await _userService.CheckUserPasswordAsync(user, context.Request.Password);
if (!passWordCheckResult)
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid username or password.");
return;
}
var roles = await _userService.GetUserRolesAsync(user);
if (!roles.Any())
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Invalid user configuration.");
return;
}
// add the claims
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
identity.AddClaim(ClaimTypes.NameIdentifier, user.Id, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(ClaimTypes.Name, user.UserName, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
// add the user's roles as claims
foreach (var role in roles)
{
identity.AddClaim(ClaimTypes.Role, role, OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
}
context.Validate(new ClaimsPrincipal(identity));
}
else
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid grant type.");
return;
}
return;
}
public override Task ApplyTokenResponse(ApplyTokenResponseContext context)
{
var token = context.Response.Root;
var stringified = JsonConvert.SerializeObject(token);
// the token will be stored in a cookie on the client
context.HttpContext.Response.Cookies.Append(
"exampleToken",
stringified,
new Microsoft.AspNetCore.Http.CookieOptions()
{
Path = "/",
HttpOnly = true, // to prevent XSS
Secure = false, // set to true in production
Expires = // your token life time
}
);
return base.ApplyTokenResponse(context);
}
}
}
然后,您需要确保每个请求都附加了cookie。您还必须编写一些中间件来拦截cookie并将其设置为标头:
public class AuthorizationHeader
{
private readonly RequestDelegate _next;
public AuthorizationHeader(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var authenticationCookieName = "exampleToken";
var cookie = context.Request.Cookies[authenticationCookieName];
if (cookie != null)
{
if (!context.Request.Path.ToString().ToLower().Contains("/account/logout"))
{
if (!string.IsNullOrEmpty(cookie))
{
var token = JsonConvert.DeserializeObject<AccessToken>(cookie);
if (token != null)
{
var headerValue = "Bearer " + token.access_token;
if (context.Request.Headers.ContainsKey("Authorization"))
{
context.Request.Headers["Authorization"] = headerValue;
}else
{
context.Request.Headers.Append("Authorization", headerValue);
}
}
}
await _next.Invoke(context);
}
else
{
// this is a logout request, clear the cookie by making it expire now
context.Response.Cookies.Append(authenticationCookieName,
"",
new Microsoft.AspNetCore.Http.CookieOptions()
{
Path = "/",
HttpOnly = true,
Secure = false,
Expires = DateTime.UtcNow.AddHours(-1)
});
context.Response.Redirect("/");
return;
}
}
else
{
await _next.Invoke(context);
}
}
}
此解决方案适用于api和mvc应用程序。但是,对于ajax和fetch请求,您必须编写一些自定义中间件,这些中间件不会将用户重定向到登录页面,而是返回401:
public class RedirectHandler
{
private readonly RequestDelegate _next;
public RedirectHandler(RequestDelegate next)
{
_next = next;
}
public bool IsAjaxRequest(HttpContext context)
{
return context.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
public bool IsFetchRequest(HttpContext context)
{
return context.Request.Headers["X-Requested-With"] == "Fetch";
}
public async Task Invoke(HttpContext context)
{
await _next.Invoke(context);
var ajax = IsAjaxRequest(context);
var fetch = IsFetchRequest(context);
if (context.Response.StatusCode == 302 && (ajax || fetch))
{
context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await context.Response.WriteAsync("Unauthorized");
return;
}
}
}
我成功地实现了中间件(基于Darxtar回答):
//TokenController.cs
公共IActionResult Some()
{
...
var tokenString=new JwtSecurityTokenHandler().WriteToken(令牌);
Response.Cookies.Append(
“x”,
标记字符串,
新的CookieOptions()
{
Path=“/”
}
);
返回状态码(200,令牌字符串);
}
//JWTInHeaderMiddleware.cs
公共类JWTINHEADermiddware
{
private readonly RequestDelegate\u next;
公共jwtinheadermiddware(RequestDelegate-next)
{
_下一个=下一个;
}
公共异步任务调用(HttpContext上下文)
{
var name=“x”;
var cookie=context.Request.Cookies[name];
if(cookie!=null)
if(!context.Request.Headers.ContainsKey(“授权”))
context.Request.Headers.Append(“授权”、“承载者”+cookie);
wait_next.Invoke(上下文);
}
}
//Startup.cs
公共无效配置(IApplicationBuilder应用程序,IHostingEnvironment环境)
{
...
app.UseMiddleware();
...
}
好奇:如果你想使用cookies来传递不记名代币,那么使用不记名代币有什么意义?使用承载令牌而不是cookie的全部目的是为了避免诸如XSRF攻击之类的安全问题。如果您在等式中重新引入cookie,您将重新引入其威胁模型。@Pinpoint JWT不是严格意义上的承载令牌;它们可以通过承载头或cookie使用。我使用JWTs进行无状态“会话”,但仍然将它们存储在cookie中,因为浏览器支持很简单。XSS由cookie标志减轻。1。根据定义,JWT是不记名或PoP令牌(在第一种情况下,您不需要证明您是令牌的合法持有人,在第二种情况下,您需要向服务器提供占有证明)。2.恐怕,使用JWT表示“会话”并将其存储在身份验证cookie(它本身就是“会话”)中毫无意义。3.XSS与XSRF无关,它是一个完全不同的威胁。@Pinpoint我正在进行令牌身份验证,并将访问令牌JWT存储在(纯文本)cookie中,而不是HTML5存储中。我意识到XSS!=XSRF,你完全正确。我应该澄清一下:我选择Cookie是为了对XSS提供强大的安全性,这确实意味着我需要处理CSRF问题。TBH,您的场景听起来确实像是令牌和Cookie之间的奇怪混合。如果您真的想使用cookie,那么根本不要使用令牌身份验证,而是直接使用cookie进行身份验证。您必须处理XSRF风险,但这与您试图实现的场景没有什么不同。恕我直言,这并不值得,特别是当你知道这样做并不能真正缓解XSS攻击时:别忘了,如果我不能窃取HttpOnly cookie,当你的JS应用程序中存在XSS漏洞时,没有什么可以阻止我代表用户发送恶意API请求。我不得不问,你有没有查过那篇博文的作者是谁?你说的很有道理,不,我没有检查作者。我将研究一个更客观的解决方案。我已经用oauth2做了一些自定义的身份验证,我会很快编辑它以提供一个替代方案。哈哈,我仍然不确定你是否注意到:你链接了OP他自己的博客文章和代码。这就是我要问的。是的,我注意到了。因此,我想提供一个替代解决方案,这不仅仅是OP所写的。谢谢Darxtar,在我的应用程序中实现了中间件解决方案,它工作得很好。您使用此解决方案实现的cookie确实是一个仅用于HTTPC的cookie吗
// use the AuthorizationHeader middleware
app.UseMiddleware<AuthorizationHeader>();
// Add a new middleware validating access tokens.
app.UseOAuthValidation();
[Authorize(Roles = "Administrator,User")]
public class RedirectHandler
{
private readonly RequestDelegate _next;
public RedirectHandler(RequestDelegate next)
{
_next = next;
}
public bool IsAjaxRequest(HttpContext context)
{
return context.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
public bool IsFetchRequest(HttpContext context)
{
return context.Request.Headers["X-Requested-With"] == "Fetch";
}
public async Task Invoke(HttpContext context)
{
await _next.Invoke(context);
var ajax = IsAjaxRequest(context);
var fetch = IsFetchRequest(context);
if (context.Response.StatusCode == 302 && (ajax || fetch))
{
context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await context.Response.WriteAsync("Unauthorized");
return;
}
}
}
// TokenController.cs
public IActionResult Some()
{
...
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
Response.Cookies.Append(
"x",
tokenString,
new CookieOptions()
{
Path = "/"
}
);
return StatusCode(200, tokenString);
}
// JWTInHeaderMiddleware.cs
public class JWTInHeaderMiddleware
{
private readonly RequestDelegate _next;
public JWTInHeaderMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var name = "x";
var cookie = context.Request.Cookies[name];
if (cookie != null)
if (!context.Request.Headers.ContainsKey("Authorization"))
context.Request.Headers.Append("Authorization", "Bearer " + cookie);
await _next.Invoke(context);
}
}
// Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseMiddleware<JWTInHeaderMiddleware>();
...
}