4

JWT 简介与 C# 示例 - 橙子家

 8 months ago
source link: https://www.cnblogs.com/hnzhengfy/p/JMZDS_jwt.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

〇、什么是 JWT ?

JWT,即 JSON Web Token,是一种基于 JSON 的开放标准(RFC 7519),主要用于在网络应用环境间安全地传递声明。这种声明被进行了数字签名,可以验证和信任,因此,它适用于各种需要信息安全性和无状态的应用。

在具体加密过程中,客户端会使用 RSA 算法生成 JWT 串,这里用到了私钥“加密”,而公钥是公开的,任何人都能解密,但内容无法变更。也就是说,在 JWT 中并没有纯粹的加密过程,而是通过加密保障了信息的完整性和真实性。

适用场景:

  • 用户认证:当用户成功登录后,服务器会生成一个 JWT 令牌并返回给客户端,此后客户端只需携带这个令牌即可访问服务器提供的资源。
  • 一次性验证:比如,用户注册后发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备能够标识用户、具有时效性、不能被篡改以及一次性的特性。这种场景就适合使用 JWT。
  • 防止传输数据篡改:即使数据在传输过程中被截获,由于 JWT 可以使用加密算法对传输内容进行签名,因此很难同时篡改签名和传输内容。
  • 更少的数据库连接:因其基于算法来实现身份认证,在使用 JWT 时查询数据的次数更少,可以获得更快的系统响应时间。
  • 构建更简单:如果你的应用程序本身是无状态的,那么选择 JWT 可以加快系统构建过程。
  • 跨服务调用:可以通过构建一个认证中心,来处理用户身份认证和发放签名的工作,其他应用服务在后续的用户请求中,可使用自有的公钥对用户签名进行验证。
  • 无状态:你不需要向传统的 Web 应用那样将用户状态保存于 Session 中。
  • 安全性:由于 JWT 的 Payload 中负载信息,是使用 base64Url 编码的,并没有加密,因此 JWT 中不能存储敏感数据
  • 一次性:无状态是 JWT 的特点,但也导致了 JWT 是一次性的。想修改里面的内容,就必须签发一个新的 JWT
  • 严重依赖于秘钥:JWT 的生成与解析过程都需要依赖于秘钥(Secret),且都以硬编码的方式存在于系统中或配置里。如果秘钥泄露,系统的安全性将受到严重威胁。
  • 服务端无法管理客户端的信息:如果用户身份发生异常(信息泄露或者被攻击),服务端很难将异常用户进行隔离。
  • 服务端无法主动推送消息:服务端由于是无状态的,就无法推送消息到客户端。例如过期时间将至,服务端无法主动为用户续约,需要客户端向服务端发起续约请求。
  • 冗余的数据开销:一个 JWT 签名的大小要远比一个 Session ID 长很多,如果你对有效载荷(payload)中的数据不做有效控制,其长度会成几何倍数增长,且在每一次请求时都需要负担额外的网络开销。

一、JWT 的组成

下边是一个示例密文 token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IuW8oOS4iSIsImVtYWlsIjoiemhhbmdzYW5AZXhhbXBsZS5jb20iLCJ0ZW1wa2V5IjoidGVtcHZhbHVl5YC8IiwibmJmIjoxNzAyODg2MjgxLCJleHAiOjE3MDI4ODYyOTEsImlhdCI6MTcwMjg4NjI4MX0.2nuyYrAxVq3aAReN257eMHKGG44j5QyPMabxMnSzVBU

密文起始就是,看起来是非常复杂,实际上有章可循的,如下图,密文以其中的两个句点为分隔,可分为三个部分:Header、Payload、Signature

1868241-20231218111606612-693191663.png

1.1 Header 头信息

Header 的主要作用是用来标识。通常是两部分组成:

  • typ:type 的简写,令牌类型,也就是 JWT。
  • alg:Algorithm 的简写,加密签名算法。

alg 参数 JWT 官网提供了 12 种算法,如下图,但一般都采用 HS256

  1868241-20231218114218694-978605193.png

明文示例:

{ "alg": "HS256", "typ": "JWT"}

经过 Base64Url 编码后,就是密文中第一部分的内容:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

1.2 Payload 有效负载

Payload 是 JWT 密文中的重要组成部分,也可称为 JWT claims,它包含了需要传递的数据信息,解密后的数据格式也是 Json。

claims 可以分为三种类型:registered(预定义声明)、public(公共声明)、private(私有声明)。

registered(预定义声明):不是强制性的,但推荐使用,以提供一组有用的、可互操作的声明。

  其中主要包括四个:iss(issuer,发送数据人,标识发送 JWT 主体)、exp(expiration time,数据消息的过期时间,一般采用时间戳格式)、sub(subject,数据消息主题)、aud(audience,数据消息的接收者)。

public(公共声明):公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息。一般不建议添加敏感信息,因为该部分在任何客户端均可解密。

private(私有声明):私有声明是提供者和消费者所共同定义的声明

注:claims 声明名称一般只有三个字符长,因为 JWT 的目的是精简。

