协议标准:https://tools.ietf.org/html/rfc7519

jwt.io:https://jwt.io

开箱即用:https://jwt.io/#libraries

前言

最近网站后台迎来第三次改版,原来采用的是jquery+bootstrap这样常规的方式,但是随着网站的交互越来越多,信息量越来越大,就非常力不从心了,每次写动态交互都好痛苦。趁着这次机会,决定采用MVVM的新JS框架,最终评估选择vue.js大礼包,没错!正因为如此,前后端实现了完全分离,就不能采用session这样简单的登陆校验机制了,取而代之的是令牌+RESTful的方式进行交互,此时JWT闪亮登场!

什么是JWT?

JWT(Json Web Token)是一个开放标准(RFC 7519),它基于json对象定义了一种紧凑并且自包含的方式进行安全信息传输。由于消息经过了数字签名,所以是可以被校验和信任的。另外JWT可以使用密匙,或者使用RSA的公钥/私钥进行签名。

其中的一些概念:

  • 紧凑:由于其较小的尺寸,JWT可通过URL,POST参数或HTTP标头内发送。 另外,较小的尺寸意味着传输速度很快。
  • 自包含:JWT的数据中可以包含用户的必要信息,避免了多次查询数据库的情况。

为什么使用JWT?

session认证

因为http本身是无状态的协议,所以每一次的请求其实都要校验,session的原理就初次登陆的时候将相关信息保存到服务端,响应一个cookie保存到客户端,这样每次请求都携带cookie,服务器能够实现校验,这会面临3个问题

1、难以实现单点登录,除非不同服务器之间共享session

2、session默认保存在服务端,增加服务器的存储压力

3、API调试麻烦

OAuth 2.0

OAuth 一般用于第三方接入的场景,管理对外的权限,比如什么第三方登录,微信授权,开放平台等,类似这些更加严谨的场景,相对来说也更加安全,但是部署过程复杂,授权流程也是麻烦,感觉是有些小题大做。而JWT更适用于类似RESTful API(微服务)之间的交互。

自建token协议

这种情况当然最灵活,但是除非有雄厚的资金实例,多余的时间和必要的情况,否则没必要重复造轮子呐。

曾经我们还用过简单的办法,登陆之后根据用户信息进行加盐hash,该hash值即为token,然后以(hash,value)的形式存储在缓存或者数据库中,每次请求携带hash,然后读取校验该hash是否存在,否则校验失败。这种方式也不失为一种简单快捷的好办法,但是仅仅只能当做token校验,并且相关数据存储在服务器,每次访问都还需要进行一次查询,增加服务器开销

什么时候使用JWT?

下面是一些JWT有用的场景

1、身份校验

这是最常见的的使用场景,一旦用户完成了登陆校验,后面每一次的请求豆浆携带JWT,从而校验用户是否允许访问路由、服务、资源。更重要的是,通过JWT可以非常容易实现SSO(Single Sign On)单点登录,因为开销很小,这就意味着,在一个主站登陆了,别的站点就都可以轻松使用JWT访问。

2、信息交换

从上文可知,JWT是能够被签名的的,所以在安全信息传输中,是一个不错的方案,例如使用公钥私钥时,你可以确定收件人是谁,另外还可以校验确保内容是否被篡改。这样,就可以在一些类似下单、交易等等重要的场合使用。

JWT的基本结构



JWT由三部分组成,他们中间由.分隔:

  • Header 头部
  • Payload 数据
  • Signature 签名

因此,典型的JWT看起来是这样的

xxxxx.yyyyy.zzzzz

Header

头部主要包含2个部分,token类型和采用的加密算法。

  1. {
  2. "alg": "HS256",
  3. "typ": "JWT"
  4. }

然后用Base64Url进行编码,就成了JWT的第一个部分

Payload

数据部分包含了主要的声明字段以及相应的值,声明主要包括3种类型:reserved , public 和 private

  • Reserved claims: 这些字段是JWT预先定义的,在JWT中并不会强制使用它们,而是推荐使用。

    常用的有:
  1. iss(issuer): jwt签发者
  2. sub(subject): 签发的项目
  3. aud(audience): 接收jwt的一方
  4. exp(exipre): jwt的过期时间,这个过期时间必须要大于签发时间
  5. nbf(not before): 定义在什么时间之前,该jwt是不可用的.
  6. iat(issued at): jwt的签发时间
  7. jti(jwt token id): jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

