服务器如何验证jwt,使用JWT实现前后端权限验证

本帖最后由 菜肉果蔬 于 2018-11-26 16:23 编辑

一、JWT简介

JWT是json web token缩写。是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)。JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,如下所示,登录时发送如下信息:

{

"username": "源码时代",

"password": "itsource.cn",

}

服务器收到请求后,如果信息正确,可以到数据库查询到相关用户的所有信息,如下示例:

{

"username": "源码时代",

"password": "itsource.cn",

"role": "管理员",

"major": "Java",

"status": "在职",

}

这时,服务器可以把该用户的详细信息,通过密钥进行加密,生成一个token并返回给用户。用户可以把该token保存到客户端,服务器不保存任何用户信息。之后,当用户与服务器通信时,通常将token通过HTTP的Authorization header发送给服务端,服务端使用自己保存的密钥验证token的正确性,只要正确即通过验证。服务器仅依赖于这个token来标识用户。

1、jwt的组成(了解)

一个token分3部分,按顺序为

头部(header)

消息体(payload)

签证(signature)

由三部分生成token,3部分之间用“.”号做分隔。例如eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

2、头部(header)

头信息指定了该JWT使用的签名算法:

header = '{"alg":"HS256","typ":"JWT"}'

HS256 表示使用了 HMAC-SHA256 来生成签名。

其他的一下算法:

1c5d5d86d2592c1816609676c24a907d.gif

图片1.png (68.65 KB, 下载次数: 43)

2018-11-26 16:13 上传

3、消息体(payload)

消息体就是存放有效信息的地方。基本上填2种类型数据

-标准中注册的声明的数据-自定义数据

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

{

iss: jwt签发者

sub: jwt所面向的用户

aud: 接收jwt的一方

exp: jwt的过期时间,这个过期时间必须要大于签发时间

nbf: 定义在什么时间之前,该jwt都是不可用的.

iat: jwt的签发时间

jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

}

自定义数据

{

"username": "源码时代",

"password": "itsource.cn"

}

4、签名signature

未签名的令牌由base64url编码的头信息和消息体拼接而成(使用"."分隔),签名则通过私有的key计算而成:

key = 'secretkey'

unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)

signature = HMAC-SHA256(key, unsignedToken)

最后在未签名的令牌尾部拼接上base64url编码的签名(同样使用"."分隔)就是JWT了:

token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)

# token看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI

token常常被用作保护服务端的资源(resource),客户端通常将token通过HTTP的Authorization header发送给服务端,服务端使用自己保存的key计算、验证签名以判断该token是否可信:

Authorization: 'Bearer eyJhbGciyu5CSpyHI'

二、JWT的使用

使用环境: 使用nodeJs express作为后台的技术,使用express-generator脚手架快速搭建后台服务器

以下所有使用到npm的地方推荐使用cnpm或者yarn替代

1、安装脚手架

npm install express-generator -g

2、构建项目

express myapp  --view=pug

cd myapp

npm install

3、添加nodemon

在开发的时候,每次修改文件,都需要重启 express 服务,比较麻烦。使用nodemon,修改文件后可以自动重启 express 服务。

npm install --save-dev nodemon

修改 package.json 的 scripts 内容:

"scripts": {

"start": "node ./bin/www",

"devstart": "nodemon ./bin/www"

},

之后,使用npm run devstart 启动 express 服务。这样在开发过程中修改文件的时候,express服务就会自动重启,非常方便。

4、安装Jwt相关依赖

npm install jsonwebtoken --save

npm install express-jwt --save

express-jwt是nodejs的一个中间件,他来验证指定http请求的JsonWebTokens的有效性,如果有效就将JsonWebTokens的值设置到req.user里面,然后路由到相应的router。 此模块允许您使用Node.js应用程序中的JWT令牌来验证HTTP请求。 JWT通常用于保护API端点

express-jwt和jsonwebtoken是什么关系

express-jwt内部引用了jsonwebtoken,对其封装使用。 在实际的项目中这两个都需要引用,他们两个的定位不一样。jsonwebtoken是用来生成token给客户端的,express-jwt是用来验证token的。

5、设置需要保护的API

在app.js里面添加下面的内容

varexpressJWT = require('express-jwt');

varsecretOrPrivateKey = "itsource.cn";//秘钥,加密token 校验token时要使用

app.use(expressJWT({

"secret": secretOrPrivateKey, // 校验时用的秘钥

}).unless({

path: ['/getToken', '/login']  //除了这个地址,其他的URL都需要验证

}));

6、添加校验token失败时的处理

app.js里面添加蓝色部分的代码

// error handler

app.use(function(err, req, res, next) {

// set locals, only providing error in development

res.locals.message = err.message;

res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page

res.status(err.status || 500);

//  当客户端请求时发过来的token无效或者过期的时候会触发该错误

if(err.name === 'UnauthorizedError') {

//  这个需要根据自己的业务逻辑来处理( 具体的err值 请看下面)

res.status(401).send('无效的token,请重新获取');

}

res.render('error');

});

7、定义获取token的接口

定义一个方法,在里面加密相应的用户信息,生成token并返给客户端,可以让客户端获取token。

在routes/index.js里面添加如下代码:

varjwt = require('jsonwebtoken');

varsecretOrPrivateKey = "itsource.cn";

router.get('/getToken', function(req, res) {

// 模拟数据库真实存在的用户信息, 真实项目中,应该去数据库查找

constuserInfo = {

"username": "源码时代",

"password": "itsource.cn",

"role": "管理员",

"major": "Java",

"status": "在职",

};

/*

* 生成token并返回

* userInfo:要加密的用户信息

* secretOrPrivateKey:用来加密的秘钥

* expiresIn:生成的token多久之后失效,单位秒

* */

consttoken = jwt.sign(userInfo, secretOrPrivateKey, { expiresIn: 60 * 60 * 10 });

res.json({

success: false,

data: token,

});

});

8、调用接口,生成token

这里使用poatman模拟客户端请求

请求方式:GET

如下:

1c5d5d86d2592c1816609676c24a907d.gif

图片2.png (52.76 KB, 下载次数: 49)

2018-11-26 16:14 上传

返回数据:

{

"success":true,"

data":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJyb2xlIjoi566h55CG5ZGYIiwibWFqb3IiOiJKYXZhIiwic3RhdHVzIjoi5Zyo6IGMIiwiaWF0IjoxNTQyMTcxMzE0LCJleHAiOjE1NDIyMDczMTR9.yhRSl0YqJ7Z1cXTBqxvurPInKVhOGoAeEaoTrPPhITo"

}

success: 表示此次请求是否成功

data:服务器返回的数据,这里是返回的 token

9、新增需要验证token的接口

第7步定义的接口,因为在app.js里面配置了路径,如下:

unless({

path: ['/getToken', '/login]  //除了这里面的地址,其他的URL都需要验证

}));

所以请求的时候不需要验证token,但没有配置路径的接口则需要验证,这里我们可以新增一个接口验证我们之前返回的token是否有效。

在routes/index.js里面添加如下代码:

// 访问这个地址,token 要放到 authorization 这个header里,

// 对应的值以Bearer开头然后空一格,接近着是token值。为什么会这样,请看下面后续。

router.get('/getData', function(req, res) {

// 请求成功,可以从request里面获取之前jwt封装的用户的信息。

constuserInfo = req.user;

// 拿到用户信息以后可以在这里做一下额外的事情,比如说权限验证,根据用户的角色,判断他是否可以调用这个接口

if(userInfo.role !== '管理员') { // 不是管理员直接返回

res.json({

success: false,

message: '权限不够,管理员才有权限访问该接口',

})

}

res.json({

success: true,

message: '看到这个说明你访问成功了',

data: userInfo,

})

});

直接访问该接口:

请求方式:GET

如下:

1c5d5d86d2592c1816609676c24a907d.gif

图片3.png (49.61 KB, 下载次数: 51)

2018-11-26 16:15 上传

因为请求的时候没有携带token,所以请求会被直接拦截,无法访问到该接口。

设置token并访问该接口

请求方式:GET

1c5d5d86d2592c1816609676c24a907d.gif

图片4.png (96.36 KB, 下载次数: 45)

2018-11-26 16:16 上传

success: 表示此次请求是否成功

data:服务器返回的数据

请求的时候,在headers里面设置了authorization的值,所以可以访问到接口,并且可以从请求的request里面获取之前加密的用户信息,并返回。

10、客户端携带token正确的姿势

放到 authorization 这个header里, 对应的值以Bearer开头然后空一格

Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJyb2xlIjoi566h55CG5ZGYIiwibWFqb3IiOiJKYXZhIiwic3RhdHVzIjoi5Zyo6IGMIiwiaWF0IjoxNTQyMTcyODg0LCJleHAiOjE1NDIyMDg4ODR9.Bn25XzMae5t_CNQyiZzLBuXfBYVBmhls1mvLMP210do

为什么会这样携带token,请看express-jwt源码里是如何获取token的:

//1 从options中获取token  这个忽略  因为 在设置 需要保护的API 时 并没有传递 getToken 这个方法

if(options.getToken && typeofoptions.getToken === 'function') {

try{

token = options.getToken(req);

} catch(e) {

returnnext(e);

}

//2 从authorization中获取token

} else if(req.headers && req.headers.authorization) {

// --这是关键代码-----开始切割--------->

varparts = req.headers.authorization.split(' ');

if(parts.length == 2) {

varscheme = parts[0];

varcredentials = parts[1];

if(/^Bearer$/i.test(scheme)) {

token = credentials; //

} else{

if(credentialsRequired) {

returnnext(newUnauthorizedError('credentials_bad_scheme', { message: 'Format is Authorization: Bearer [token]' }));

} else{

returnnext();

}

}

//3 以上两个途径都没有token时 就报错呗

} else{

returnnext(newUnauthorizedError('credentials_bad_format', { message: 'Format is Authorization: Bearer [token]' }));

}

}

11、express-jwt 作为中间件都做了什么

总体流程:

1c5d5d86d2592c1816609676c24a907d.gif

图片5.png (47.49 KB, 下载次数: 46)

5

2018-11-26 16:16 上传

12、前端框架中使用示例代码(以axios为例,根据实际情况调整,只要在请求的haders里面添加Authorization属性就可以, 值为‘Bearer ’ +后台返回的token)

varaxios =require('axios');

export default functiongetData() {

returnaxios.request({

url: 'http://localhost:3000/getData',

method: 'get',

headers: {

Authorization: 'Bearer eyJhbGciCJ9.eyJ1c2VwMjB9.vDDhhaLNYufXlQ'

} }).then((res) => {

if(res.status === 200) {

returnPromise.resolve({ success: true, data: res.data });

} else{

returnPromise.resolve({ success: false});

}

}).catch((err) => {

returnPromise.resolve({ success: false});

});

}

感谢源码时代教学讲师提供此文章!

本文为原创文章,转载请注明出处!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值