JWT learning (3) to solve the problem that JWT cannot be withdrawn in advance

1. Disadvantages of TWT

JWT saves user information to the client, unlike Session, which saves the state on the server side, so it is more suitable for distributed systems and front-end and back-end separation projects, but no technology is perfect. The disadvantage is that once the JWT is issued to the client, the token will remain valid within the validity period, and the token cannot be withdrawn in advance. What scenarios need to withdraw the token in advance before the JWT expires? For example, if the user is deleted, the token for this user must be withdrawn, otherwise there will be a problem that the client uses the user identity that has been deleted; For example, if a certain JWT is obtained by a malicious attacker and used to send a malicious request, we also need to withdraw the token for this user in order to block the attacker; After logging in on the device, we need to withdraw the JWT obtained by the user logging in on device A, otherwise there will be a problem that the user logs in on multiple devices at the same time.

The requirements mentioned above are actually more suitable to be implemented with the traditional Session, because these requirements are related to "server-side state maintenance", and JWT is a server-side stateless mechanism. If you want to use JWT, but also solve the problems related to server-side state maintenance, it is obviously a dream. However, considering that JWT has become a new de facto standard, if the project cannot be changed to the traditional Session mechanism, we still need to find an implementation method under JWT. There are many solutions on the Internet, such as using Redis to save the state, or using the refresh_token+access_token mechanism, etc.

This is another solution for me. The idea of ​​the solution is: add an integer type column JWTVersion in the user table, which represents the version number of the token issued last time; every time we log in and issue a token, we Both make the value of JWTVersion auto-increment,

At the same time, put the value of JWTVersion in the JWT load; when performing operations such as disabling the user and withdrawing the user's token, we let the value of the corresponding JWTVersion of the user increase automatically; when the server receives the JWT submitted by the client Finally, compare the JWTVersion value in the JWT with the JWTVersion value in the database. If the JWTVersion value in the JWT is smaller than the JWTVersion value in the database, it means that the JWT has expired, so we have implemented the JWT withdrawal mechanism. Since we save the JWTVersion value in the user table, this solution essentially still saves the state on the server side, which cannot be bypassed, but this solution is a compromise with fewer disadvantages.

2. Instance of JWT

Identity project written before

MyRole entity class

MyUser entity class

MyDbContext class

DemoController class

Do the following project based on the previous code

Step 1: Add a long type attribute JWTVersion to the user entity User class, and perform database migration to add corresponding columns in the database table.

Step 2: Modify the code of the login concurrency token, increment the value of the user's JWTVersion attribute, and write the value of JWTVersion into the JWT token.

The reason why adding the custom token is to add //!! is to get points. This is the code added after the previous project

Step 3: Write an operation filter to uniformly implement the check operation of the JWT token in the operation method of all controllers.

public class JWTValidationFilter : IAsyncActionFilter

{

private IMemoryCache memCache;

private UserManager<User> userMgr;

public JWTValidationFilter(IMemoryCache memCache, UserManager<User> userMgr)

{

this. memCache =memCache ;

this:aBerMgr=userMgr;

}

//automatically generated method

public asyne Task OnActionExecutionAsync(ActionExecutingContext context, NetionExecutionDelegate next)

{

//如果取出的返回值为null,那么await next();的作用是把请求转给下一个筛选器

var claimUserId=context.HttpContext.User.Findfirst(ClaimTypos.NameIdentifi);

if (claimUserId==null)

{

await next();

return;

}

long userId = long.Parse(claimUserId!.Value);

//这4行代码的作用是把用户信息保存在内存缓存中

string cacheKey = $"JWTValidationFilter.UserInfo.(userId)";

User user = await memCache.GetOrCreateAsync(cacheKey, asyne e=> {

e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconda(5);

return await userMgr.FindByIdAsync(userId.ToString());

});

//表示无法通过JWT提供的用户ID查找用户信息,就说明这个用户可能已经被删除了

if(user = null)

{

var result = new ObjectResultis($"UserId(userId)) not found")i;

result.StatusCode =(int)HttpStatuaCode.Unauthorized;

context.Result = result!;

return;

}

//表示我们从JWT负载中取到客户端保存的JWT版本号,如果发现个数据库中保存的JWT版本号不匹配,就说明这个令牌被回收了。

var claimVersion = context.HtEpContext.User.FindFirsti(ClaimTypes.Version);

long jwtVerOfReq = long.Parse(claimVersion!.Value) ;

if(jwtVerOfReq>=user.JWTVersion)

{

await next();

else

{

var result = new Objecthesultis($"JWTVersion miamatch");

result.StatusCode = (int)HttpStatusCode.Unauthorized;

context.Result = result;

return;

}

}

}

第四步.把JWTValidationFilter注册到Program.cs中MVC的全局筛选器中。

三.JWT和Session的比较

Guess you like

Origin blog.csdn.net/qq_71012549/article/details/128632560
jwt