JWT(Json Web Token)初探与实践
协议标准: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类型和采用的加密算法。
{"alg": "HS256","typ": "JWT"}
然后用Base64Url进行编码,就成了JWT的第一个部分
Payload
数据部分包含了主要的声明字段以及相应的值,声明主要包括3种类型:reserved , public 和 private
- Reserved claims: 这些字段是JWT预先定义的,在JWT中并不会强制使用它们,而是推荐使用。
常用的有:
iss(issuer): jwt签发者sub(subject): 签发的项目aud(audience): 接收jwt的一方exp(exipre): jwt的过期时间,这个过期时间必须要大于签发时间nbf(not before): 定义在什么时间之前,该jwt是不可用的.iat(issued at): jwt的签发时间jti(jwt token id): jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
需要注意的是,声明名称只有三个字符长度,这是为了让JWT保持紧凑
- Public claims:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.
- Private claims:私有声明是提供者和消费者所共同定义的声明
简单示例如下:
{"iss": "www","iat": 1441593502,"exp": 1441594722,"aud": "www.example.com","sub": "www@example.com","from_user": "B","target_user": "A"}
然后用Base64Url进行编码,就成了JWT的第二个部分
Signature
为了创建签名,你需要先对前面的部分进行Base64的编码,然后加上私匙,对其进行签名。
例如,你想使用HMAC SHA256算法进行前面,那么创建过程如下:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
签名的目的是为了校验JWT的携带者信息,并且检验是否有篡改过所携带的JWT信息。
HMAC SHA256算法计算之后的二进制数据默认进行Base64编码,就是JWT的第三个部分了
将他们放在一起
最终的结果是三段Base64字符串,通过.拼接在一起,这样就很容易在HTML和HTTP环境中传输,与基于XML的标准相比,更加紧凑节省资源。
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安装之后使用起来非常的简单,新建一个类专门用于校验
use Lcobucci\JWT\Builder;use Lcobucci\JWT\Parser;use Lcobucci\JWT\Signer\Hmac\Sha256;use Lcobucci\JWT\ValidationData;class Auth{const KEY = 'febcbaae13751fa2ds44c2f107afb08d';const VALID_INFO = ['Issuer' => 'http://www.xxxx.com','Audience' => 'http://aaa.xxxx.com','Subject' => 'test','Expire' => 259200];public static function check(){$jwt = request()->header('jwt');$valid = new ValidationData();$valid->setIssuer(self::VALID_INFO['Issuer']);$valid->setAudience(self::VALID_INFO['Audience']);$valid->setSubject(self::VALID_INFO['Subject']);//校验jwt信息,同时校验签名,否则可以伪造信息$signer = new Sha256();if ($jwt->validate($valid) && $jwt->verify($signer, self::KEY)) {$uinfo = $jwt->getClaim('uinfo');//取出数据的时候是对象而不是数组$uinfo->id//后续的权限校验过程……}}public static function getSignedJWT($userinfo){$signer = new Sha256();$token = (new Builder())->setIssuer(self::VALID_INFO['Issuer'])->setAudience(self::VALID_INFO['Audience'])->setSubject(self::VALID_INFO['Subject'])->setIssuedAt(time())->setExpiration(time() + self::VALID_INFO['Expire'])//可以直接保存数组或对象->set('uinfo', $userinfo)->sign($signer, self::KEY)->getToken()->__toString();return $token;}}
前端
登陆的时候保存JWT到localStorage,退出登录时前端删除保存的JWT即可。
apiLogin.login(this.$data.loginForm).then(res => {if (res.data.ret === 0) {this.$local.set('jwt', res.data.jwt)this.$local.set('menu', res.data.menu)this.$local.set('rules', res.data.rules)this.$local.set('username', this.loginForm.username)this.$local.set('title', res.data.title)this.$local.set('gpid', res.data.gpid)this.$router.push('index')// 原本没有jwt,所以登陆获取之后手动设置一次this.$http.defaults.headers.common['jwt'] = this.$local.get('jwt')} else {this.isLogining = falsethis.$message.error(res.data.msg)}}).catch(() => {this.isLogining = false})
base_api.js
import axios from 'axios'import { Message } from 'element-ui'import local from 'store'// Add a request interceptoraxios.interceptors.request.use(function (config) {return config}, function (error) {Message.error({showClose: true,message: '网络异常,请检查您的网络'})console.log(error)// Do something with request errorreturn Promise.reject(error)})// Add a response interceptoraxios.interceptors.response.use(function (response) {// 授权过期,无授权信息,跳出登陆if (response.data.ret === 4011 || response.data.ret === 4013) {window.location.href = '/#/login'// 删除本地的token令牌local.remove('jwt')Message.error({showClose: true,message: response.data.msg})return}if (response.data.ret === 4012) {// 无权限返回window.history.back()Message.error({showClose: true,message: response.data.msg})return}return response}, function (error) {Message.error({showClose: true,message: '网络异常,请检查您的网络'})return Promise.reject(error)})const baseUrl = process.env.API_ROOTaxios.defaults.baseURL = baseUrl// 初始化的时候加载本地储存过的jwtif (local.get('jwt')) {axios.defaults.headers.common['jwt'] = local.get('jwt')}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保存签名时间小于服务器签名时间,即强制过期
参考
JWT(Json Web Token)初探与实践的更多相关文章
- 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 ...
- 如何在SpringBoot中集成JWT(JSON Web Token)鉴权
这篇博客主要是简单介绍了一下什么是JWT,以及如何在Spring Boot项目中使用JWT(JSON Web Token). 1.关于JWT 1.1 什么是JWT 老生常谈的开头,我们要用这样一种工具 ...
- JWT(JSON Web Token) 【转载】
JWT(JSON Web Token) 什么叫JWTJSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. 一般来说,互联网用户认证是这样子的. 1.用户向服务器发送用户名和密码. ...
- [更新]一份包含: 采用RSA JWT(Json Web Token, RSA加密)的OAUTH2.0,HTTP BASIC,本地数据库验证,Windows域验证,单点登录的Spring Security配置文件
没有任何注释,表怪我(¬_¬) 更新: 2016.05.29: 将AuthorizationServer和ResourceServer分开配置 2016.05.29: Token获取采用Http Ba ...
- ( 转 ) 什么是 JWT -- JSON WEB TOKEN
什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点 ...
- 关于JWT(Json Web Token)的思考及使用心得
什么是JWT? JWT(Json Web Token)是一个开放的数据交换验证标准rfc7519(php 后端实现JWT认证方法一般用来做轻量级的API鉴权.由于许多API接口设计是遵循无状态的(比如 ...
- 什么是JWT(Json Web Token)
什么是 JWT (Json Web Token) 用户认证是计算机安全领域一个永恒的热点话题. JWT 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519). 该to ...
- API安全验证之JWT(JSON WEB TOKEN) OLCMS
假如www.olcms.com/getUserInfo获取用户信息,你怎么知道当前用户是谁?有人说登陆时候我把他UID写入session了,如果是API接口,没有session怎么办,那么就需要把UI ...
- 5分钟搞懂:JWT(Json Web Token)
https://www.qikegu.com/easy-understanding/892 JWT 基于token的用户认证原理:让用户输入账号和密码,认证通过后获得一个token(令牌),在toke ...
- JWT(Json Web Token)认证
目录 JWT(Json Web Token) JWT的数据结构 JWT的用法 JWT验证流程
随机推荐
- 编写高质量代码改善C#程序的157个建议——建议127:用形容词组给接口命名
建议127:用形容词组给接口命名 接口规范的是“Can do”,也就是说,它规范的是类型可以具有哪些行为.所以,接口的命名应该是一个形容词,如: IDisposable表示可以被释放 IEnumera ...
- 编写高质量代码改善C#程序的157个建议——建议104:用多态代替条件语句
建议104:用多态代替条件语句 假设要开发一个自动驾驶系统.在设计之初,此自动驾驶系统拥有一个驾驶系统命令的枚举类型: enum DriveCommand { Start, Stop } 当前该枚举存 ...
- 20169205实验二 Java面向对象程序设计
20169205实验二 Java面向对象程序设计 实验内容及步骤 (一)单元测试 1.三种代码 伪代码:以简洁的自然语言表明设计步骤: 产品代码:用以实现特定功能的程序或机器语言: 测试代码:用以对产 ...
- Promise实现简易AMD加载器
在最新的Chrome和FF中已经 实现了Promise.有了Promise我们用数行代码即可实现一个简易AMD模式的加载器 var registry = { promises: { }, resolv ...
- 深入理解java虚拟机(三)对象回收判断算法以及死亡过程
在堆里面存放着Java几乎所有的对象实例,垃圾收集器要进行垃圾回收,要做的第一步便是找出那些对象是需要回收的. 怎么判断对象是否需要回收? 常用的方法有两种. 1.引用计数算法.为每一个对象添加一个引 ...
- CodeForces 122G Lucky Array(一脸懵逼的树状数组)
Petya loves lucky numbers. Everybody knows that lucky numbers are positive integers whose decimal re ...
- Apache Shiro 10分钟之旅!
欢迎来到Apache Shiro 10分钟之旅! 希望通过这个简单.快速的示例,可以让你对应用程序中使用Shiro有个深入的了解.嗯,10分钟你应该可以搞定它. 概述 Apache Shiro是什么? ...
- jQuery,Table表头固定插件chromatable存在的问题及解决办法
在最近的项目中需要将表格的表头固定,搜寻了大量的资料,发现chromatable插件还是比较方便的.但是当我在一个页面中多次使用 chromatable固定对个表格的表头时问题就出现了,首先说明系统的 ...
- CDC--Demo
--CDC通过对事务日志的异步读取,记录DML操作的发生时间.--类型和实际影响的数据变化,然后将这些数据记录到启用--CDC时自动创建的表中.通过cdc相关的存储过程,可以获--取详细的数据变化情况 ...
- Nutch2.2.1 爬虫问题列表
http://www.cnblogs.com/cy163/archive/2013/02/14/2912630.html http://blog.csdn.net/wangzhaodong001/ar ...