首页 > 其他分享 >JWT

JWT

时间:2024-08-01 22:10:41浏览次数:6  
标签:令牌 using JWT claims var new

我喜欢你,是那种一想到你的名字,心里动辄海啸山鸣的喜欢。 --zhu
Session缺点
1、对于分布式集群环境,Session数据保存在服务器内存中就不合适了,应该放到一个中心状态服务器上。ASP.NET Core支持Session采用Redis、Memcached。
2、中心状态服务器有性能问题。

JWT
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络上以 JSON 对象的形式安全地传输信息。
JWT 通常用于在用户和服务器之间传递身份验证信息,以便在用户进行跨域访问时进行身份验证。
JWT 由三部分组成,它们用点号(.)连接在一起,形成一个紧凑的字符串。

这三部分分别是:
1、Header(头部):包含了描述 JWT 的元数据,例如令牌的类型(即JWT),以及所使用的签名算法等信息。
2、Payload(载荷):包含了有关用户或其他实体的信息,以及其他元数据。Payload 可以包含称为 “声明” 的键值对,用于描述实体的一些属性。声明分为注册声明、公共声明和私有声明。
3、Signature(签名):使用头部中指定的算法对头部和载荷进行签名,以确保数据的完整性和验证发送方的身份。签名是由编码后的头部、编码后的载荷、密钥和指定的算法生成的。

JWT 具有很多优点,例如很方便在不同的域之间进行身份验证、减少服务器端的存储压力、以及支持跨语言和跨平台使用等等。

通过本文,可以详细了解如何利用 ASP.NET Core 标识(Identity)框架生成 JWT Token。

1、JWT把登录信息(也称作令牌)保存在客户端。
2、为了防止客户端的数据造假,保存在客户端的令牌经过了签名处理,而签名的密钥只有服务器端才知道,每次服务器端收到客户端提交过来的令牌的时候都要检查一下签名。
3、基于JWT如何实现“登录”。

1)引用以下 Nuget 包:

System.IdentityModel.Tokens.Jwt

2)生成 JWT token

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

// Claim代表用户信息
// 一个Claim就代表一条用户信息
// Claim有两个主要的属性:Type 和 Value
// 它们都是 string 类型的,Type 代表用户信息的类型,Value 代表用户信息的值
// Type可以取任意值
// 不过,一般 Type 的值都取自 ClaimTypes 类中的成员
// 好处是可以更方便地与其他系统对接
// 下面代码创建了5个Claim对象
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, "6"));
claims.Add(new Claim(ClaimTypes.Name, "yzk"));
claims.Add(new Claim(ClaimTypes.Role, "User"));
claims.Add(new Claim(ClaimTypes.Role, "Admin"));
claims.Add(new Claim("PassPort", "E90000082")); //自定义的PassPort为E90000082的用户护照信息

// 对 JWT 进行签名的密钥
string key = "fasdfad&9045dafz222#fadpio@0232";

// 设置令牌的过期时间
DateTime expires = DateTime.Now.AddDays(1);

//根据过期时间、多个 Claim 对象、密钥来生成 JWT
byte[] secBytes = Encoding.UTF8.GetBytes(key);
var secKey = new SymmetricSecurityKey(secBytes);
var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256Signature); //算法
var tokenDescriptor = new JwtSecurityToken(claims: claims, expires: expires, signingCredentials: credentials);
string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
Console.WriteLine(jwt);

JWT实现登录
一、流程:

  1. 客户端向服务器端发送用户名、密码等请求登录。
  2. 服务器端校验用户名、密码,如果校验成功,则从数据库中取出这个用户的ID、角色等用户相关信息。
  3. 服务器端采用只有服务器端才知道的密钥来对用户信息的 JSON 字符串进行签名,形成签名数据。
  4. 服务器端把用户信息的 JSON 字符串和签名拼接到一起形成JWT,然后发送给客户端。
  5. 客户端保存服务器端返回的 JWT,并且在客户端每次向服务器端发送请求的时候都带上这个 JWT。
  6. 每次服务器端收到浏览器请求中携带的 JWT 后,服务器端用密钥对JWT的签名进行校验,如果校验成功,服务器端则从 JWT 中的 JSON 字符串中读取出用户的信息。

