ASP.NET Core JWT Version
目录
JWT缺点
方案
实现
Program.cs
IdentityHelper.cs
Controller
NotCheckJWTVersionAttribute.cs
JWTVersionCheckkFilter.cs
优化
JWT缺点
- 到期前,令牌无法被提前撤回。什么情况下需要撤回?用户被删除了、禁用了;令牌被盗用了;单设备登录。
- 需要JWT撤回的场景用传统Session更合适。
- 如果需要在JWT中实现,思路:用Redis保存状态,或者用refresh_token+access_token机制等。
方案
在用户表中增加一个整数类型的列JWTVersion,代表最后一次发放出去的令牌的版本号;每次登录、发放令牌的时候,都让JWTVersion的值自增,同时将JWTVersion的值也放到JWT令牌的负载中;当执行禁用用户、撤回用户的令牌等操作的时候,把这个用户对应的JWTVersion列的值自增;当服务器端收到客户端提交的JWT令牌后,先把JWT令牌中的JWTVersion值和数据库中JWTVersion的值做一下比较,如果JWT令牌中JWTVersion的值小于数据库中JWTVersion的值,就说明这个JWT令牌过期了。
实现
- 为用户实体User类增加一个long类型的属性JWTVersion。
public class MyUser : IdentityUser<long> {public string? WeChatAccout { get; set; }public long JWTVersions { get; set; } } - 修改登录并发放令牌的代码,把用户的JWTVersion属性的值自增,并且把JWTVersion的值写入JWT令牌。
- 编写一个操作筛选器,统一实现对所有的控制器的操作方法中JWT令牌的检查操作。把JWTValidationFilter注册到Program.cs中MVC的全局筛选器中。
Program.cs
using Identity框架;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Scalar.AspNetCore;
using System.Text;var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
//将Bearer身份验证添加到Scalar
builder.Services.AddOpenApi(opt =>
{opt.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});
//添加数据库上下文
builder.Services.AddDbContext<MyDbContext>(opt =>
{string connStr = Environment.GetEnvironmentVariable("ConnStr");opt.UseSqlServer(connStr);
});
//添加Filter
builder.Services.Configure<MvcOptions>(opt =>
{opt.Filters.Add<JWTVersionCheckFilter>();//添加JWT版本检查ActionFilter
});
//添加Identity服务
builder.Services.AddDataProtection();
builder.Services.AddIdentityCore<MyUser>(options =>
{//设置密码规则,不需要数字,小写字母,大写字母,特殊字符,长度为6options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromSeconds(30);options.Password.RequireDigit = false;options.Password.RequireLowercase = false;options.Password.RequireUppercase = false;options.Password.RequireNonAlphanumeric = false;options.Password.RequiredLength = 6;options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
});
var idBuilder = new IdentityBuilder(typeof(MyUser), typeof(MyRole), builder.Services);
idBuilder.AddEntityFrameworkStores<MyDbContext>().AddDefaultTokenProviders().AddRoleManager<RoleManager<MyRole>>().AddUserManager<UserManager<MyUser>>();
//添加JWT设置
builder.Services.Configure<JWTSettings>(builder.Configuration.GetSection("JWT"));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt =>
{var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTSettings>();byte[] key = Encoding.UTF8.GetBytes(jwtOpt.SecKey);//设置对称秘钥var secKey = new SymmetricSecurityKey(key);//设置验证参数opt.TokenValidationParameters = new(){ValidateIssuer = false,//是否验证颁发者ValidateAudience = false,//是否验证订阅者ValidateLifetime = true,//是否验证生命周期ValidateIssuerSigningKey = true,//是否验证签名IssuerSigningKey = secKey//签名秘钥};
});var app = builder.Build();
if (app.Environment.IsDevelopment())
{app.MapOpenApi();app.MapScalarApiReference();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
IdentityHelper.cs
public static class IdentityHelper
{public static async Task CheckAsync(this Task<IdentityResult> task){var r = await task;if (!r.Succeeded){throw new Exception(JsonSerializer.Serialize(r.Errors));}}
}
Controller
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 Identity框架.Controllers
{[Route("api/[controller]/[action]")][ApiController]public class DemoController : ControllerBase{private readonly UserManager<MyUser> userManager;private readonly RoleManager<MyRole> roleManager;private readonly IOptionsSnapshot<JWTSettings> jwtSettingsOpt;public DemoContrller(UserManager<MyUser> userManager, RoleManager<MyRole> roleManager, IOptionsSnapshot<JWTSettings> jwtSettingsOpt){this.userManager = userManager;this.roleManager = roleManager;this.jwtSettingsOpt = jwtSettingsOpt;}[HttpPost][NotCheckJWTVersion]public async Task<ActionResult<string>> Login(string userName, string password){//根据用户名查找用户var user = await userManager.FindByNameAsync(userName);if (user == null){return BadRequest("用户或密码错误1");}//判断是否登录成功,失败则记录失败次数if (await userManager.CheckPasswordAsync(user, password)){//登录成功,重置失败次数,CheckAsync判断操作是否成功,失败则抛出异常await userManager.ResetAccessFailedCountAsync(user).CheckAsync();//更新JWT版本号,防止旧JWT被使用user.JWTVersions++;await userManager.UpdateAsync(user);//身份验证声明List<Claim> claims = new List<Claim>{new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),new Claim(ClaimTypes.Name, user.UserName),new Claim("JWTVersions", user.JWTVersions.ToString())};//获取用户角色,添加到声明中var roles = await userManager.GetRolesAsync(user);foreach (var role in roles){claims.Add(new Claim(ClaimTypes.Role, role));}//生成JWTstring key = jwtSettingsOpt.Value.SecKey;DateTime expires = DateTime.Now.AddSeconds(jwtSettingsOpt.Value.ExpireSeconds);byte[] keyBytes = Encoding.UTF8.GetBytes(key);var secKey = new SymmetricSecurityKey(keyBytes);var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256Signature);var tokenDescriptor = new JwtSecurityToken(claims: claims,//声明expires: expires,//过期时间signingCredentials: credentials//签名凭据);string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);return jwt;}else{await userManager.AccessFailedAsync(user).CheckAsync();return BadRequest("用户或密码错误2");}}}
}
NotCheckJWTVersionAttribute.cs
[AttributeUsage(AttributeTargets.Method)]
public class NotCheckJWTVersionAttribute:Attribute
{
}
JWTVersionCheckkFilter.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Security.Claims;namespace Identity框架
{public class JWTVersionCheckFilter : IAsyncActionFilter{private readonly UserManager<MyUser> userManager;public JWTVersionCheckFilter(UserManager<MyUser> userManager){this.userManager = userManager;}public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next){//获取当前Action的特性,判断是否有NotCheckJWTVersionAttribute特性,如果有则不检查JWTVersionControllerActionDescriptor controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;if (controllerActionDescriptor == null){await next();return;}if (controllerActionDescriptor.MethodInfo.GetCustomAttributes(typeof(NotCheckJWTVersionAttribute), true).Any()){await next();return;}//获取JWTVersion,不存在则返回400var claimJWTVersion = context.HttpContext.User.FindFirst("JWTVersions");if (claimJWTVersion == null){context.Result = new ObjectResult("payload中JWTVersion不存在"){StatusCode = 400};return;}//获取用户id,不存在则返回400var claimUserId = context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier);long userId = Convert.ToInt64(claimUserId.Value);//不用每次都查询数据库,可以从缓存中获取用户var user = await userManager.FindByIdAsync(userId.ToString());if (user == null){context.Result = new ObjectResult("用户不存在"){StatusCode = 400};return;}//判断JWTVersion是否过时long jwtVersionClient = Convert.ToInt64(claimJWTVersion.Value);if (user.JWTVersions > jwtVersionClient){context.Result = new ObjectResult("客户端jwt过时"){StatusCode = 400};return;}await next();}}
}
优化
每一次客户端和Controller的交互的时候,检查JWTVersion的筛选器都要查询数据库,性能太低,可以用缓存进行优化。
相关文章:
ASP.NET Core JWT Version
目录 JWT缺点 方案 实现 Program.cs IdentityHelper.cs Controller NotCheckJWTVersionAttribute.cs JWTVersionCheckkFilter.cs 优化 JWT缺点 到期前,令牌无法被提前撤回。什么情况下需要撤回?用户被删除了、禁用了;令牌被盗用了&…...
【ArcGIS】R语言空间分析、模拟预测与可视化技术
R语言在空间数据挖掘中具有广泛的应用,以下是一些关键内容和常用包的介绍: R语言空间数据挖掘的关键技术 空间数据类型 矢量数据:包括点(Point)、线(Line)、面(Polygon)等…...
日常知识点之面试后反思遗留问题汇总
梳理一下最近接触到的几个知识点: 1:突然问到端口复用 (SO_REUSEADDR) 端口复用一般用在服务端重启时,套接字处于time_wait状态时,无法绑定该端口,导致无法启动问题。 设置端口复用ÿ…...
链表(LinkedList) 1
上期内容我们讲述了顺序表,知道了顺序表的底层是一段连续的空间进行存储(数组),在插入元素或者删除元素需要将顺序表中的元素整体移动,时间复杂度是O(n),效率比较低。因此,在Java的集合结构中又引入了链表来解决这一问…...
Qt:Qt Creator项目创建
目录 认识Qt Creator Qt Creator概览 使用Qt Creator新建项目 选择项目模板 选择项目路径 选择构建系统 填写类信息设置界面 选择语言和翻译文件 选择Qt套件 选择版本控制系统 最终效果 认识Qt Creator Qt Creator概览 从开始菜单或者快捷方式打开Qt Creator集成开…...
windows11上,使用pipx安装Poetry,Poetry的安装路径是什么?
当使用 pipx 安装 Poetry 时,pipx 会将 Poetry 安装到一个独立的虚拟环境中,并将其可执行文件链接到一个集中的目录中。以下是 pipx 安装 Poetry 时的路径信息: 1. Poetry 的安装路径 pipx 会为每个工具(如 Poetry)创…...
详解状态模式
引言 水有固态、液态、气态三种状态,在不同条件下这三种状态可以相互转化。同样在软件设计中,有些对象也有不同的状态,不同状态的行为不同,状态模式就是用来处理这种情况的。 1.概念 状态模式(State Pattern):允许一个…...
能否通过蓝牙建立TCP/IP连接来传输数据
前言: 最近在做一个项目时,产生了一个疑问:能否通过蓝牙建立TCP/IP连接来传输数据 查阅了一些文章,可以得出结论:不行 下面是我截取的两篇个人认可的文章的回答: 文章一: 蓝牙是一种短距离无…...
uniapp mqttjs 小程序开发
在UniApp中集成MQTT.js开发微信小程序时,需注意平台差异、协议兼容性及消息处理等问题。以下是关键步骤与注意事项的综合指南: 一、环境配置与依赖安装 安装MQTT.js 推荐使用兼容性较好的版本:mqtt4.1.0(H5和小程序兼容性最佳&…...
爬虫工程师分享:获取京东商品详情SKU数据的技术难点与攻破方法
在电商数据领域,京东商品详情页的SKU数据是许多爬虫工程师的目标。这些数据包含了商品的价格、库存、规格等关键信息,对于市场分析、价格监控等应用场景至关重要。然而,获取这些数据并非易事,京东作为国内电商巨头,其反…...
数据库操作与数据管理——Rust 与 SQLite 的集成
第六章:数据库操作与数据管理 第一节:Rust 与 SQLite 的集成 在本节中,我们将深入探讨如何在 Rust 中使用 SQLite 数据库,涵盖从基本的 CRUD 操作到事务处理、数据模型的构建、性能优化以及安全性考虑等方面。SQLite 是一个轻量…...
LeetCode 0063.不同路径 II:动态规划 - 原地使用地图数组,几乎无额外空间开销
【LetMeFly】63.不同路径 II:动态规划 - 原地使用地图数组,几乎无额外空间开销 力扣题目链接:https://leetcode.cn/problems/unique-paths-ii/ 给定一个 m x n 的整数数组 grid。一个机器人初始位于 左上角(即 grid[0][0]&#…...
elementui:el-table支持搜索、切换分页多选功能,以及数据回显
1、el-table相关代码,需注意:row-key"(row) > { return row.id }" 以及 :reserve-selection"true" <div class"boxList"><div class"search-form"><!-- 搜索表单 --><el-form :inline"true&q…...
深度整理总结MySQL——索引正确使用姿势
索引正确使用姿势 前言MySQL索引优缺点分析✅ 索引的优势⚠️ 索引的代价 如何合理建立索引?——关键原则总结重要的优化机制索引覆盖——通俗的方式讲解索引下推索引跳跃式扫描 前言 这篇文章是补充一些基本概念和实战的一些使用建议. MySQL索引优缺点分析 ✅ 索引的优势 …...
使用LLaMA Factory踩坑记录
前置条件:电脑显卡RTX 4080 问题:LLaMA-Factory在运行的时候,弹出未检测到CUDA的报错信息 结论:出现了以上的报错,主要可以归结于以下两个方面: 1、没有安装GPU版本的pytorch,下载的是CPU版本…...
亚博microros小车-原生ubuntu支持系列:25 二维码控制运动
二维码识别 安装依赖 pip3 install pyzbarsudo apt install libzbar-dev 在用小车识别之前,先用电脑的摄像头测试下基本的识别 import cv2 import rclpy from rclpy.node import Node import pyzbar.pyzbar as pyzbar import numpy as np from ament_index_pyth…...
基于深度学习的人工智能量化衰老模型构建与全流程应用研究
一、引言 1.1 研究背景与意义 1.1.1 人口老龄化现状与挑战 人口老龄化是当今全球面临的重要社会趋势之一,其发展态势迅猛且影响深远。根据联合国的相关数据,1980 年,全球 65 岁及以上人口数量仅为 2.6 亿,到 2021 年,这一数字已翻番,达到 7.61 亿,而预计到 2050 年,…...
【医院运营统计专题】2.运营统计:医院管理的“智慧大脑”
医院成本核算、绩效管理、运营统计、内部控制、管理会计专题索引 引言 在当今医疗行业快速发展的背景下,医院运营管理的科学性和有效性成为了决定医院竞争力和可持续发展能力的关键因素。运营统计作为医院管理的重要工具,通过对医院各类数据的收集、整理、分析和解读,为医…...
Spring Boot Actuator使用
说明:本文介绍Spring Boot Actuator的使用,关于Spring Boot Actuator介绍,下面这篇博客写得很好,珠玉在前,我就不多介绍了。 Spring Boot Actuator 简单使用 项目里引入下面这个依赖 <!--Spring Boot Actuator依…...
【AI应用】免费的文本转语音工具:微软 Edge TTS 和 开源版 ChatTTS 对比
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】【AI应用】 我试用了下Edge TTS,感觉还不错,不过它不支持克隆声音(比如自己的声音) 微软 Edge TTS 和 开源版 ChatTTS 都是免费的 文本转语音&…...
5分钟搞定OpenClaw飞书接入:Qwen3.5-9B对话机器人配置
5分钟搞定OpenClaw飞书接入:Qwen3.5-9B对话机器人配置 1. 为什么选择OpenClaw飞书Qwen3.5-9B组合 上周我在整理团队周报时,突然意识到一个痛点:每次都要反复切换浏览器、文档和聊天工具,手动复制粘贴信息。这种机械操作不仅浪费…...
CAN总线大数据传输的解决方案
CAN总线通讯最多传输8个字节,如果需要传输大量数据该怎么办呢?这个问题工业界有很多成熟的解决方案,我现在就来详细为你介绍各种处理方法。 一、CAN协议的限制原因 CAN帧的数据场限制为8字节,主要是为了保证: • 实时性…...
别再死磕EKF了!用ESKF搞定无人机姿态估计,避开‘大数吃小数’的坑
无人机姿态估计实战:用ESKF避开EKF的数值陷阱 四轴飞行器在高速翻滚时,IMU数据突然出现剧烈抖动——这是去年调试自主无人机时遇到的真实场景。当时使用传统EKF算法,姿态解算在极端机动下频繁发散,直到切换到误差状态卡尔曼滤波&a…...
FLUX小红书V2模型版本对比:V1与V2的核心改进与效果差异
FLUX小红书V2模型版本对比:V1与V2的核心改进与效果差异 1. 引言 如果你最近在玩AI图像生成,特别是想做出那种看起来特别真实、特别有小红书风格的照片,那你肯定听说过FLUX小红书模型。这个模型从V1版本开始就挺火的,主要是因为它…...
如何快速实现本地离线语音识别:面向Windows用户的完整解决方案
如何快速实现本地离线语音识别:面向Windows用户的完整解决方案 【免费下载链接】TMSpeech 腾讯会议摸鱼工具 项目地址: https://gitcode.com/gh_mirrors/tm/TMSpeech 还在为会议记录、视频字幕、语音笔记而烦恼吗?传统的语音识别工具要么需要网络…...
Python农业图像识别精度为何卡在92.3%?揭秘3个被90%开发者忽略的标注陷阱与突破路径
第一章:Python农业图像识别精度为何卡在92.3%?在多个田间部署的玉米病害识别模型中,验证集准确率稳定收敛于92.3%,进一步调参或增加训练轮次均未突破该阈值。深入分析发现,该瓶颈并非源于模型容量不足,而是…...
OpenClaw飞书办公助手:Qwen3-VL:30B自动化会议纪要生成
OpenClaw飞书办公助手:Qwen3-VL:30B自动化会议纪要生成 1. 为什么需要自动化会议纪要 每次开完会最痛苦的事情是什么?对我来说就是整理会议纪要。作为团队的技术负责人,我每周要参加至少5场会议,从需求评审到技术方案讨论&#…...
Ubuntu 20.04安装MATLAB R2023B保姆级避坑指南:从卸载旧版到选对产品,一步一截图
Ubuntu 20.04安装MATLAB R2023B全流程实战:从彻底卸载到精准选配 在科研与工程计算领域,MATLAB始终保持着不可替代的地位。当最新版的R2023B遇上Ubuntu 20.04这个长期支持版本,如何实现完美部署却让不少用户望而却步。不同于Windows下的图形化…...
商家客服智能管理系统架构设计与性能优化实战
商家客服智能管理系统架构设计与性能优化实战 面对电商大促期间海量用户的咨询涌入,传统的客服系统往往不堪重负。我记得去年双十一,我们团队维护的客服系统就经历了严峻考验:页面响应时间从平时的200ms飙升到2秒以上,大量用户排队…...
SDMatte提示词(Prompt)高级使用技巧:引导模型优化抠图边缘
SDMatte提示词(Prompt)高级使用技巧:引导模型优化抠图边缘 1. 为什么提示词对抠图质量至关重要 你可能已经发现,同样的图片在不同提示词下,SDMatte生成的蒙版质量会有明显差异。这就像给修图师不同的工作指令——说&…...
