jwt javascript(【2023】前端JWT详解)
概述
回顾登录的流程:
接下来的问题是:这个出入证(令牌)里面到底存啥?
一种比较简单的办法就是直接存储用户信息的JSON串 ,这会造成下面的几个问题:
非浏览器环境 ,如何在令牌中记录过期时间 如何防止令牌被伪造JWT就是为了解决这些问题出现的 。
JWT全称Json Web Token,本质就是一个字符串
它要解决的问题 ,就是在互联网环境中 ,提供统一的 、安全的令牌格式
因此 ,jwt只是一个令牌格式而已 ,你可以把它存储到cookie ,也可以存储到localstorage ,没有任何限制!
同样的 ,对于传输 ,你可以使用任何传输方式来传输jwt ,一般来说,我们会使用消息头来传输它
比如 ,当登录成功后 ,服务器可以给客户端响应一个jwt:
HTTP/1.1 200 OK ... set-cookie:token=jwt令牌 authentication:jwt令牌 ... {..., token:jwt令牌}可以看到,jwt令牌可以出现在响应的任何一个地方 ,客户端和服务器自行约定即可 。
当然 ,它也可以出现在响应的多个地方,比如为了充分利用浏览器的cookie ,同时为了照顾其他设备 ,也可以让jwt出现在set-cookie和authorization或body中 ,尽管这会增加额外的传输量 。
当客户端拿到令牌后 ,它要做的只有一件事:存储它 。
你可以存储到任何位置 ,比如手机文件 、PC文件、localstorage 、cookie
当后续请求发生时 ,你只需要将它作为请求的一部分发送到服务器即可 。
虽然jwt没有明确要求应该如何附带到请求中 ,但通常我们会使用如下的格式:
GET /api/resources HTTP/1.1 ... authorization: bearer jwt令牌 ...这样一来 ,服务器就能够收到这个令牌了 ,通过对令牌的验证,即可知道该令牌是否有效 。
它们的完整交互流程是非常简单清晰的
令牌的组成
为了保证令牌的安全性 ,jwt令牌由三个部分组成 ,分别是:
header:令牌头部,记录了整个令牌的类型和签名算法 payload:令牌负荷 ,记录了保存的主体信息 ,比如你要保存的用户信息就可以放到这里 signature:令牌签名,按照头部固定的签名算法对整个令牌进行签名 ,该签名的作用是:保证令牌不被伪造和篡改它们组合而成的完整格式是:header.payload.signature
比如 ,一个完整的jwt令牌如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9.BCwUy3jnUQ_E6TqCayc7rCHkx-vxxdagUwPOWqwYCFc它各个部分的值分别是:
header:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 payload:eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9 signature: BCwUy3jnUQ_E6TqCayc7rCHkx-vxxdagUwPOWqwYCFc下面分别对每个部分进行说明
header
它是令牌头部 ,记录了整个令牌的类型和签名算法
它的格式是一个json对象 ,如下:
{ "alg":"HS256", "typ":"JWT" }该对象记录了:
alg:signature部分使用的签名算法 ,通常可以取两个值 HS256:一种对称加密算法 ,使用同一个秘钥对signature加密解密 RS256:一种非对称加密算法 ,使用私钥签名 ,公钥验证 typ:整个令牌的类型 ,固定写JWT即可设置好了header之后,就可以生成header部分了
具体的生成方式极其简单 ,就是把header部分使用base64 url编码即可
base64 url不是一个加密算法 ,而是一种编码方式,它是在base64算法的基础上对+ 、= 、/三个字符做出特殊处理的算法
而base64是使用64个可打印字符来表示一个二进制数据 ,具体的做法参考百度百科
浏览器提供了btoa函数 ,可以完成这个操作:
window.btoa(JSON.stringify({ "alg":"HS256", "typ":"JWT" })) // 得到字符串:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9同样的,浏览器也提供了atob函数 ,可以对其进行解码:
window.atob("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9") // 得到字符串:{"alg":"HS256","typ":"JWT"}nodejs中没有提供这两个函数 ,可以安装第三方库atob和bota搞定
或者 ,手动搞定
payload
这部分是jwt的主体信息 ,它仍然是一个JSON对象 ,它可以包含以下内容:
{ "ss":"发行者", "iat":"发布时间", "exp":"到期时间", "sub":"主题", "aud":"听众", "nbf":"在此之前不可用", "jti":"JWT ID" }以上属性可以全写 ,也可以一个都不写 ,它只是一个规范 ,就算写了 ,也需要你在将来验证这个jwt令牌时手动处理才能发挥作用
上述属性表达的含义分别是:
ss:发行该jwt的是谁,可以写公司名字 ,也可以写服务名称 iat:该jwt的发放时间 ,通常写当前时间的时间戳 exp:该jwt的到期时间,通常写时间戳 sub:该jwt是用于干嘛的 aud:该jwt是发放给哪个终端的 ,可以是终端类型 ,也可以是用户名称,随意一点 nbf:一个时间点 ,在该时间点到达之前 ,这个令牌是不可用的 jti:jwt的唯一编号 ,设置此项的目的 ,主要是为了防止重放攻击(重放攻击是在某些场景下 ,用户使用之前的令牌发送到服务器 ,被服务器正确的识别 ,从而导致不可预期的行为发生)可是到现在 ,看了半天 ,没有出现我想要写入的数据啊😂
当用户登陆成功之后,我可能需要把用户的一些信息写入到jwt令牌中 ,比如用户id 、账号等等(密码就算了😳)
其实很简单 ,payload这一部分只是一个json对象而已,你可以向对象中加入任何想要加入的信息
比如 ,下面的json对象仍然是一个有效的payload
{ "foo":"bar", "iat":1587548215 }foo: bar是我们自定义的信息 ,iat: 1587548215是jwt规范中的信息
最终,payload部分和header一样 ,需要通过base64 url编码得到:
window.btoa(JSON.stringify({ "foo":"bar", "iat":1587548215 })) // 得到字符串:eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9signature
这一部分是jwt的签名 ,正是它的存在 ,保证了整个jwt不被篡改
这部分的生成 ,是对前面两个部分的编码结果 ,按照头部指定的方式进行加密
比如:头部指定的加密方法是HS256 ,前面两部分的编码结果是eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9
则第三部分就是用对称加密算法HS256对字符串eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9进行加密 ,当然你得指定一个秘钥 ,比如shhhhh
HS256(`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9`, "shhhhh") // 得到:BCwUy3jnUQ_E6TqCayc7rCHkx-vxxdagUwPOWqwYCFc最终 ,将三部分组合在一起,就得到了完整的jwt
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9.BCwUy3jnUQ_E6TqCayc7rCHkx-vxxdagUwPOWqwYCFc由于签名使用的秘钥保存在服务器 ,这样一来 ,客户端就无法伪造出签名,因为它拿不到秘钥 。
换句话说 ,之所以说无法伪造jwt ,就是因为第三部分的存在 。
而前面两部分并没有加密,只是一个编码结果而已 ,可以认为几乎是明文传输
这不会造成太大的问题 ,因为既然用户登陆成功了 ,它当然有权力查看自己的用户信息
甚至在某些网站 ,用户的基本信息可以被任何人查看
你要保证的 ,是不要把敏感的信息存放到jwt中 ,比如密码
jwt的signature可以保证令牌不被伪造 ,那如何保证令牌不被篡改呢?
比如 ,某个用户登陆成功了 ,获得了jwt,但他人为的篡改了payload ,比如把自己的账户余额修改为原来的两倍 ,然后重新编码出payload发送到服务器,服务器如何得知这些信息被篡改过了呢?
这就要说到令牌的验证了
令牌的验证
令牌在服务器组装完成后 ,会以任意的方式发送到客户端
客户端会把令牌保存起来 ,后续的请求会将令牌发送给服务器
而服务器需要验证令牌是否正确,如何验证呢?
首先 ,服务器要验证这个令牌是否被篡改过 ,验证方式非常简单 ,就是对header+payload用同样的秘钥和加密算法进行重新加密
然后把加密的结果和传入jwt的signature进行对比 ,如果完全相同 ,则表示前面两部分没有动过 ,就是自己颁发的 ,如果不同 ,肯定是被篡改过了 。
传入的header.传入的payload.传入的signature 新的signature = header中的加密算法(传入的header.传入的payload, 秘钥) 验证:新的signature == 传入的signature当令牌验证为没有被篡改后 ,服务器可以进行其他验证:比如是否过期 、听众是否满足要求等等,这些就视情况而定了
注意:这些验证都需要服务器手动完成 ,没有哪个服务器会给你进行自动验证 ,当然,你可以借助第三方库来完成这些操作
总结
最后 ,总结一下jwt的特点:
jwt本质上是一种令牌格式。它和终端设备无关 ,同样和服务器无关,甚至与如何传输无关 ,它只是规范了令牌的格式而已 jwt由三部分组成:header 、payload 、signature 。主体信息在payload jwt难以被篡改和伪造 。这是因为有第三部分的签名存在。面试题
请阐述JWT的令牌格式
参考答案:
token 分为三段 ,分别是 header 、payload 、signature
其中 ,header 标识签名算法和令牌类型;payload 标识主体信息 ,包含令牌过期时间、发布时间 、发行者 、主体内容等;signature 是使用特定的算法对前面两部分进行加密 ,得到的加密结果 。
token 有防篡改的特点 ,如果攻击者改动了前面两个部分 ,就会导致和第三部分对应不上 ,使得 token 失效 。而攻击者不知道加密秘钥 ,因此又无法修改第三部分的值。
所以,在秘钥不被泄露的前提下 ,一个验证通过的 token 是值得被信任的 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!