二、代码实现

  1. Nuget包
Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
  1. appsettings.json 文件,配置数据库连接字符串和JWT的密钥、过期时间
{
  "Logging": {
	"LogLevel": {
	  "Default": "Information",
	  "Microsoft.AspNetCore": "Warning"
	}
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
	"Default": "Server=(localdb)\\mssqllocaldb;Database=IdentityTestDB;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "JWT": {
	"SigningKey": "fasdfad&9045dafz222#fadpio@0232",//密钥
	"ExpireSeconds": "86400"  //过期时间(秒)
  }
}
  1. 创建JWT配置实体类 JWTOptions
public class JWTOptions
{
	public string SigningKey { get; set; }
	public int ExpireSeconds { get; set; }
}
  1. Program.cs 文件,在 builder.Build 之前,编写代码对 JWT 进行配置
// 注入 JWT 配置
services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));

// 注入 JwtBearer 配置
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
	.AddJwtBearer(x => { 
		var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
		byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
		var secKey = new SymmetricSecurityKey(keyBytes);
		x.TokenValidationParameters = new()
		{
			ValidateIssuer = false,
			ValidateAudience = false,
			ValidateLifetime = true,
			ValidateIssuerSigningKey = true,
			IssuerSigningKey = secKey
		};
	});
  1. Program.cs 文件,在 app.UseAuthorization 之前,添加身份验证中间件
// 使用 Authentication 中间件,放在 UseAuthorization 之前
app.UseAuthentication();
  1. 创建继承 IdentityRole 的 User 和 Role 实体类
using Microsoft.AspNetCore.Identity;

public class User: IdentityUser<long>
{
	public DateTime CreationTime { get; set; }
	public string? NickName { get; set; }
}

public class Role: IdentityRole<long>
{

}
  1. 创建继承 IdentityDbContext 的上下文类
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

public class IdDbContext: IdentityDbContext<User, Role, long>
{
	public IdDbContext(DbContextOptions<IdDbContext> options) : base(options)
	{

	}

	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		base.OnModelCreating(modelBuilder);
		modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
	}
}
  1. 如果数据表还没创建,执行数据库迁移命令
  2. 创建登录请求的参数实体类 LoginRequest
public record LoginRequest(string UserName, string Password);
  1. 打开登录请求控制器,编写 Login API,在其中创建 JWT
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 ASPNETCore_JWT1.Controllers
{
	[ApiController]
	[Route("[controller]/[action]")]
	public class Test1Controller : ControllerBase
	{
		private readonly UserManager<User> userManager;

		//注入 UserManager
		public Test1Controller(UserManager<User> userManager)
		{
			this.userManager = userManager;
		}

		// 生成 JWT
		private static string BuildToken(IEnumerable<Claim> claims, JWTOptions options)
		{
			DateTime expires = DateTime.Now.AddSeconds(options.ExpireSeconds);
			byte[] keyBytes = Encoding.UTF8.GetBytes(options.SigningKey);
			var secKey = new SymmetricSecurityKey(keyBytes);
			var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256Signature);
			var tokenDescriptor = new JwtSecurityToken(
				expires: expires, signingCredentials: 
				credentials, 
				claims: claims);
			var result = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor); 
			return result;
		}

		// 在方法中注入 IOptions<JWTOptions> 
		// 只需要返回 JWT Token 即可,其它的身份验证中间件会处理
		[HttpPost]
		public async Task<IActionResult> Login(
			LoginRequest req,
			[FromServices] IOptions<JWTOptions> jwtOptions)
		{
			string userName = req.UserName;
			string password = req.Password;
			var user = await userManager.FindByNameAsync(userName);
			if (user == null)
			{
				return NotFound($"用户名不存在{userName}");
			}
			if (await userManager.IsLockedOutAsync(user))
			{
				return BadRequest("LockedOut");
			}
			var success = await userManager.CheckPasswordAsync(user, password);
			if (!success)
			{
				return BadRequest("Failed");
			}
			
			var claims = new List<Claim>();
			claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
			claims.Add(new Claim(ClaimTypes.Name, user.UserName));
			var roles = await userManager.GetRolesAsync(user);
			foreach (string role in roles)
			{
				claims.Add(new Claim(ClaimTypes.Role, role));
			}

			var jwtToken = BuildToken(claims, jwtOptions.Value);
			return Ok(jwtToken);
		}
	}
}
  1. 打开其它控制器,在类上添加 [Authorize] 这个特性
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;

namespace ASPNETCore_JWT1.Controllers
{
	// [Authorize] 特性标识此控制器的方法需要身份授权才能访问
	// 授权中间件会处理其它的
	[ApiController]
	[Route("[controller]/[action]")]
	[Authorize]
	public class Test2Controller : Controller
	{
		[HttpGet]
		public IActionResult Hello()
		{
			// ControllerBase中定义的ClaimsPrincipal类型的User属性代表当前登录用户的身份信息
			// 可以通过ClaimsPrincipal的Claims属性获得当前登录用户的所有Claim信息
			// this.User.Claims
			
			string id = this.User.FindFirst(ClaimTypes.NameIdentifier)!.Value;
			string userName = this.User.FindFirst(ClaimTypes.Name)!.Value;
			IEnumerable<Claim> roleClaims = this.User.FindAll(ClaimTypes.Role);
			string roleNames = string.Join(",", roleClaims.Select(c => c.Value));
			return Ok($"id={id},userName={userName},roleNames ={roleNames}");
		}
	}
}
  1. Program.cs 文件,配置 Swagger,支持发送 Authorization 报文头
// 配置 Swagger 支持 Authorization
builder.Services.AddSwaggerGen(c => {
	var scheme = new OpenApiSecurityScheme()
	{
		Description = "Authorization header. \r\nExample: 'Bearer 12345abcdef'",
		Reference = new OpenApiReference
		{
			Type = ReferenceType.SecurityScheme,
			Id = "Authorization"
		},
		Scheme = "oauth2",
		Name = "Authorization",
		In = ParameterLocation.Header,
		Type = SecuritySchemeType.ApiKey
	};
	c.AddSecurityDefinition("Authorization", scheme);
	var requirement = new OpenApiSecurityRequirement();
	requirement[scheme] = new List<string>();
	c.AddSecurityRequirement(requirement);
});

三、运行
1.访问/Test1/Login,获取JWT Token,复制下这个值
2.然后访问/Test2/Hello,不带 JWT Token,将收到 401 信息
3.在 Swagger 上的 Authorization 输入 JWT Token,重新访问/Test2/Hello,将返回正确的结果
(如果是在 Postman 等第三方,要在 Header 上加上参数 Authorization=bearer {JWT Token})

四、总结

  1. 如果其中某个操作方法不想被验证,可以在这个操作方法上添加 [AllowAnonymous] 特性。
  2. 对于客户端获得的 JWT,在前端项目中,可以把令牌保存到 Cookie、LocalStorage 等位置,从而在后续请求中重复使用,而对于移动App、PC客户端,可以把令牌保存到配置文件中或者本地文件数据库中。当执行【退出登录】操作的时候,我们只要在客户端本地把 JWT 删除即可。
  3. 在发送请求的时候,只要按照 HTTP 的要求,把 JWT 按照 “Bearer {JWT Token}” 格式放到名字为 Authorization 的请求报文头中即可。
  4. 从 Authorization 中取出令牌,并且进行校验、解析,然后把解析结果填充到 User 属性中,这一切都是 ASP.NET Core 完成的,不需要开发人员自己编写代码。

JWT拓展
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。
2、修改登录并发放令牌的代码,把用户的JWTVersion属性的值自增,并且把JWTVersion的值写入JWT令牌。
3、编写一个操作筛选器,统一实现对所有的控制器的操作方法中JWT令牌的检查操作。把JWTValidationFilter注册到Program.cs中MVC的全局筛选器中。

优化:
每一次客户端和Controller的交互的时候,检查JWTVersion的筛选器都要查询数据库,性能太低,可以用缓存进行优化。

