秒懂JWT

简介: 秒懂JWT

JWT 英文名是 Json Web Token ,是一种用于通信双方之间传递安全信息的简洁的、URL安全的表述性声明规范,经常用在跨域身份验证。JWT 以 JSON 对象的形式安全传递信息。因为存在数字签名,因此所传递的信息是安全的。

在讲解 JWT 之前我们先来看一个问题。我们都知道 Internet 服务的身份验正过程是这样的,客户端向服务器发送登录名和登录密码,服务器验证后将对应的相关信息保存到当前会话中,这些信息包括权限、角色等数据,服务器向客户端返回 session ,session 信息都会写入到客户端的 Cookie 中,后面的请求都会从 Cookie 中读取 session 发送给服务器,服务器在收到 session 后会对比保存的数据来确认客户端身份。

但是上述模式存在一个问题,无法横向扩展。在服务器集群或者面向服务且跨域的结构中,需要数据库来保存 session 会话,实现服务器之间的会话数据共享。

在单点登录中我们会遇到上述问题,当有多个网站提供同一拨服务,那么我们该怎么实现在甲网站登陆后其他网站也同时登录呢?其中一种方法时持久化 session 数据,也就是上面所说的将 session 会话存到数据库中。这个方法的优点是架构清晰明了。但是缺点也非常明显,就是架构修改很困难,验证逻辑需要重修,并且整体依赖于数据库,如果存储 session 会话的数据库挂掉那么整个身份认证就无法使用,进而导致系统无法登录。要解决这个问题我们就用到了 JWT 。


零、JWT 简述

客户端身份经过服务器验证通过后,会生成带有签名的 JSON 对象并将它返回给客户端。客户端在收到这个 JSON 对象后存储起来。在以后的请求中客户端将 JSON 对象连同请求内容一起发送给服务器,服务器收到请求后通过 JSON 对象标识用户,如果验证不通过则不返回请求的数据。验证不通过的情况有很多,比如签名不正确、无权限等。在 JWT 中服务器不保存任何会话数据,使得服务器更加容易扩展。


一、Base64URL 算法

在讲解 JWT 的组成结构前我们先来讲解一下 Base64URL 算法。这个算法和 Base64 算法类似,但是有一点区别。我们通过名字可以得知这个算法使用于 URL 的,因此它将 Base64 中的 + 、 / 、 = 三个字符替换成了 - 、 _ ,删除掉了 = 。因为这个三个字符在 URL 中有特殊含义。


二、JWT 组成结构

JWT 是由三段字符串和两个 . 组成,每个字符串和字符串之间没有换行(类似于这样:xxxxxx.yyyyyy.zzzzzz),每个字符串代表了不同的功能,我们将这三个字符串的功能按顺序列出来并讲解:


1.JWT 头

JWT 头描述了 JWT 元数据,是一个 JSON 对象,它的格式如下:

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

这里的 alg 属性表示签名所使用的算法,JWT 签名默认的算法为 HMAC SHA256 , alg 属性值 HS256 就是 HMAC SHA256 算法。typ 属性表示令牌类型,这里就是 JWT。

2. 有效载荷

有效载荷是 JWT 的主体,同样也是个 JSON 对象。有效载荷包含三个部分:


  • 标准注册声明

标准注册声明不是强制使用是的,但是我建议使用。它一般包括以下内容:

iss:jwt的签发者/发行人;

sub:主题;

aud:接收方;

exp:jwt过期时间;

nbf:jwt生效时间;

iat:签发时间

jti:jwt唯一身份标识,可以避免重放攻击


  • 公共声明

可以在公共声明添加任何信息,我们一般会在里面添加用户信息和业务信息,但是不建议添加敏感信息,因为公共声明部分可以在客户端解密。


  • 私有声明

私有声明是服务器和客户端共同定义的声明,同样这里不建议添加敏感信息。

下面这个代码段就是定义了一个有效载荷:

{
  "exp":"201909181230",
  "role":"admin",
  "isShow":false
}

3.哈希签名

哈希签名的算法主要是确保数据不会被篡改。它主要是对前面所讲的两个部分进行签名,通过 JWT 头定义的算法生成哈希。哈希签名的过程如下:

4.指定密码,密码保存在服务器中,不能向客户端公开;

5.使用 JWT 头指定的算法进行签名,进行签名前需要对 JWT 头和有效载荷进行 Base64URL 编码,JWT 头和邮箱载荷编码后的结果之间需要用 . 来连接。

简单示例如下:

HMACSHA256(base64UrlEncode(JWT 头) + "." + base64UrlEncode(有效载荷),密码)

最终结果如下:

base64UrlEncode(JWT 头)+"."+base64UrlEncode(有效载荷)+"."+HMACSHA256(base64UrlEncode(JWT 头) + "." + base64UrlEncode(有效载荷),密码)

三、JWT 注意事项

在使用 JWT 时需要注意以下事项:


  1. JWT 默认不加密,如果要写入敏感信息必须加密,可以用生成的原始令牌再次对内容进行加密;
  2. JWT 无法使服务器保存会话状态,当令牌生成后在有效期内无法取消也不能更改;
  3. JWT 包含认证信息,如果泄露了,任何人都可以获得令牌所有的权限;因此 JWT 有效期不能太长,对于重要操作每次请求都必须进行身份验证。

四、JWT 例子

1.自定义 JWT

  • 定义 JWT 头
string jwtHeader = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
  • 定义有效载荷
string exp = GetTimeStamp(DateTime.Now.AddHours(1));
string jwtHeader = "{\"name\":\"zhangsan\",\"exp\":\"" + exp + "\",\"jti\":\"123123\"}";
  • 加密 JWT 头和有效载荷
string jwtHeaderBase64Url = Base64Url(jwtHeader);
string jwtPlayloadBase64Url = Base64Url(jwtPlayload);
  • 生成哈希签名
string signature = HMACSHA256(jwtHeaderBase64Url + "." + jwtPlayloadBase64Url,"123123");
  • 按顺序链接三部分,最终形成jwt
string jwtStr = jwtHeaderBase64Url + "." + jwtPlayloadBase64Url + "." + signature;

完整代码如下:

static void Main(string[] args)
{
    string jwtHeader = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
    string exp = GetTimeStamp(DateTime.Now.AddHours(1));
    string jwtPlayload = "{\"name\":\"zhangsan\",\"exp\":\"" + exp + "\",\"jti\":\"123123\"}";
    string jwtHeaderBase64Url = Base64Url(jwtHeader);
    string jwtPlayloadBase64Url = Base64Url(jwtPlayload);
    string signature = HMACSHA256(jwtHeaderBase64Url + "." + jwtPlayloadBase64Url,"123123");
    string jwtStr = jwtHeaderBase64Url + "." + jwtPlayloadBase64Url + "." + signature;
    Console.WriteLine(jwtStr);
    Console.ReadLine();
}
private static string HMACSHA256(string message, string key)
{
    var encoding = new System.Text.UTF8Encoding();
    byte[] keyByte = encoding.GetBytes(key);
    byte[] messageBytes = encoding.GetBytes(message);
    using (var hmacSHA256 = new HMACSHA256(keyByte))
    {
        byte[] hashMessage = hmacSHA256.ComputeHash(messageBytes);
        return BitConverter.ToString(hashMessage).Replace("-", "").ToLower();
    }
}
private static string Base64Url(string str)
{
    byte[] encodedBytes = Encoding.UTF8.GetBytes(str);
    string base64EncodedText = Convert.ToBase64String(encodedBytes);
    base64EncodedText = base64EncodedText
        .Replace("=", String.Empty)
        .Replace('+', '-')
        .Replace('/', '_');
    return base64EncodedText;
}
private static string GetTimeStamp(DateTime dt)
{
    DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1, 0, 0, 0, 0));
    DateTime nowTime = dt;
    long unixTime =
        (long)System.Math.Round((nowTime - startTime).TotalMilliseconds, MidpointRounding.AwayFromZero);
    return unixTime.ToString();
}

运行结果如下:

image.png

2.使用 .NET JWT 包

上面的代码我们在造轮子,但是 NuGet 中已经有造好的轮子了。在 NuGet 中搜索 jwt 并安装。使用 jwt 包我们只需要自定义有效载荷和密码即可,可生成三段格式的字符串

jwt 生成代码如下:

static void Main(string[] args)
{
    string exp = GetTimeStamp(DateTime.Now.AddHours(1));
    var payload = new Dictionary<string, object>
    {
        {"name", "zhangsan"},
        {"exp", exp},
        {"jti", "123123"}
    };
    IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
    IJsonSerializer serializer = new JsonNetSerializer();
    IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
    IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
    string jwtStr= encoder.Encode(payload, "123123");
    Console.WriteLine(jwtStr);
    Console.ReadLine();
}

同样,我们可以利用 jwt 包对生成的 jwt 进行解密,代码如下:

IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
var json = decoder.Decode(jwtStr, "123123", verify: true);

上述代码在项目中可以直接拿来用,只需修改其中的有效载荷和密码。

五、总结

本篇首先讲解了 jwt 的相关知识,然后通过自定义的方式和调用 jwt 包的方式手动创建了 jwt 。文章所讲的内容大家一定要切记,这些知识在开发中至关重要,在最后再补充两个小知识:


  1. 客户端发送 jwt 发送给服务器时,最好把 jwt 放在HTTP请求的Header Authorization,格式是:Authorization: Bearer jwt。
  2. jwt 不仅仅可以实现身份认证还可以在跨域 post 请求时将请求参数加入到有效载荷中,实现 post 跨域请求。

知识在开发中至关重要,在最后再补充两个小知识:

  1. 客户端发送 jwt 发送给服务器时,最好把 jwt 放在HTTP请求的Header Authorization,格式是:Authorization: Bearer jwt。
  2. jwt 不仅仅可以实现身份认证还可以在跨域 post 请求时将请求参数加入到有效载荷中,实现 post 跨域请求。


目录
相关文章
|
前端开发 JavaScript
JavaScript快捷方式:15个简写技巧,让你的代码事半功倍!
JavaScript快捷方式:15个简写技巧,让你的代码事半功倍!
|
资源调度 Ubuntu JavaScript
从零开始在一个Ubuntu服务器上部署node后端
从零开始在一个Ubuntu服务器上部署node后端
452 0
|
关系型数据库 数据库 PostgreSQL
PostgreSQL 内存表可选项 - unlogged table
标签 PostgreSQL , 内存表 , unlogged table 背景 内存表,通常被用于不需要持久化,变更频繁,访问RT低的场景。 目前社区版本PostgreSQL没有内存表的功能,postgrespro提供了两个插件可以实现类似内存表的功能。
3736 0
|
监控 JavaScript API
局域网监控软件的实时通知系统:利用Node.js和WebSocket实现即时消息推送
本文介绍了如何使用Node.js和WebSocket构建局域网监控软件的实时通知系统。实时通知对于网络安全和家庭监控至关重要,能即时发送监控数据变化的通知,提高响应速度。通过Node.js创建WebSocket服务器,当数据变化时,监控软件发送消息至服务器,服务器随即推送给客户端。此外,还展示了如何利用Node.js编写API,自动将监控数据提交到网站,便于用户查看历史记录,从而提升监控体验。
437 3
|
JavaScript 网络协议 前端开发
【Nodejs】WebSocket 全面解析+实战演练——(Nodejs实现简易聊天室)
【Nodejs】WebSocket 全面解析+实战演练——(Nodejs实现简易聊天室)
860 0
|
12月前
|
JSON Java 开发工具
Java服务端集成Google FCM推送的注意事项和实际经验
本文分享了作者在公司APP海外发布过程中,选择Google FCM进行消息推送的集成经验。文章详细解析了Java集成FCM推送的多种实现方式,包括HTTP请求和SDK集成,并指出了通知栏消息和透传消息的区别与应用场景。同时,作者还探讨了Firebase项目的创建、配置和服务端集成的注意事项,帮助读者解决文档混乱和选择困难的问题。
672 1
|
11月前
|
资源调度 前端开发 JavaScript
React 测试库 React Testing Library
【10月更文挑战第22天】本文介绍了 React Testing Library 的基本概念和使用方法,包括安装、基本用法、常见问题及解决方法。通过代码案例详细解释了如何测试 React 组件,帮助开发者提高应用质量和稳定性。
356 1
|
11月前
|
前端开发 JavaScript 测试技术
React 模拟测试与 Jest
【10月更文挑战第21天】本文介绍了如何使用 Jest 进行 React 组件的单元测试和模拟测试,涵盖了基础概念、常见问题及解决方案,并提供了实践案例。通过学习本文,你将掌握如何有效地使用 Jest 提高代码质量和稳定性。
307 1
|
JavaScript Java Android开发
kotlin安卓在Jetpack Compose 框架下跨组件通讯EventBus
**EventBus** 是一个Android事件总线库,简化组件间通信。要使用它,首先在Gradle中添加依赖`implementation &#39;org.greenrobot:eventbus:3.3.1&#39;`。然后,可选地定义事件类如`MessageEvent`。在活动或Fragment的`onCreate`中注册订阅者,在`onDestroy`中反注册。通过`@Subscribe`注解方法处理事件,如`onMessageEvent`。发送事件使用`EventBus.getDefault().post()`。
|
前端开发 JavaScript 测试技术
Jest与React Testing Library:前端测试的最佳实践
Jest和React Testing Library是React应用测试的核心工具。安装相关依赖后,在`jest.config.js`中配置Jest。测试时,编写描述性测试用例,使用`render`、`fireEvent`和`screen`来检查组件行为。Jest提供模拟功能,如模拟API调用。测试组件交互性时,模拟用户行为并验证状态变化。确保覆盖边缘情况,使用代码覆盖率报告评估测试完整性,并将测试集成到CI流程中。
201 1