.net core中过滤器JWT提前撤回

我们都知道JWT是一个无状态的,存储在客户端,服务端无法提前撤回。这里的解决思路是生成JWT时,给一个版本号,用户再次登录时,版本号加一,当用户使用旧的JWT登录的时候,会比对数据库中存储的version以及JWT中的version,假如不一样则登录失败
本文使用Identity实现RBAC

  1. 给用户表添加JWTVersion字段
using Microsoft.AspNetCore.Identity;

namespace EFCoreJWT
{
    public class MyUser: IdentityUser<long>
    {
        public long JWTVersion { get; set; }
    }
}

  1. 生成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);
        }

    }
}

  1. 添加attribute,这个attribute标记的方法不会check version
namespace EFCoreJWT
{

    public class NoCheckVersion:Attribute
    {
    }
}

  1. 添加过滤器,实现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();
        }
    }
}

  1. programe中添加过滤器
builder.Services.Configure<MvcOptions>(opt =>
{
    opt.Filters.Add<JWTFilter>();

});

至此完成所有工作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值