{ "unique_name": "张三", "email": "[email protected]", "tempkey": "tempvalue值"}

经过 Base64Url 加密后,得到密文的第二部分内容:

eyJ1bmlxdWVfbmFtZSI6IuW8oOS4iSIsImVtYWlsIjoiemhhbmdzYW5AZXhhbXBsZS5jb20iLCJ0ZW1wa2V5IjoidGVtcHZhbHVl5YC8IiwibmJmIjoxNzAyODg2MjgxLCJleHAiOjE3MDI4ODYyOTEsImlhdCI6MTcwMjg4NjI4MX0

1.3 signature 签名信息

Signature 部分是对 Header 和 Payload 两部分的签名,作用是防止 JWT 被篡改

要创建签名部分,前提是必须获取编码后的 Header、编码后的有效负载 Payload、secret 密钥、标头中 alg 指定的算法,一般为 HS256,然后才能对其进行签名。

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

加密后得到的密文就是 Token 中最后一部分内容。

密钥 secret 是保存在服务端的,服务端会根据这个密钥进行生成 token 和验证,所以需要严格保密

二、JWT 身份验证的流程简介

JWT 经常用于身份验证流程,以下是一个简单的步骤:

  1. 用户首次登录,输入账号密码,请求登录接口 /users/login。
  2. 服务端验证登陆信息,并通过密钥创建 JWT 凭证。
  3. 服务端返回 JWT 凭证到浏览器,浏览器进行缓存。
  4. 用户操作触发请求,浏览器会自动将 JWT 凭证加入到请求的 Header 中。
  5. 服务端接收到请求,先判断 JWT 凭证的有效性。
  6. 若 JWT 凭证有效,则正常返回的请求结果数据;若校验不通过,则提示用户重新进行身份验证。
1868241-20231220152502547-1882034517.png

另外,在日常业务中,有可能出现并发异常问题。

当服务端在检查到请求的令牌过期之后,会提示用户再次做登录操作。

这在流程上没什么问题,但在页面加载后,如果同一个页面中有多个异步请求同时触发,每一个请求都携带原始令牌,在这样的设计下,就有可能出现在第一个请求到达后刷新了 Token,并更改了缓存中数据的时间戳,以至于剩余请求校验时发现时间戳不一致导致验证失败。

同一时间触发的请求越多,抛出的异常也就越多。虽然第一个请求已经刷新了 Token,但是其余的请求是失败的,页面中的数据并不完整,显然这是不正常的。那如何避免呐?

redis 锁机制:在触发更新 Token 时,将同一用户信息加锁,使得此用户的其他请求均失败,待登录验证通过后再重新加载。

Token 定时刷新:当用户在线时,间隔一段时间刷新一次 Token。要刷新令牌,API 需要一个新的端点,它接收一个有效的,没有过期的 JWT,并返回与新的到期字段相同的签名的 JWT。若用户长时间没登录,则直接跳转到登录页。

三、C# 简单实现

直接看代码吧。

// 测试一下class Program{ static void Main(string[] args) { JwtTest jwtTest= new JwtTest(); string key = "keayvkkakeyvaluyeaeayvalalujeehayvalguaealrue"; var claims = new[] { new Claim(ClaimTypes.Name, "张三"), new Claim(ClaimTypes.Email, "[email protected]"), new Claim("tempkey", "tempvalue值"), }; string token = jwtTest.JwtEncode(key, claims); Console.WriteLine(token); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IuW8oOS4iSIsImVtYWlsIjoiemhhbmdzYW5AZXhhbXBsZS5jb20iLCJ0ZW1wa2V5IjoidGVtcHZhbHVl5YC8IiwibmJmIjoxNzAzNTkxMTk4LCJleHAiOjE3MDM1OTEyMDgsImlhdCI6MTcwMzU5MTE5OH0.65Mx_ldbQijHevkalutHMaejQ06vhe5fW6e6-t-aziw string json = jwtTest.JwtDecode(token, key); // {"unique_name":"张三","email":"[email protected]","tempkey":"tempvalue值","nbf":1703591258,"exp":1703591268,"iat":1703591258} Console.WriteLine(json); }}

验证一下:https://jwt.io/

1868241-20231226195325294-1583050172.png
// JWT 加密解密类public class JwtTest{ public string JwtEncode(string keyvalue, Claim[] claims) { var key = Encoding.UTF8.GetBytes(keyvalue); var tokenHandler = new JwtSecurityTokenHandler(); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddSeconds(10), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); var tokenString = tokenHandler.WriteToken(token); Console.WriteLine($"加密后的JWT: {tokenString}"); return tokenString; } public string JwtDecode(string jwttoken, string publickey) { try { IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJsonSerializer serializer = new JsonNetSerializer(); IDateTimeProvider provider = new UtcDateTimeProvider(); IJwtValidator validator = new JwtValidator(serializer, provider); IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); var json = decoder.Decode(jwttoken, publickey, verify: true); return json; } catch(Exception ex) { return ""; } }}

参考:https://jwt.io/introduction  https://juejin.cn/post/7232550589964140602  https://cloud.tencent.com/developer/article/2148676


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK