JSON-WEB-TOKEN的原理及使用实例

本篇介绍了关于JSON-WEB-TOKEN的原理介绍以及实例演示

基于token的鉴权机制

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

流程上是这样的:

  • 用户使用用户名密码来请求服务器
  • 服务器进行验证用户的信息
  • 服务器通过验证发送给用户一个token
  • 客户端存储token,并在每次请求时附送上这个token值
  • 服务端验证token值,并返回数据

JWT的基本格式

JWT由三部分组成:第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature)。

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

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

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

playload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

1
2
3
4
5
6
7
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

然后将其进行base64加密,得到Jwt的第二部分。

1
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

1
2
3
4
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

使用实例

使用nodejs编写服务端代码,用postman进行验证。
项目的简单目录:

目录

1
2
3
4
5
6
7
8
9
10
//user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// set up a mongoose model
module.exports = mongoose.model('User', new Schema({
name: String,
password: String,
admin: Boolean
}));

1
2
3
4
5
//config.js
module.exports = {
'secret': 'learnRestApiwithNickjs', // used when we create and verify JSON Web Tokens
'database': 'mongodb://localhost:27017/ttms' // 填写本地自己 mongodb 连接地址,xxx为数据表名
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//app.js
/**
* Created by lipeishang on 17-8-19.
*/
const express = require('express');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const config = require('./config');
const User = require('./models/user');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.set('superSecret', config.secret);
mongoose.connect(config.database);
app.post('/register', function (req, res, next) {
new User(req.body).save(function (err) {
if (err) {
return next(err);
}
console.log('register success!');
res.json({success: true});
});
});
app.post('/login', function (req, res, next) {
User.findOne({name: req.body.name}, function (err, user) {
if (err) {
return next(err);
}
if (!user) {
res.json({success: false, message: 'can not find this user'});
}
else if (user) {
if (user.password != req.body.password) {
res.json({success: false, message: 'incorrect password'});
}
else {
let token = jwt.sign(user, app.get('superSecret'), {});
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
}
});
});
app.get('/getName', function (req, res, next) {
const token = req.body.token || req.query.token || req.headers['x-access-token'];
if (token) {
/*验证token*/
jwt.verify(token, app.get('superSecret'), function (err, decoded) {
if (err) {
return res.json({success: false, message: 'fail to versify token'});
}
else {
req.decoded = decoded;
res.send(decoded._doc);
}
});
}
else {
return res.status(403).send({
success: false,
message: 'no token'
});
}
});
app.listen(3000, function () {
console.log('server started at http://localhost:3000'); // eslint-disable-line no-console
});

注册

注册成功

验证用户名密码并生成token

生成token

token验证

token验证返回数据
demo地址:https://github.com/lipeishang/json-web-token