需要注意的是,声明名称只有三个字符长度,这是为了让JWT保持紧凑

  • Public claims:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.
  • Private claims:私有声明是提供者和消费者所共同定义的声明

简单示例如下:

  1. {
  2. "iss": "www",
  3. "iat": 1441593502,
  4. "exp": 1441594722,
  5. "aud": "www.example.com",
  6. "sub": "www@example.com",
  7. "from_user": "B",
  8. "target_user": "A"
  9. }

然后用Base64Url进行编码,就成了JWT的第二个部分

Signature

为了创建签名,你需要先对前面的部分进行Base64的编码,然后加上私匙,对其进行签名。

例如,你想使用HMAC SHA256算法进行前面,那么创建过程如下:

  1. HMACSHA256(
  2. base64UrlEncode(header) + "." +
  3. base64UrlEncode(payload),
  4. secret)

签名的目的是为了校验JWT的携带者信息,并且检验是否有篡改过所携带的JWT信息。

HMAC SHA256算法计算之后的二进制数据默认进行Base64编码,就是JWT的第三个部分了

将他们放在一起

最终的结果是三段Base64字符串,通过.拼接在一起,这样就很容易在HTML和HTTP环境中传输,与基于XML的标准相比,更加紧凑节省资源。

  1. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

调试工具:https://jwt.io/#debugger-io

项目实践JWT

后端

项目使用的是基于php的thinkphp5.0框架作为后端提供服务。前端则是vue+element-ui+axios,至于php类库,采用的是php中Star最多的

https://github.com/lcobucci/jwt

后端php通过composer安装之后使用起来非常的简单,新建一个类专门用于校验

  1. use Lcobucci\JWT\Builder;
  2. use Lcobucci\JWT\Parser;
  3. use Lcobucci\JWT\Signer\Hmac\Sha256;
  4. use Lcobucci\JWT\ValidationData;
  5. class Auth
  6. {
  7. const KEY = 'febcbaae13751fa2ds44c2f107afb08d';
  8. const VALID_INFO = [
  9. 'Issuer' => 'http://www.xxxx.com',
  10. 'Audience' => 'http://aaa.xxxx.com',
  11. 'Subject' => 'test',
  12. 'Expire' => 259200
  13. ];
  14. public static function check()
  15. {
  16. $jwt = request()->header('jwt');
  17. $valid = new ValidationData();
  18. $valid->setIssuer(self::VALID_INFO['Issuer']);
  19. $valid->setAudience(self::VALID_INFO['Audience']);
  20. $valid->setSubject(self::VALID_INFO['Subject']);
  21. //校验jwt信息,同时校验签名,否则可以伪造信息
  22. $signer = new Sha256();
  23. if ($jwt->validate($valid) && $jwt->verify($signer, self::KEY)) {
  24. $uinfo = $jwt->getClaim('uinfo');
  25. //取出数据的时候是对象而不是数组
  26. $uinfo->id
  27. //后续的权限校验过程……
  28. }
  29. }
  30. public static function getSignedJWT($userinfo)
  31. {
  32. $signer = new Sha256();
  33. $token = (new Builder())
  34. ->setIssuer(self::VALID_INFO['Issuer'])
  35. ->setAudience(self::VALID_INFO['Audience'])
  36. ->setSubject(self::VALID_INFO['Subject'])
  37. ->setIssuedAt(time())
  38. ->setExpiration(time() + self::VALID_INFO['Expire'])
  39. //可以直接保存数组或对象
  40. ->set('uinfo', $userinfo)
  41. ->sign($signer, self::KEY)
  42. ->getToken()->__toString();
  43. return $token;
  44. }
  45. }

前端