标签:令牌,using,JWT,claims,var,new
From: https://www.cnblogs.com/zhusichen/p/18334994

相关文章

  • 搭建 STM32 网关服务器的全流程:集成嵌入式 C++、TCP/IP 通信、Flash 存储及 JWT 认证(
    引言随着物联网(IoT)技术的快速发展,基于STM32的服务器(类似网关)在数据采集、设备控制等方面的应用越来越广泛。本文将介绍搭建一个基于STM32的服务器所需的技术栈,以及详细的搭建步骤和代码示例。技术栈介绍在搭建基于STM32的服务器时,我们需要用到以下技术栈和组件:1.硬......
  • 【Vue3】前端使用JWT令牌技术的实践方案
    目录技术介绍简单介绍:详细介绍:操作流程1.后端在登录响应里返回jwt2.前端将该变量存入浏览器当中3.前端使用jwt请求的时候作为请求头解码令牌内信息技术介绍简单介绍:        JWT令牌是一种用户校验机制,在登录后服务器会返回用户一个JWT令牌(相当于门票),用......
  • 在课堂上使用 JWT 令牌编写 pytest
    我有一个类TestSecured,其中有一些方法可以获取受保护的端点,因此您需要一个jwt来发出请求。我正在尝试优化我的测试类,这样我就不需要登录过程3次,而只需要1次,并在我的3个测试方法中使用相同的令牌。@pytest.mark.usefixtures("client","auth","setup_user_and......
  • 在 FastAPI + JWT 身份验证中如何最好地实现 is_active ?
    我的用户模型有一个字段is_active。如果为假,则说明该用户的账户被封锁。在这种情况下如何实施访问限制?我应该拒绝用户访问某些端点吗?执行此检查的最佳地点在哪里?如果is_active=False,它是否应该在get_current_user依赖项中?我的依赖项函数get_current_userasyncdefg......
  • 一文带你了解Jwt和session的区别
    全栈开发文章目录全栈开发前言Session和Jwt是什么?Session概念工作场景Jwt概念工作场景总结前言因为要重构公司的web端产品,和前端一起对了下接口。在对接用户登录的时候,发现当用户关掉浏览器后再次打开浏览器竟然不能无密码登录。然后就和前端商量了下,后端采用j......
  • jwt令牌生成和解析 + 几种数据获取方法
    ——————jwt令牌生成和解析jdk:17springboot:3.x JwtUtils.java其中StringsingKey这一部分不要太短,不然会报错packagecom.example.utils;importio.jsonwebtoken.Claims;importio.jsonwebtoken.Jwts;importio.jsonwebtoken.SignatureAlgorithm;importjava......
  • 白话理解Jwt验证
    一、jwt叫jsonwebtoken,也就是个token,这个token按照json格式在客户端与服务器之间传输。jwt的产生过程1.客户端提交用户、密码、验证码...2.服务端验证用户密码,通过后,随机成uuid,以服务端保密的secureKey,通过加密手段一般为HS512加密方式,加密成一串JSON密文,也就是jwt3......
  • JWT 没那么神秘,用它换掉 Session + Cookie 认证
    本项目代码已开源,具体见fullstack-blog。数据库初始化脚本:关注公众号程序员白彬,回复关键词“博客数据库脚本”,即可获取。背景引入在《前端轻松拿捏!最简全栈登录认证和权限设计!》一文中,我们掌握了如何基于Session+Cookie实现一个基本的登录认证功能,这是一个经得起时......
  • net core中使用jwt时,提示DenyAnonymousAuthorizationRequirement: Requires an authe
    客户端请求是401,控制台提示info:Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]Authorizationfailed.Theserequirementswerenotmet:DenyAnonymousAuthorizationRequirement:Requiresanauthenticateduser.翻遍了资料,也查不到原因,......
  • Cookie、Session、JWT在koa中的应用及实现原理
    Cookie、Session、JWT在koa中的应用及实现原理  目录Cookie重要属性实现原理cookie签名实现原理注意事项Session实现原理JWT使用方式组成实际应用实现原理前端存储方式cookiesessionlocalStoragesessionStoragetoken区别 CookieHTTP......