当前位置: 首页 > article >正文

ASP.NET Core JWT Version

目录

JWT缺点

方案

实现

Program.cs

IdentityHelper.cs

Controller

NotCheckJWTVersionAttribute.cs

JWTVersionCheckkFilter.cs

优化


JWT缺点

  1. 到期前,令牌无法被提前撤回。什么情况下需要撤回?用户被删除了、禁用了;令牌被盗用了;单设备登录。
  2. 需要JWT撤回的场景用传统Session更合适。
  3. 如果需要在JWT中实现,思路:用Redis保存状态,或者用refresh_token+access_token机制等。

方案

在用户表中增加一个整数类型的列JWTVersion,代表最后一次发放出去的令牌的版本号;每次登录、发放令牌的时候,都让JWTVersion的值自增,同时将JWTVersion的值也放到JWT令牌的负载中;当执行禁用用户、撤回用户的令牌等操作的时候,把这个用户对应的JWTVersion列的值自增;当服务器端收到客户端提交的JWT令牌后,先把JWT令牌中的JWTVersion值和数据库中JWTVersion的值做一下比较,如果JWT令牌中JWTVersion的值小于数据库中JWTVersion的值,就说明这个JWT令牌过期了。

实现

  1. 为用户实体User类增加一个long类型的属性JWTVersion。
    public class MyUser : IdentityUser<long>
    {public string? WeChatAccout { get; set; }public long JWTVersions { get; set; }
    }
  2. 修改登录并发放令牌的代码,把用户的JWTVersion属性的值自增,并且把JWTVersion的值写入JWT令牌。
  3. 编写一个操作筛选器,统一实现对所有的控制器的操作方法中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缺点 到期前&#xff0c;令牌无法被提前撤回。什么情况下需要撤回&#xff1f;用户被删除了、禁用了&#xff1b;令牌被盗用了&…...

电路研究9.3——合宙Air780EP中的AT开发指南(含TCP 示例)

根据合宙的AT研发推荐&#xff0c; AT指令基本上也简单看完了&#xff0c;这里开始转到AT的开发了。 AT 命令采用标准串口进行数据收发&#xff0c;将以前复杂的设备通讯方式转换成简单的串口编程&#xff0c; 大大简化了产品的硬件设计和软件开发成本&#xff0c;这使得几乎所…...

Reqable使用实践

一、背景 日常开发中&#xff0c;难免要抓取请求数据&#xff0c;查看接口数据&#xff0c;从而更好定位问题&#xff0c;基于这个原因&#xff0c;查找了一些抓包工具&#xff0c;例如&#xff1a; HttpCanary、 Steam 、Fiddler等&#xff0c;不是要钱&#xff0c;就是只对苹…...

【蓝桥杯嵌入式】2_LED

全部代码网盘自取 链接&#xff1a;https://pan.baidu.com/s/1PX2NCQxnADxYBQx5CsOgPA?pwd3ii2 提取码&#xff1a;3ii2 1、电路图 74HC573是八位锁存器&#xff0c;当控制端LE脚为高电平时&#xff0c;芯片“导通”&#xff0c;LE为低电平时芯片“截止”即将输出状态“锁存”…...

Flink 调用海豚调度器 SQL 脚本实现1份SQL流批一体化的方案和可运行的代码实例

目录 一、流批一体化概述 二、Flink 与海豚调度器结合实现流批一体化的好处 2.1 代码复用性增强 2.2 开发和维护成本降低 2.3 数据一致性保证 2.4 提高系统的灵活性和可扩展性 三、实现思路步骤 3.1 环境准备 3.2 编写 SQL 脚本并上传到海豚调度器 3.3 实现资源下载功…...

B树详解及其C语言实现

目录 一、B树的基本原理 二、B树操作过程图形化演示 三、B树的应用场景 四、C语言实现B树及示例 五、代码执行结果说明 六、应用实例&#xff1a;文件系统目录索引 七、总结 一、B树的基本原理 B树&#xff08;B-Tree&#xff09; 是一种自平衡的树数据结构&#xff0c;…...

【Go语言快速上手】第二部分:Go语言进阶

文章目录 并发编程goroutine&#xff1a;创建和调度 goroutinechannel&#xff1a;无缓冲 channel、有缓冲 channel、select 语句无缓冲 channel有缓冲 channelselect 语句 sync 包&#xff1a;Mutex、RWMutex、WaitGroup 等同步原语Mutex&#xff1a;互斥锁RWMutex&#xff1a…...

ARM64 Linux 内核学习指南:从基础到实践