登陆的时候保存JWT到localStorage,退出登录时前端删除保存的JWT即可。

  1. apiLogin.login(this.$data.loginForm).then(res => {
  2. if (res.data.ret === 0) {
  3. this.$local.set('jwt', res.data.jwt)
  4. this.$local.set('menu', res.data.menu)
  5. this.$local.set('rules', res.data.rules)
  6. this.$local.set('username', this.loginForm.username)
  7. this.$local.set('title', res.data.title)
  8. this.$local.set('gpid', res.data.gpid)
  9. this.$router.push('index')
  10. // 原本没有jwt,所以登陆获取之后手动设置一次
  11. this.$http.defaults.headers.common['jwt'] = this.$local.get('jwt')
  12. } else {
  13. this.isLogining = false
  14. this.$message.error(res.data.msg)
  15. }
  16. }).catch(() => {
  17. this.isLogining = false
  18. })

base_api.js

  1. import axios from 'axios'
  2. import { Message } from 'element-ui'
  3. import local from 'store'
  4. // Add a request interceptor
  5. axios.interceptors.request.use(function (config) {
  6. return config
  7. }, function (error) {
  8. Message.error({
  9. showClose: true,
  10. message: '网络异常,请检查您的网络'
  11. })
  12. console.log(error)
  13. // Do something with request error
  14. return Promise.reject(error)
  15. })
  16. // Add a response interceptor
  17. axios.interceptors.response.use(function (response) {
  18. // 授权过期,无授权信息,跳出登陆
  19. if (response.data.ret === 4011 || response.data.ret === 4013) {
  20. window.location.href = '/#/login'
  21. // 删除本地的token令牌
  22. local.remove('jwt')
  23. Message.error({
  24. showClose: true,
  25. message: response.data.msg
  26. })
  27. return
  28. }
  29. if (response.data.ret === 4012) {
  30. // 无权限返回
  31. window.history.back()
  32. Message.error({
  33. showClose: true,
  34. message: response.data.msg
  35. })
  36. return
  37. }
  38. return response
  39. }, function (error) {
  40. Message.error({
  41. showClose: true,
  42. message: '网络异常,请检查您的网络'
  43. })
  44. return Promise.reject(error)
  45. })
  46. const baseUrl = process.env.API_ROOT
  47. axios.defaults.baseURL = baseUrl
  48. // 初始化的时候加载本地储存过的jwt
  49. if (local.get('jwt')) {
  50. axios.defaults.headers.common['jwt'] = local.get('jwt')
  51. }
  52. export const http = axios

关于安全性

Cookie 可以启用 HttpOnly 和 Secure:

  • HttpOnly:禁止浏览器的 JavaScript 环境访问 Cookie,防御针对 Cookie 的 XSS。
  • Secure:Cookie 只在 HTTPS 请求中被传输。

但是为了实现正真意义上的无状态和跨域单点,还是坚持存储在LocalStorage,而目前localStorage存储没有对XSS攻击有任何抵御机制,一旦出现XSS漏洞,那么存储在localStorage里的数据就极易被获取到。

如果一个网站存在XSS漏洞,那么攻击者注入如下代码,就可以获取使用localStorage存储在本地的所有信息。



所以务必做好过滤安全检查。

总结

1、JWT并不包含权限校验部分,只包含Token校验,所以在Token校验完成之后,权限部分还需自行校验一次。

2、jwt的payload数据部分不要存放敏感信息,此部分是任何人都可以解密查看的,而jwt主要依靠签名校验身份,同时也不建议存放易改动的信息,否则需要token过期或者重新登录才能来获取最新的信息。

3、签名所用的secret私匙一定要保管好!!!

4、务必使用https,否则用户被截获到token,就可以进行伪造攻击。

5、JWT使用的场景中,一般是要跨域的,所以服务端需要做好CORS的策略支持。见这里

6、若需要强制过期JWT,则在用户表新建一个签名时间字段即可,在登陆的时候检查,若JWT保存签名时间小于服务器签名时间,即强制过期

参考

1 2 3 4 5

