协议标准: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. 编写高质量代码改善C#程序的157个建议——建议135: 考虑使用肯定性的短语命名布尔属性

    建议135: 考虑使用肯定性的短语命名布尔属性 布尔值无非就是True和False,所以应该用肯定性的短语来表示它,例如,以Is.Can.Has作为前缀. 布尔属性正确命名的一个示例如下: class ...

  2. Linux或者window装svn

    Centos7搭建SVN Server手记 安装svn和依赖模块 yum install httpd httpd-devel subversion mod_dav_svn mod_auth_mysql ...

  3. EF中三大开发模式之DB First,Model First,Code First以及在Production Environment中的抉择

    一:ef中的三种开发方式 1. db first... db放在第一位,在我们开发之前必须要有完整的database,实际开发中用到最多的... <1> DBset集合的单复数... db ...

  4. C# 字符,字符串和文本处理。

    1. 字符: 在.net中 字符是表示成16为Unicode代码值.每个字符都是System.Char结构(一个值类型)的实例. public class StringTempte { public ...

  5. button的onclick函数一直刷新

    button中的onclick写成函数时需要 <button onclick="return function();"></button> 加一个retur ...

  6. win7 64 VC++ ado方式连接access 连接字符串

    运行环境:win7 64       vc++6.0       office 2007  32位(access 2007) 我用的是ado方式连接access数据库,(现在的Win7系统中安装的一般 ...

  7. CentOS关机命令

    Linux centos关机与重启命令详解与实战 Linux centos重启命令: 1.reboot 2.shutdown -r now 立刻重启(root用户使用) 3.shutdown -r 1 ...

  8. sqlite初识

    最近在部署PHP网站项目的时候,发现项目并没有使用传统的三大关系型数据库,而是采用了sqlite数据库,以前的时候,也见过sqlite,但是并没有深入了解其功能和用法,好奇心驱使,决定好好研究一下sq ...

  9. HAOI2014 遥感监测

    题目链接:戳我 比较水的一个题,直接处理点,找在直线上的可以覆盖到它的区间,然后做最小线段覆盖即可: 代码如下: #include<iostream> #include<cstdio ...

  10. “全栈2019”Java第六十五章:接口与默认方法详解

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