前言 ARM64 作为当今主流的处理器架构&#xff0c;被广泛应用于移动设备、嵌入式系统和服务器领域。学习 ARM64 在 Linux 内核中的实现&#xff0c;不仅有助于深入理解操作系统底层机制&#xff0c;还能提升在内核开发、驱动编写、虚拟化等领域的专业能力。 本指南面向对 Lin…...

零基础都可以本地部署Deepseek R1

文章目录 一、硬件配置需求二、详细部署步骤1. 安装 Ollama 工具2. 部署 DeepSeek-R1 模型3. API使用4. 配置图形化交互界面&#xff08;可选&#xff09;5. 使用与注意事项 一、硬件配置需求 不同版本的 DeepSeek-R1 模型参数量不同&#xff0c;对硬件资源的要求也不尽相同。…...

掌握Spring @SessionAttribute:跨请求数据共享的艺术

SessionAttribute注解在Spring中的作用&#xff0c;就像是一个“数据中转站”。 在Web应用中&#xff0c;我们经常需要在多个请求之间共享数据。比如&#xff0c;用户登录后&#xff0c;我们需要在多个页面或请求中保持用户的登录状态。这时&#xff0c;SessionAttribute注解就…...

视频采集卡接口

采集卡的正面有MIC IN、LINE IN以及AUDIO OUT三个接口&#xff0c; MIC IN为麦克风输入&#xff0c;我们如果要给采集到的视频实时配音或者是在直播的时候进行讲解&#xff0c;就可以在这里插入一个麦克风&#xff0c; LINE IN为音频线路输入&#xff0c;可以外接播放背景音乐…...

64【32与64位程序的区别】

很多人可能有一个观念&#xff0c;那就是64位的程序NB&#xff0c;有技术含量&#xff0c;但是要说nb在哪&#xff0c;很多人又说不上来&#xff0c;本节来对这个问题做一个探讨 下图中左边的是加载的64程序&#xff0c;右边的是32位程序&#xff0c; 在上一节课我们已经理解…...

ai智能DeepSeek 在 Cursor 中的配置与应用实践

DeepSeek 是一款高效的深度搜索引擎&#xff0c;能够为开发者提供更智能、更精准的搜索体验。在数据量大、查询复杂的场景中&#xff0c;DeepSeek 能够帮助提升查询的响应速度和精确度。本文将介绍 DeepSeek 在 Cursor 中的配置与应用&#xff0c;帮助开发者理解如何在实际开发…...

Deepseek的起源与发展

文章目录 前言一、Deepseek的起源二、DeepSeek的发展脉络三、Deepseek的突破与优势(1)功能强大:核心能力与应用场景(2)性能优势:效率与效果的革命性提升四、Deepseek开源引发关注前言 DeepSeek 在网络安全领域带来的新机遇,DeepSeek 从崭露头角到引领 AI 领域的重大变革,已…...

ubuntu conda运行kivy时报“No matching FB config found”

错误描述&#xff1a;本人使用ubuntu自带的python环境运行kivy是没有问题的&#xff0c;就是在使用conda时发生了错误&#xff0c;去网上寻找报错原因&#xff0c;却一直没有头绪&#xff08;这个问题有诸多问题导致的&#xff0c;不敢说用我的这个方法100%能好&#xff09; 1…...

1-1二分查找

二分查找 1 基础版1.1 算法描述1.2 算法流程图1.3 算法实现1.3.1 Java实现 2 改动版2.1 算法描述2.2 算法流程图2.3 算法实现2.3.1 Java实现 2.4 改进点分析2.4.1 区间定义差异2.4.2 核心改进原理2.4.3 数学等价性证明 3 平衡版3.1 算法描述3.2 算法流程图3.3 算法实现3.3.1 Ja…...

【如何掌握CSP-J 信奥赛中的深搜算法】

CSP-J 信奥赛中的深搜&#xff08;深度优先搜索&#xff09;算法是一个重要知识点&#xff0c;以下是一些学习深搜算法的建议&#xff1a; 理解基础概念 定义与原理&#xff1a;深度优先搜索是一种用于遍历或搜索图、树等数据结构的算法。它从起始节点开始&#xff0c;沿着一条…...

Unity笔试常考

线程同步的几种方式 1.信号量pv操作 2.互斥加锁 3.条件变量 五层网络协议指的是哪五层 1.应用层 2.运输层 3.网络层 4.链路层 5.物理层 TCP和UDP区别 tcp 面向连接&#xff0c;保证发送顺序&#xff0c;速度慢&#xff0c;必须在线&#xff0c;三次握手&#xff0c;4次挥手…...

知识图谱智能应用系统:基于人工智能的知识提取架构