JWT(Json Web Token)初探与实践的更多相关文章

  1. Java JWT: JSON Web Token

    Java JWT: JSON Web Token for Java and Android JJWT aims to be the easiest to use and understand libr ...

  2. 如何在SpringBoot中集成JWT(JSON Web Token)鉴权

    这篇博客主要是简单介绍了一下什么是JWT,以及如何在Spring Boot项目中使用JWT(JSON Web Token). 1.关于JWT 1.1 什么是JWT 老生常谈的开头,我们要用这样一种工具 ...

  3. JWT(JSON Web Token) 【转载】

    JWT(JSON Web Token) 什么叫JWTJSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. 一般来说,互联网用户认证是这样子的. 1.用户向服务器发送用户名和密码. ...

  4. [更新]一份包含: 采用RSA JWT(Json Web Token, RSA加密)的OAUTH2.0,HTTP BASIC,本地数据库验证,Windows域验证,单点登录的Spring Security配置文件

    没有任何注释,表怪我(¬_¬) 更新: 2016.05.29: 将AuthorizationServer和ResourceServer分开配置 2016.05.29: Token获取采用Http Ba ...

  5. ( 转 ) 什么是 JWT -- JSON WEB TOKEN

    什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点 ...

  6. 关于JWT(Json Web Token)的思考及使用心得

    什么是JWT? JWT(Json Web Token)是一个开放的数据交换验证标准rfc7519(php 后端实现JWT认证方法一般用来做轻量级的API鉴权.由于许多API接口设计是遵循无状态的(比如 ...

  7. 什么是JWT(Json Web Token)

    什么是 JWT (Json Web Token) 用户认证是计算机安全领域一个永恒的热点话题. JWT 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519). 该to ...

  8. API安全验证之JWT(JSON WEB TOKEN) OLCMS

    假如www.olcms.com/getUserInfo获取用户信息,你怎么知道当前用户是谁?有人说登陆时候我把他UID写入session了,如果是API接口,没有session怎么办,那么就需要把UI ...

  9. 5分钟搞懂:JWT(Json Web Token)

    https://www.qikegu.com/easy-understanding/892 JWT 基于token的用户认证原理:让用户输入账号和密码,认证通过后获得一个token(令牌),在toke ...

  10. JWT(Json Web Token)认证

    目录 JWT(Json Web Token) JWT的数据结构 JWT的用法 JWT验证流程

随机推荐

  1. 【Android开发精要笔记】Android组件模型解析

    Android组件模型解析 Android中的Mashup 将应用切分成不同类别的组件,通过统一的定位模型和接口标准将他们整合在一起,来共同完成某项任务.在Android的Mashup模式下,每个组件 ...

  2. WINAPI和APIENTRY是一样的

    今天写线程函数时,发现msdn中对ThreadProc的定义有要求:DWORD WINAPI ThreadProc(LPVOID lpParameter); 不解为什么要用WINAPI宏定义,查了后发 ...

  3. Discovering Gold LightOJ - 1030 (概率dp)

    You are in a cave, a long cave! The cave can be represented by a 1 x N grid. Each cell of the cave c ...

  4. Python 数据分析—第七章 数据归整:清理、转换、合并、重塑

    一.数据库风格的Dataframe合并 import pandas as pd import numpy as np df1 = pd.DataFrame({'1key':['b','b','a',' ...

  5. PreTranslateMessage()函数捕获键盘按键消息

    01. PreTranslateMessage函数,常用于屏蔽MFC对话框中默认的Enter和ESC消息 函数原型:BOOL PreTranslateMessage(MSG* pMsg) 用法举例: ...

  6. Backup--备份基础理论

    --完整备份:完整备份会备份所有数据的区和少量的日志(日志文件用于恢复数据保持数据一致性).由于差异备份需要依据最后一次完整备份,因此完整备份会清楚一些分配位图数据. --差异备份:差异备份是针对完全 ...

  7. 爬虫开发10.scrapy框架之日志等级和请求传参

    今日概要 日志等级 请求传参 今日详情 一.Scrapy的日志等级 - 在使用scrapy crawl spiderFileName运行程序时,在终端里打印输出的就是scrapy的日志信息. - 日志 ...

  8. 爬虫开发3.requests模块

    requests模块 - 基于如下5点展开requests模块的学习 什么是requests模块 requests模块是python中原生的基于网络请求的模块,其主要作用是用来模拟浏览器发起请求.功能 ...

  9. “全栈2019”Java第十九章:关系运算符、条件运算符和三元运算符

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  10. [ActionScript 3.0] 自制简单拾色器

    colorBoard为库中绑定的影片剪辑,colorBoard中包含影片剪辑currColor,文本colorText,影片剪辑close: colorDot为库中绑定的影片剪辑,colorDot中包 ...