手绘10张图,把CSRF跨域攻击、JWT跨域认证说得明明白白的( 四 )


headers = {
'typ': 'JWT',
'alg': 'HS256'
}
# 构造payload
payload = {
'user_id': str(uuid.uuid4), # 自定义用户ID
'username': "wangbm", # 自定义用户名
'exp': datetime.datetime.utcnow + datetime.timedelta(minutes=5) # 超时时间,取现在时间,五分钟后token失效
}
token = jwt.encode(payload=payload, key=salt, algorithm="HS256", headers=headers).decode('utf-8')
# token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiODg4ZjIwZDktMDdlZC00MWJkLWIzMjktMTdjNmYwNThhMTRlIiwidXNlcm5hbWUiOiJ3YW5nYm0iLCJleHAiOjE1OTQ0MzQzMjZ9.kkEMhSx732lO6HWWNPNVQDHR9WuCEVxKgNol-LTbCP8
如果你只是测试使用,完全不用写那么多代码,用命令行即可
 $ pyjwt --key="minggezuishuai" encode user_id=888f20d9-07ed-41bd-b329-17c6f058a14e username=wangbm exp=+120
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiODg4ZjIwZDktMDdlZC00MWJkLWIzMjktMTdjNmYwNThhMTRlIiwidXNlcm5hbWUiOiJ3YW5nYm0iLCJleHAiOjE1OTQ0MzQ4NTl9.A792th12kY1YnBWyVgbr5l6OQ5emRiETIjsnmIl4Ji8

手绘10张图,把CSRF跨域攻击、JWT跨域认证说得明明白白的

文章插图
 
Base64URL 算法前面提到,Header 和 Payload 串型化的算法是 Base64URL 。这个算法跟 Base64 算法基本类似,但有一些小的不同 。
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx) 。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_。这就是 Base64URL 算法 。
手绘10张图,把CSRF跨域攻击、JWT跨域认证说得明明白白的

文章插图
JWT 如何保存?
关于浏览器应该将 JWT 保存在哪?这个问题,其实也困扰了我很久 。
如果使用搜索引擎去查,我相信你也一定会被他们绕晕 。
比如在这篇帖子(When and how to use it )里,作者的观点是,不应该保存在 localstorage 和 session storage,因为这样,第三方的脚本就能直接获取到 。
作者推荐的做法是,将 JWT 保存在 cookie 里,并设置 HttpOnly 。
手绘10张图,把CSRF跨域攻击、JWT跨域认证说得明明白白的

文章插图
再比如这一篇帖子(JWT(JSON Web Token) : Implementation with Node)提到了要把 JWT 保存到 local-storage 。
手绘10张图,把CSRF跨域攻击、JWT跨域认证说得明明白白的

文章插图
因此,我决定不再看网络上关于 『应将 JWT 保存的哪?』的文章 。而是自己思考,以下是我个人观点,不代表一定正确,仅供参考。
JWT 的保存位置,可以分为如下四种
  1. 保存在 localStorage
  2. 保存在 sessionStorage
  3. 保存在 cookie
  4. 保存在 cookie 并设置 HttpOnly
第一种和第二种其实可以归为一类,这一类有个特点,就是该域内的 js 脚本都可以读取,这种情况下 JWT 通过 js 脚本放入 Header 里的 Authorization 字段,会存在 XSS 攻击风险 。
第三种,与第四种相比,区别在于 cookie 有没有标记 HttpOnly,没有标记 HttpOnly 的 cookie ,客户端可以将 JWT 通过 js 脚本放入 Header 里的 Authorization 字段 。这么看好像同时存在CSRF 攻击风险和 XSS 攻击风险,实则不然,我们虽然将 JWT 存储在 cookie 里,但是我们的服务端并没有利用 cookie 里的 JWT 直接去鉴权,而是通过 header 里的 Authorization 去鉴权,因此这种方法只有 XSS 攻击风险,而没有 CSRF 攻击风险 。
而第四种,加了 HttpOnly 标记,意味着这个 cookie 无法通过js脚本进行读取和修改,杜绝了 XSS 攻击的发生 。与此同时,网站自身的 js 脚本也无法利用 cookie 设置 header 的Authorization 字段,因此只能通过 cookie 里的 JWT 去鉴权,所以不可避免还是存在 CSRF 攻击风险 。
如此看来,好像不管哪一种都有弊端,没有一种完美的解决方案 。
手绘10张图,把CSRF跨域攻击、JWT跨域认证说得明明白白的

文章插图
是的,事实也确实如此 。
所以我的观点是,开发人员应当根据实际情况来选择 JWT 的存储位置 。