验证 JWT 令牌 IDX12741 时出错。 JWT:必须有三段(JWS)或者五段(JWE)
Error on validating JWT token IDX12741. JWT: must have three segments (JWS) or five segments (JWE)
我正在尝试在 angular 12 和 .net core 5 中实现令牌刷新功能。
这是我的 JWT 服务注册:
startup.cs:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero,
ValidAudience = _conf["JWT:ValidAudience"],
ValidIssuer = _conf["JWT:ValidIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_conf["JWT:Secret"]))
};
});
这是我的 appsettings.json:
"JWT": {
"ValidAudience": "http://localhost:4200",
"ValidIssuer": "http://localhost:4200",
"Secret": "JWTRefreshTokenHIGHsecuredPasswordVVVp1OH7Xzyr",
"TokenValidityInMinutes": 1,
"RefreshTokenValidityInDays": 7
}
调用登录控制器后,一个简单的访问令牌和一个刷新令牌被发送回客户端:
登录控制器:
//login codes omitted for simplicity
foreach (var role in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, role));
}
var accessToken = CreateToken(authClaims);
var refreshToken = GenerateRefreshToken();
_ = int.TryParse(_configuration["JWT:RefreshTokenValidityInDays"], out int refreshTokenValidityInDays);
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.Now.AddDays(refreshTokenValidityInDays);
CreateToken 方法:
private JwtSecurityToken CreateToken(List<Claim> authClaims)
{
var authSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));
_ = int.TryParse(_configuration["JWT:TokenValidityInMinutes"], out int TokenValidityInMinutes);
var expires = DateTime.Now.ToLocalTime().AddMinutes(TokenValidityInMinutes);
var token = new JwtSecurityToken(
issuer: _configuration["JWT:ValidIssuer"],
audience: _configuration["JWT:ValidAudience"],
claims: authClaims,
expires: expires,
signingCredentials: new SigningCredentials(authSecurityKey, SecurityAlgorithms.HmacSha256)
) ;
return token;
}
现在登录后,我在浏览器 SessionStorage 中保存了访问令牌和刷新令牌。
在客户端,我实现了一个 HTTP 拦截器来处理 401 未经授权的错误。当我的后端显示令牌已过期时,我想刷新我的 JWT 令牌。
auth.interceptor.ts:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(private tokenService: tokenStorageService, private authService: AuthenticationService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<Object>> {
debugger;
let authReq = req;
const token = this.tokenService.getToken();
if (token != null) {
authReq = this.addTokenHeader(req, token);
}
return next.handle(authReq).pipe(catchError(error => {
if (error instanceof HttpErrorResponse && !authReq.url.includes('auth/login') && error.status === 401) {
debugger;
return this.handle401Error(authReq, next);
}
return throwError(error);
}));
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
const token = this.tokenService.getRefreshToken();
const refreshToken=this.tokenService.getRefreshToken();
if (token)
return this.authService.refreshToken(token,refreshToken).pipe(
switchMap((token: any) => {
this.isRefreshing = false;
this.tokenService.saveToken(token.accessToken);
this.refreshTokenSubject.next(token.accessToken);
return next.handle(this.addTokenHeader(request, token.accessToken));
}),
catchError((err) => {
debugger;
this.isRefreshing = false;
//this.tokenService.signOut();
return throwError(err);
})
);
}
return this.refreshTokenSubject.pipe(
filter(token => token !== null),
take(1) switchMap((token) => next.handle(this.addTokenHeader(request, token)))
);
}
private addTokenHeader(request: HttpRequest<any>, token: string) {
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
}
登录后一分钟我的令牌过期并且拦截器调用我的刷新令牌api。
RefreshToken 控制器:
public async Task<object> RefreshToken(tokenModel tokenModel)
{
if (tokenModel == null)
throw new ServiceException("Invalid Token Model");
string accessToken = tokenModel.accessToken;
string refreshToken = tokenModel.refreshToken;
var principal = GetPrincipalsFromExpiredToken(accessToken);
if(principal==null)
{
throw new ServiceException("Invalid access token or refresh token");
}
string username = principal.Identity.Name;
var user = await _userManager.FindByNameAsync(username);
if(user==null || user.RefreshToken!=refreshToken || user.RefreshTokenExpiryTime<=DateTime.Now)
{
throw new ServiceException("Invalid access token or refresh token");
}
var newAccessToken = CreateToken(principal.Claims.ToList());
var newRefreshToken = GenerateRefreshToken();
user.RefreshToken = newRefreshToken;
await _userManager.UpdateAsync(user);
return new
{
accessToken=new JwtSecurityTokenHandler().WriteToken(newAccessToken),
refreshToken=newRefreshToken
};
}
这是 GetPrincipalsFromExpiredToken 方法:
private ClaimsPrincipal GetPrincipalsFromExpiredToken(string token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"])),
ValidateLifetime = false
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken = null;
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
if (securityToken is not JwtSecurityToken jwtSecurityToken ||
!jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256,
StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
在 tokenHandler.ValidateToken 我收到此错误消息:
IDX12741:JWT:'[PII 已隐藏。有关更多详细信息,请参阅 https://aka.ms/IdentityModel/PII.]' must have three segments (JWS) or five segments (JWE).
我做错了什么?
有没有更简单的方法来实现 jwt 刷新令牌?
这是生成的令牌:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdEBrYXNiaW0uaXIiLCJqdGkiOiJjM2I2ZTQyZi00ZGI1LTQzMDMtYjY4Mi02YWU5Yzg3ZjI1ZTUiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJVc2VyIiwiZXhwIjoxNjUyMzc0ODI4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQyMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjQyMDAifQ.8rzkMkVENPAyWV2DpPhUUAzza0cXY_HiUpWq2u_Sqqs
你的 jwt 是什么样子的? JWT由三个parts:header、playload和signature组成,以“.”分割。我想在你的 jwt string.Your 错误是由错误的 jwt 结构引起的。
你可以看到如下测试结果:
已更新
第二次请求的错误信息(我在jwt中删掉了一个"."):
我正在尝试在 angular 12 和 .net core 5 中实现令牌刷新功能。
这是我的 JWT 服务注册:
startup.cs:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero,
ValidAudience = _conf["JWT:ValidAudience"],
ValidIssuer = _conf["JWT:ValidIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_conf["JWT:Secret"]))
};
});
这是我的 appsettings.json:
"JWT": {
"ValidAudience": "http://localhost:4200",
"ValidIssuer": "http://localhost:4200",
"Secret": "JWTRefreshTokenHIGHsecuredPasswordVVVp1OH7Xzyr",
"TokenValidityInMinutes": 1,
"RefreshTokenValidityInDays": 7
}
调用登录控制器后,一个简单的访问令牌和一个刷新令牌被发送回客户端:
登录控制器:
//login codes omitted for simplicity
foreach (var role in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, role));
}
var accessToken = CreateToken(authClaims);
var refreshToken = GenerateRefreshToken();
_ = int.TryParse(_configuration["JWT:RefreshTokenValidityInDays"], out int refreshTokenValidityInDays);
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.Now.AddDays(refreshTokenValidityInDays);
CreateToken 方法:
private JwtSecurityToken CreateToken(List<Claim> authClaims)
{
var authSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));
_ = int.TryParse(_configuration["JWT:TokenValidityInMinutes"], out int TokenValidityInMinutes);
var expires = DateTime.Now.ToLocalTime().AddMinutes(TokenValidityInMinutes);
var token = new JwtSecurityToken(
issuer: _configuration["JWT:ValidIssuer"],
audience: _configuration["JWT:ValidAudience"],
claims: authClaims,
expires: expires,
signingCredentials: new SigningCredentials(authSecurityKey, SecurityAlgorithms.HmacSha256)
) ;
return token;
}
现在登录后,我在浏览器 SessionStorage 中保存了访问令牌和刷新令牌。
在客户端,我实现了一个 HTTP 拦截器来处理 401 未经授权的错误。当我的后端显示令牌已过期时,我想刷新我的 JWT 令牌。
auth.interceptor.ts:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(private tokenService: tokenStorageService, private authService: AuthenticationService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<Object>> {
debugger;
let authReq = req;
const token = this.tokenService.getToken();
if (token != null) {
authReq = this.addTokenHeader(req, token);
}
return next.handle(authReq).pipe(catchError(error => {
if (error instanceof HttpErrorResponse && !authReq.url.includes('auth/login') && error.status === 401) {
debugger;
return this.handle401Error(authReq, next);
}
return throwError(error);
}));
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
const token = this.tokenService.getRefreshToken();
const refreshToken=this.tokenService.getRefreshToken();
if (token)
return this.authService.refreshToken(token,refreshToken).pipe(
switchMap((token: any) => {
this.isRefreshing = false;
this.tokenService.saveToken(token.accessToken);
this.refreshTokenSubject.next(token.accessToken);
return next.handle(this.addTokenHeader(request, token.accessToken));
}),
catchError((err) => {
debugger;
this.isRefreshing = false;
//this.tokenService.signOut();
return throwError(err);
})
);
}
return this.refreshTokenSubject.pipe(
filter(token => token !== null),
take(1) switchMap((token) => next.handle(this.addTokenHeader(request, token)))
);
}
private addTokenHeader(request: HttpRequest<any>, token: string) {
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
}
登录后一分钟我的令牌过期并且拦截器调用我的刷新令牌api。
RefreshToken 控制器:
public async Task<object> RefreshToken(tokenModel tokenModel)
{
if (tokenModel == null)
throw new ServiceException("Invalid Token Model");
string accessToken = tokenModel.accessToken;
string refreshToken = tokenModel.refreshToken;
var principal = GetPrincipalsFromExpiredToken(accessToken);
if(principal==null)
{
throw new ServiceException("Invalid access token or refresh token");
}
string username = principal.Identity.Name;
var user = await _userManager.FindByNameAsync(username);
if(user==null || user.RefreshToken!=refreshToken || user.RefreshTokenExpiryTime<=DateTime.Now)
{
throw new ServiceException("Invalid access token or refresh token");
}
var newAccessToken = CreateToken(principal.Claims.ToList());
var newRefreshToken = GenerateRefreshToken();
user.RefreshToken = newRefreshToken;
await _userManager.UpdateAsync(user);
return new
{
accessToken=new JwtSecurityTokenHandler().WriteToken(newAccessToken),
refreshToken=newRefreshToken
};
}
这是 GetPrincipalsFromExpiredToken 方法:
private ClaimsPrincipal GetPrincipalsFromExpiredToken(string token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"])),
ValidateLifetime = false
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken = null;
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
if (securityToken is not JwtSecurityToken jwtSecurityToken ||
!jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256,
StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
在 tokenHandler.ValidateToken 我收到此错误消息:
IDX12741:JWT:'[PII 已隐藏。有关更多详细信息,请参阅 https://aka.ms/IdentityModel/PII.]' must have three segments (JWS) or five segments (JWE).
我做错了什么? 有没有更简单的方法来实现 jwt 刷新令牌?
这是生成的令牌:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdEBrYXNiaW0uaXIiLCJqdGkiOiJjM2I2ZTQyZi00ZGI1LTQzMDMtYjY4Mi02YWU5Yzg3ZjI1ZTUiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJVc2VyIiwiZXhwIjoxNjUyMzc0ODI4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQyMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjQyMDAifQ.8rzkMkVENPAyWV2DpPhUUAzza0cXY_HiUpWq2u_Sqqs
你的 jwt 是什么样子的? JWT由三个parts:header、playload和signature组成,以“.”分割。我想在你的 jwt string.Your 错误是由错误的 jwt 结构引起的。
你可以看到如下测试结果:
已更新
第二次请求的错误信息(我在jwt中删掉了一个"."):