我们都知道JWT是一个无状态的,存储在客户端,服务端无法提前撤回。这里的解决思路是生成JWT时,给一个版本号,用户再次登录时,版本号加一,当用户使用旧的JWT登录的时候,会比对数据库中存储的version以及JWT中的version,假如不一样则登录失败
本文使用Identity实现RBAC
- 给用户表添加JWTVersion字段
using Microsoft.AspNetCore.Identity;
namespace EFCoreJWT
{
public class MyUser: IdentityUser<long>
{
public long JWTVersion { get; set; }
}
}
- 生成JWT时,添加JWTVersion ,并入库
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace EFCoreJWT.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
private readonly UserManager<MyUser> _userManager;
private readonly RoleManager<MyRole> _roleManager;
private readonly IOptionsSnapshot<Config> _config;
public TestController(UserManager<MyUser> userManager, RoleManager<MyRole> roleManager, IOptionsSnapshot<Config> config)
{
_userManager = userManager;
_roleManager = roleManager;
_config = config;
}
[Authorize(Roles ="admin")]
[HttpPost]
public async Task<ActionResult<string>> GenerateToken(string username)
{
string getfromtoken = this.User.FindFirst(ClaimTypes.Name)?.Value;
MyUser myUser =await _userManager.FindByNameAsync(username);
if (myUser==null)
{
return BadRequest("用户不存在");
}
var token = await _userManager.GeneratePasswordResetTokenAsync(myUser);
return token;
}
[HttpPost]
[NoCheckVersion]
public async Task<string> GenerateJWTToken(string username,string password)
{
MyUser user =await _userManager.FindByNameAsync(username);
if (user == null)
{
return "failed";
}
var success =await _userManager.CheckPasswordAsync(user, password);
if (!success)
{
return "failed";
}
string token =await GenerateJSONWebToken(user);
return token;
}
private async Task<string> GenerateJSONWebToken(MyUser userInfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.Value.Key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
userInfo.JWTVersion++;
await _userManager.UpdateAsync(userInfo);
var claims=new List<Claim>{
new Claim(ClaimTypes.Name, userInfo.UserName),
new Claim(ClaimTypes.NameIdentifier,userInfo.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("JWTVersion",userInfo.JWTVersion.ToString()),
};
var roles =await _userManager.GetRolesAsync(userInfo);
foreach (var item in roles)
{
claims.Add(new Claim(ClaimTypes.Role, item));
}
var token = new JwtSecurityToken(_config.Value.Issuer,
_config.Value.Issuer,
claims,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
}
- 添加attribute,这个attribute标记的方法不会check version
namespace EFCoreJWT
{
public class NoCheckVersion:Attribute
{
}
}
- 添加过滤器,实现version的比对
记住不要忘了next
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Security.Claims;
namespace EFCoreJWT
{
public class JWTFilter : IAsyncActionFilter
{
private readonly UserManager<MyUser> _userManager;
public JWTFilter(UserManager<MyUser> userManager)
{
_userManager = userManager;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
ControllerActionDescriptor? acti = context.ActionDescriptor as ControllerActionDescriptor;
if (acti == null)
{
await next();
return;
}
if (acti.MethodInfo.GetCustomAttributes(typeof(NoCheckVersion),true).Any())
{
//不检查,继续往下走
await next();
return;
}
var version = context.HttpContext.User.FindFirst("JWTVersion")?.Value;
if (version == null)
{
context.Result = new ObjectResult(new { code = 400, msg = "error" });
return;
}
var user =await _userManager.FindByNameAsync(context.HttpContext.User.FindFirst(ClaimTypes.Name)?.Value);
if (user.JWTVersion> Convert.ToInt64( version))
{
context.Result = new ObjectResult(new {code =400,msg= "error"});
return;
}
await next();
}
}
}
- programe中添加过滤器
builder.Services.Configure<MvcOptions>(opt =>
{
opt.Filters.Add<JWTFilter>();
});
至此完成所有工作