JWT是JSON WEB TOKEN的缩写,是为了在网络应用环境建传递声明而执行的一种基于JSON的开放标准。该Toekn被设计为紧凑并且安全的,特别适用于分布式站点的单点登录(SSO)
传统的session认证是为了让我们的而英勇能识别是哪个用户发送的请求,需要在服务器存储一份用户的登陆信息,这份登陆信息会在返给用户的响应时传递给浏览器,浏览器保存为cookie,下次请求时发送给服务器,用来验证用户身份。
基于session认证存在一些问题:
基于token的鉴权机制类似于http协议,也是无状态的,不需要在服务端保留用户的认证信息或者会话信息。这就意味着基于token认证的应用不需要考虑用户在哪一台服务器登陆了,为应用的扩展提供了遍历。
主要的流程如下:
JWT由三段信息构成,第一部分为头部(header),第二部分为载荷(payload),第三部分是签名(signature)
header用来描述描述关于该JWT的最基本的信息,承载两部分信息:
这也可以被表示成一个JSON对象。
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64加密,构成了第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷是存放有效信息的地方,包含三个部分:
一个payload如下:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后对其进行base64编码,得到JWT的第二部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
签名信息由三个部分组成:
secret是利用HS256T算法进行加密时的密钥,将header和payload使用secret进行加密后,构成了jwt的第三部分
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.
连接成一个完整的字符串,构成了最终的jwt
注意,jwt的签发生成是在服务器端的,secret是保存在服务端的,用来进行jwt的签发和验证。它是服务端的密钥,在任何场景都不应该流出。一旦客户端得到这个sercret,那就意味着客户端可以自我签发jwt了。
fetch
发送发送jwt时一般是在请求头里加入Authorization
,并加上Bearer
标注:
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
服务端会验证token,如果验证通过就会返回相应的资源。
axios
发送在Vue中经常使用axios来帮助我们处理网络请求,使用axios设置请求头如下
import axios from 'axios';
import JsonWebToken from 'jsonwebtoken';
// 在登录成功后应该已经将token存到本机了
const token = sessionStorage.getItem('userToken');
// iss是我们预先定义信息,用来识别token是我们所需要的token
const isTokenValid = !!(token && JsonWebToken.decode(token) && (JsonWebToken.decode(token).iss === config.userToken.iss));
// 如果token有效
if (isTokenValid) {
// get请求
axios.get('/api/user/deatil', {
headers: {
'Authorization': 'Bearer ' + token,
'Cookie' : 'sessionId=' + sessionId + '; recId=' + recId,
}
params: {
param1: 'string',
param2: 'string'
},
})
// post请求
axios.post('/api/user/deatil',
{
data: {value1: 'value}'
},
{
headers: {
'Authorization': 'Bearer ' + token,
'Cookie' : 'sessionId=' + sessionId + '; recId=' + recId,
}
})
// 全局设定
axios.headers.common['Authorization'] = 'Bearer ' + token
}
jsonwebtoken是Node环境下JWT的实现,提供了一些API帮助我们快速的实现JWT的设计。
安装:
$ npm install jsonwebtoken --save
jwt.sign(payload, secretOrPrivateKey, [options, callback])
payload
:可以是以object或者string或者buffer(object会被使用JSON.stringify
方法转换为字符串)secretOrPrivateKey
:用来加密的密钥options
: algorithm
:加密算法(默认值HS256)–(alg
)expriesIn
:失效时间,数字默认单位为秒,字符串需指明时间单位,如60
、2 days
、10h
、7d
(如果是字符串并且没有单位,单位默认为毫秒)–(exp
)notBefore
:生效时间,单位规则同expriesInd
–(nbf
)audience
:token的接受者–(aud
)issuer
:签名的发行者–(iss
)jwtid
:JWT的IDsubject
:JWT的主题–(sub
)noTimestamp
:不需要时间戳header
:头部信息callback
:加密失败的回调函数生成一个有效期为1小时的token:
jwt.sign({
data: 'foobar'
}, 'secret', { expiresIn: 60 * 60 });
jwt.sign({
data: 'foobar'
}, 'secret', { expiresIn: '1h' });
也可以用下面这种形式:
jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: 'foobar'
}, 'secret');
验证token的合法性:
jwt.verify(token,secretOrPublicKey,[options,callback])
来看验证时的一些简单的用法,更详细的用法参考官网。
// 同步验证成功
var decoded = jwt.verify(token, 'shhhhh');
console.log(decoded.foo) // bar
// 异步验证成功
jwt.verify(token, 'shhhhh', function(err, decoded) {
console.log(decoded.foo) // bar
});
// 同步验证失败--token无效
try {
var decoded = jwt.verify(token, 'wrong-secret');
} catch(err) {
// err
}
// 异步验证失败--token无效
jwt.verify(token, 'wrong-secret', function(err, decoded) {
// err
// decoded undefined
})
将payload解码,注意:这个过程无论验证是否通过都会执行
jwt.decode(token [, options])
// get the decoded payload ignoring signature, no secretOrPrivateKey needed
var decoded = jwt.decode(token);
// get the decoded payload and header
var decoded = jwt.decode(token, {complete: true});
console.log(decoded.header);
console.log(decoded.payload)
token
:JWT的tokenoptions
: json
:强制使用JSON.parse()
机械payload,即使头信息中不包括"typ": "JWT"
complete
:返回一个包含解码后的payload和对象看一下例子:
// 解码payload不关心签名,不需要提供私钥
var decoded = jwt.decode(token);
// 获得对象
var decoded = jwt.decode(token, {complete: true});
console.log(decoded.header);
console.log(decoded.payload)
来看一个实际的例子:
const jwt = require('jsonwebtoken');
//密钥,不能暴露
const secret = 'aaa';
// payload中不应该包含敏感信息
const payload = {
username: 'jay',
userId: 123
}
// 选项(非必须)
const options = {
expiresIn: 60 * 60 * 24 // 或者为"1d"
}
// 生成token
const token = jwt.sign(payload, secret, options)
// 验证token
jwt.verify(token, secret, function (err, decoded) {
// 如果验证通过
if (!err){
console.log(decoded.username); // 'jay'
}
})
在Koa2中实现JWT的认证有中间件koa-jwt帮助我们完成,但是签发token的过程还是要安装上述过程,使用jsonwebtoken实现
直接看一个简单的例子:
import Koa from 'koa';
import jwt from 'koa-jwt';
const app = new Koa();
const router = new KoaRouter();
const authRouter = auth.router;
// 除了/public/之外的请求都需要经过JWT验证
app.use(jwt({ secret: 'shared-secret' }).unless({ path: [/^\/public/] }));
// 无论请求中是否存在Authorization header都保证执行
app.use(jwt({ secret: 'shared-secret', passthrough: true }));
// 所有走/api/打头的请求都需要经过JWT验证
router.use('/api', jwt({ secret: 'shared-secret' }), apiRouter.routes());
这篇文章提出了一些自己观点,可以参考一下,它认为jwt的token方案最适合的场景是无状态的场景,若需要考虑token注销和某些token续签的场景,实际上就是给token加上了状态,这就类似于传统的方案了,但是相比传统方案的优势是,服务端可以不用长期保存用户状态,仅仅在一定时间段内保留一小部分状态即可。
JWT的优点如下:
在安全方面需要注意:
Koa2中的具体实现: