Dev.to 链结 https://dev.to/kevinluo201/jwt-27ng
JWT 是 JSON Web Tokens 的缩写。 最近工作上需要用 JWT 来互传资讯,可能年纪渐长...觉得这东西实在使用是满简单的 xD
只是结合多个概念,一开始不是很好懂
纪录下理解的过程
个人观点,不要打我 xD
先不提 JWT 怎麽实做的,先想像班上有一对情侣 Dustin 跟 Suzie...
两人都是 Geek,所以在教室最后一排的 Dustin 要传纸条给第一排的 Suzie 的时候,竟然是用 JSON 的格式!
{
"from": "Dustin",
"to": "Suzie",
"message": "Do you want to have dinner with me tonight?",
"place": "MacDonald's"
}
不过传纸条的过程中,班上就是会有人硬要打开不是给他的纸条,比如 Will 就是打开纸条并把内容改成
{
"from": "Dustin",
"to": "Erica", // 被 Will 改了
"message": "Do you want to have dinner with me tonight?",
"place": "MacDonald's"
}
于是不仅当晚 Dustin 只能孤零零一个人吃大麦克,Suzie 还不理他一个礼拜,悲剧。 其实一切就是他没办法控制發出去的讯息会不会被篡改。
后来 Dustin 记取教训,事先跟 Suzie 约定好一个暗号 NeverEndingStory
并用 HMAC SHA-256 这种不可逆的方式製作一串乱码,再把乱码附在要传的讯息后面
// 796e0c718cc2768edfb67a53b0f4fed74b4abbac61baaa68876630d9827714a0
Suzie 打开纸条后即可以用NeverEndingStory
以相同的方式将纸上的讯息转成乱码,再检查是否和纸上附的乱码一致。
可以任意找一个线上的 HMAC SHA256 转换器来验证 Free HMAC-SHA256 Online Generator Tool | Devglan
只要讯息跟算出来的乱码不合,即知道讯息已遭到修改或者不完全。所以这个乱码很像一个签名
JWT 就是可以给 JSON 一个签名,确保讯息没有修任何人动过。变得好像可以宣告一个 const
的 JSON 再传送出去一样
JWT 其实是一串字串,有 3 个部分,以 .
分开
可以到 JSON Web Tokens - jwt.io任意製作一个 JWT 左边即为製作出来的 JWT。
我想这时已经有人握紧拳头了...
听你鬼扯! 这个 JWT 看起来根本就只是一串乱码! 什麽 JSON、指定的演算法跑到哪裡去了?
上面的确是漏说了一些细节 😂 Header, Payload 其实是会先经由 base64 去编码 Base64 就是个编辑的方式,可以先简单理解成一个可编码及还原的方法。 如果真的很想知道 base64 是什麽,可参考另一篇文章 base64 介绍
所以红色部色就是 header,紫色部分就是 JSON 这边用 ruby 来做看看
header = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
payload = 'eyJuYW1lIjoiS2V2aW4ifQ'
require 'base64'
puts Base64.decode64(header)
# {"alg":"HS256","typ":"JWT"}
puts Base64.decode64(payload)
# {"name":"Kevin"}
# verify the signature
require 'openssl'
mac = OpenSSL::HMAC.hexdigest("SHA256", 'mySecret', "#{header}.#{payload}")
Base64.urlsafe_encode64(mac).gsub('=', '') # '=' is just padding
# "IMa4S4W1LMP1xuKVglwBagrHA5wwK9sBu-CVDKudIkg"
可能会疑惑怎麽可以直接把 payload 还原成 JSON,那 JWT 裡的资料不就大辣辣地秀出,这样不是不安全?
没错...因为 JWT 好像变成一串乱码,容易误会它很安全,其实它跟加密完全没有关係 xD
JWT 主要在乎资料是否被篡改,Signature 是否一致而已
所以别把敏感的资讯放在裡面。
我们可以先看实际使用 JWT 的程式码
因为我主要用 ruby,这边利用 ruby-jwt
这个 gem 来 demo。不过几乎所有的语言都有实作 jwt,可在 JSON Web Tokens - jwt.io 查找相关资源。
require 'jwt'
payload = {
first_name: 'Kevin',
last_name: 'Luo'
}
secret = "my secret"
token = JWT.encode payload, secret, 'HS256'
# "eyJhbGciOiJIUzI1NiJ9.eyJmaXJzdF9uYW1lIjoiS2V2aW4iLCJsYXN0X25hbWUiOiJMdW8ifQ.dZJnejsQ9cWs1hyOvCAij_Q4k87vfbQpeBIjgqYCrgs"
decoded_token = JWT.decode token, secret, true, { algorithm: 'HS256' }
# [{"first_name"=>"Kevin", "last_name"=>"Luo"}, {"alg"=>"HS256"}]
此外,JWT 的格式 RFC 其实有约定一些参数可以设定,不过端看程式有没有做对应的处理,
一个常用的是「到期时间」 exp
,设定一个到期时间给 JWT, 假使真的到期,decode 时即丢出JWT::ExpiredSignature
这个 Exception
require 'jwt'
exp = Time.now.to_i + 3600 # 1 hour
exp_payload = { data: payload, exp: exp }
token = JWT.encode exp_payload, secret, 'HS256'
# "eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjp7ImZpcnN0X25hbWUiOiJLZXZpbiIsImxhc3RfbmFtZSI6Ikx1byJ9LCJleHAiOjE2MTM4ODU4MjF9.1_NIKXDnBVz1G6Li7_CZbcDwIk5AFaOsreK7BFDS13Q"
begin
decoded_token = JWT.decode token, secret, true, { algorithm: ‘HS256’ }
# [{"data"=>{"first_name"=>"Kevin", "last_name"=>"Luo"}, "exp"=>1613885821}, {"alg"=>"HS256"}]
rescue JWT::ExpiredSignature
# 过期
end
JWT 除了可以让 2 台有共同 secret 的电脑可互传确认不会被篡改的资料外,
最常见的情境应该就是后端 API 使用者登入后發给前端的 session token 了吧
会用 API 的 Request 的 header 中裡的Authorization Bearer [TOKEN]
来判断来源是否可以取用该 API
我觉得用 JWT 当 session token 应该有 2 个优点:
目前就只用过这 2 种应用,不知道还有什麽特别的用途囉?
JWT 的分享到此囉 : )