协议标准: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. CodeForces 146E Lucky Subsequence(组合数+DP)

    题目描述 Petya loves lucky numbers very much. Everybody knows that lucky numbers are positive integers w ...

  2. 使用ffmpeg+crtmpserver搭建文件的伪直播

    Tutorial: How to "live stream" a media file 如何"直播"一个媒体文件 I have tried a while to ...

  3. Tomcat之Web站点部署

    上线代码有两种方式,第一种方式是直接将程序目录放在webapps目录下面,这种方式大家已经明白了,就不多说了.第二种方式是使用开发工具将程序打包成war包,然后上传到webapps目录下面.下面让我们 ...

  4. python3操作Excel openpyxl模块的使用

    python 与excel 安装模块 本例子中使用的模块为: openpyxl 版本为2.4.8 安装方法请参看以前发表的文章(Python 的pip模块安装方法) Python处理Excel表格 使 ...

  5. 卸载超级兔子后,word打不开(无法创建工作文件),VS2010也没法用(找不到CL.exe)。

    又折腾了一上午,昨天用优化大师和超级兔子整理了电脑,今天来到实验室,vs打开后报错,提示"找不到CL.exe,"(具体提示忘记了,就是找不到CL.exe),打开word2010也是 ...

  6. [LeetCode 题解]:Candy

    There are N children standing in a line. Each child is assigned a rating value. You are giving candi ...

  7. C#在线运行

    初步完成c#代码的在线编辑.       首先,传回前端的c#在线代码,进行预编译,用CSharpCodeProvider这个方法.设置编译版本3.5 设置编译参数GenerateInMemory:是 ...

  8. Windows上编译zlib

    把zlib 1.2.8解压到zlib/zlib-1.2.8 在deflate.c文件中把deflate_copyright改成一个static变量. 在zlib目录底下创建并用Visual Studi ...

  9. TL431的应用

    TL431的应用 对于基准源,大部分人都认识TL431,因为它物美价廉,高精度,满足一般的应用场合,价格低至1毛钱,就算是ST高端品牌的也是几毛钱.这仅仅是其中一点,还有一点是因为它不仅仅可以作为基准 ...

  10. “全栈2019”Java第七十章:静态内部类详解

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