ajax post-request在刷新JWT Token后返回400 Bad request



ASP.NET核心MVC 5。

我使用JWT令牌的Cookie身份验证作为登录身份验证的声明。

我在每个控制器上也有一个AutoValidateAntiForgeryToken属性来处理XSRF。

JWT代币将在30分钟后到期。在我的CookieAuthenticationEvents.ValidatePrinipal(CookieValidatePrenipalContext上下文(事件处理程序中,如果令牌已过期,请刷新令牌。

当时,为了使用新的JWT令牌重新生成身份验证cookie,我们将CookeeValidatePPrincipalContext.SshouldRenew属性设置为true,如下所示。

var identity = new ClaimsIdentity(context.Principal.Identity);
var sid = identity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid);
identity.RemoveClaim(sid);
identity.AddClaim(new Claim(ClaimTypes.Sid, newJwtToken));
var newPrincipal = new ClaimsPrincipal(identity);
context.ReplacePrincipal(newPrincipal);
context.ShouldRenew = true;

因此,登录后30分钟,浏览器持有的AntiForgeryRequestToken无效,使用该RequestToken的POST将导致400错误请求。

我想知道JWT代币和反伪造结合的正确方法。非常感谢。

更新:在这个JWT令牌超时之前,带有这个AntiForgeryRequeestToken的ajax帖子将被成功接受。

登录20分钟后,我点击";更新";按钮,它工作正常。

如果我停留在同一屏幕上并且一分钟后(即登录后21分钟(;更新";按钮,它仍然正常工作。

重复这个步骤,登录30分钟后,我按下按钮,收到一个400坏请求。

以下是其他一些代码示例:

cshtml代码:

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@{
var xsrf = Xsrf.GetTokens(ViewContext.HttpContext); // GetAndStoreTokens are already called in a login page.
}
const ret = await $.post(
'@Url.Action("Update")',
{
'id': 1234,
'data1': "abc",
'@(xsrf.FormFieldName)': '@(xsrf.RequestToken)'
}
).promise();

ConfigureServices.cs:

services.AddScoped<CookieAuthenticationEventHandler>();
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x => {
x.LoginPath = "/Home";
x.AccessDeniedPath = "/Home";
x.LogoutPath = "/Signout";
x.EventsType = typeof(CookieAuthenticationEventHandler);
x.ExpireTimeSpan = timeout;
x.SlidingExpiration = true; 
});

SigninController.SignIn(signinModel(:

/* Check ID and Password */
var jwtToken = Authorize(signinModel);
Claim[] claims = { new Claim(ClaimTypes.Sid, jwtToken), };
var claimsIdentity = new ClaimsIdentity(
claims,
CookieAuthenticationDefaults.AuthenticationScheme
);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
var timeout = config.GetValue<TimeSpan>("Timeout");
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
claimsPrincipal
);
HttpContext.User = claimsPrincipal;

创建JWT令牌的代码(在Authorize方法中(:

public string CreateToken(IEnumerable<Claim> claims) {
var timeStamp = DateTime.Now;
var timeout = new TimeSpan("0.00:30:00");
var token = new JwtSecurityToken(
"https://unique-url",
"https://unique-url",
claims,
timeStamp,
expires: DateTime.Now.Add(timeout),
signingCredentials: SigningCredentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}

CookieAuthenticationEventHandler:

public override async Task ValidatePrincipal(CookieValidatePrincipalContext context) {
if(!await Validate(context)) {
context.RejectPrincipal();
await context.HttpContext.SignOutAsync();
}
}
public async Task<bool> Validate(CookieValidatePrincipalContext context) {
var jwtToken = context.Principal.Claims?.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value;
var valid = await ValidatetokenAsync(jwtToken);
if(valid) {
return true;
}
var newJwtToken = await RefreshtokenAsync(jwtToken);
if(string.IsNullOrEmpty(newJwtToken)) {
return false;
}
var identity = new ClaimsIdentity(context.Principal.Identity);
var sid = identity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid);
identity.RemoveClaim(sid);
identity.AddClaim(new Claim(ClaimTypes.Sid, newJwtToken));
var newPrincipal = new ClaimsPrincipal(identity);
context.ReplacePrincipal(newPrincipal);
context.ShouldRenew = true;
return true;
}

更新2:

这个问题的根本原因:

(1( 传递给HttpContext.SingInAsync的ClaimsPrincipal包含JWT令牌。

(2( 登录会话的到期日期与JWT令牌的到期日期不同。每次访问登录会话时,登录会话的到期日期都会延长,但JWT令牌会在到期时刷新。

(3( JWT令牌到期并且刷新令牌时,还必须更新HttpContext的ClaimsPrincipal。

(4( 由于AntiForgery执行的验证依赖于此ClaimsPrincipal,因此在刷新JWT令牌之前获得的任何AntiForgeryToken都将(突然(失效。

(5( 因此,如果在JWT令牌到期时执行HTTP-POST,则AntiForgery将失败。

如果这个方法是错误的,我想知道正确的方法。如果这种方法没有错,请让我知道如何解决它。

[已解决]

刷新JWT时,重建CookieAuthentication的ClaimsPrincipal是不可避免的,但我正在为AntiForgery寻找一个设置,以正确验证重建前创建的XSRF-TOKEN,甚至在重建后。

最后我在AspNetCore.AntiForgery的源代码中找到了它。https://github.com/dotnet/aspnetcore/blob/v3.1.4/src/Antiforgery/src/Internal/DefaultClaimUidExtractor.cs#L25

AntiForgery在生成和验证XSRF-TOKEN时使用用户标识符;sub";在ClaimsPrincipal或ClaimType.NameIdentifier、ClaimType.Upn中。如果找不到它们,AntiForgery将使用所有声明作为用户标识符。

在我的源代码中,我只是将JWT作为ClaimType.Sid传递给HttpContext.SignInAsync.的ClaimsPrincipal

因此,在刷新JWT并重建ClaimsPrincipal后,AntiForgery使用整个重建的ClaimsPrincipl来检查XSRF-TOKEN,验证失败。

通过添加"ClaimType";sub";对于ClaimsPrincipal,400坏请求不再发生。

Claim[] claims = {
new Claim(ClaimTypes.Sid, jwtToken),
new Claim("sub", loginUser.Id),
};

最新更新