在知识图谱智能应用系统中,知识提取是将非结构化数据(如文本、文档)转化为结构化知识的关键步骤。通过人工智能技术,系统能够自动识别文本中的实体、关系、属性和事件,并将其转化为可用于知识图谱构建的三元组数据。以下是对知识提取架构的详细描述,包括环境准备、数据标…...

Qt:Qt基础介绍

目录 Qt背景介绍 什么是Qt Qt的发展史 Qt支持的平台 Qt版本 Qt的优点 Qt的应用场景 Qt的成功案例 Qt的发展前景及就业分析 Qt背景介绍 什么是Qt Qt是⼀个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供了建立艺术级图形界面所需的所有功能。它是完全面向…...

【deepSeek R1】Ollama 更改模型安装位置 以及应用安装位置

【deepSeek R1】Ollama 更改模型安装位置 以及应用安装位置 本地版部署deepSeek R1 可以参考文章 3分钟教你搭建属于自己的本地大模型 DeepSeek R1 Ollama 是一个开源工具&#xff0c;旨在帮助用户轻松在本地计算机上运行、部署和管理大型语言模型&#xff08;LLMs&#xff09;…...

让office集成deepseek,支持office和WPS办公软件!(体验感受)

导读 AIGC:AIGC是一种新的人工智能技术&#xff0c;它的全称是Artificial Intelligence Generative Content&#xff0c;即人工智能生成内容。 它是一种基于机器学习和自然语言处理的技术&#xff0c;能够自动产生文本、图像、音频等多种类型的内容。这些内容可以是新闻文章、…...

DKG(Distributed Key Generation)协议

一、DKG是什么 DKG(分布式密钥生成)提供了一种去中心化的方法,使各个参与方在不相互信任的情况下生成共享密钥,以确保安全通信和多方参与的机密性。 DKG技术的关键思想是使用多方计算(secure multiparty computation)和秘钥共享(secret sharing)的概念。 秘钥共享 则…...

动态规划问题——青蛙跳台阶案例分析

问题描述&#xff1a; 一只青蛙要跳上n级台阶&#xff0c;它每次可以跳 1级或者2级。问&#xff1a;青蛙有多少种不同的跳法可以跳完这些台阶&#xff1f; 举个例子&#xff1a; 假设台阶数 n 3 &#xff0c;我们来看看青蛙有多少种跳法。 可能的跳法&#xff1a; 1. 跳1级…...

Spring(26) spring-security-oauth2 官方表结构解析

目录 一、什么是 spring-security-oauth2&#xff1f;二、spring-security-oauth2 的表结构2.1 oauth_client_details 客户端详细信息表2.2 oauth_access_token 认证授权Token记录表2.3 oauth_refresh_token 刷新授权Token记录表2.4 oauth_code 授权Code记录表 一、什么是 spri…...

MySQL 数据库编程-C++

目录 1 数据库基本知识 1.1 MYSQL常见命令 1.2 SQL注入 1.3 ORM框架 1 数据库基本知识 MySQL 为关系型数据库(Relational Database Management System), 这种所谓的"关系型"可以理解为"表格"的概念, 一个关系型数据库由一个或数个表格组成&#xff1a…...

【大数据技术】搭建完全分布式高可用大数据集群(Flume)

搭建完全分布式高可用大数据集群(Flume) apache-flume-1.11.0-bin.tar.gz注:请在阅读本篇文章前,将以上资源下载下来。 写在前面 本文主要介绍搭建完全分布式高可用集群 Flume 的详细步骤。 注意: 统一约定将软件安装包存放于虚拟机的/software目录下,软件安装至/opt目…...

疯狂前端面试题(二)

一、Webpack的理解 Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具。Webpack 能够将各种资源&#xff08;JavaScript、CSS、图片、字体等&#xff09;视为模块&#xff0c;并通过依赖关系图将这些模块打包成一个或多个最终的输出文件&#xff08;通常是一个或几个…...

kafka专栏解读

kafka专栏文章的编写将根据kafka架构进行编写&#xff0c;即先编辑kafka生产者相关的内容&#xff0c;再编写kafka服务端的内容&#xff08;这部分是核心&#xff0c;内容较多&#xff0c;包含kafka分区管理、日志存储、延时操作、控制器、可靠性等&#xff09;&#xff0c;最后…...

深入探究 C++17 std::is_invocable

文章目录 一、引言二、std::is_invocable 概述代码示例输出结果 三、std::is_invocable 的工作原理简化实现示例 四、std::is_invocable 的相关变体1. std::is_invocable_r2. std::is_nothrow_invocable 和 std::is_nothrow_invocable_r 五、使用场景1. 模板元编程2. 泛型算法 …...