028-86261949

当前位置:首页 > 技术交流 > 使用JWT实现前后端权限验证

使用JWT实现前后端权限验证

2018/11/26 16:20 分类: 技术交流 浏览:0

一、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 来生成签名。
其他的一下算法:

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里面添加下面的内容
 
var expressJWT = require('express-jwt');

var secretOrPrivateKey = "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里面添加如下代码:
 
var jwt = require('jsonwebtoken');
var secretOrPrivateKey = "itsource.cn";
 
router.get('/getToken', function(req, res) {
  // 模拟数据库真实存在的用户信息, 真实项目中,应该去数据库查找
  const userInfo = {
  "username": "源码时代",
  "password": "itsource.cn",
  "role": "管理员",
  "major": "Java",
  "status": "在职",
};
  /*
  * 生成token并返回
  * userInfo:要加密的用户信息
  * secretOrPrivateKey:用来加密的秘钥
  * expiresIn:生成的token多久之后失效,单位秒
  * */
  const token = jwt.sign(userInfo, secretOrPrivateKey, { expiresIn: 60 * 60 * 10 });
  res.json({
    success: false,
    data: token,
  });
});
 

8、调用接口,生成token

这里使用poatman模拟客户端请求
请求方式:GET
如下:
返回数据:
{
"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封装的用户的信息。
  const userInfo = req.user;
  // 拿到用户信息以后可以在这里做一下额外的事情,比如说权限验证,根据用户的角色,判断他是否可以调用这个接口
  if(userInfo.role !== '管理员') { // 不是管理员直接返回
    res.json({
      success: false,
      message: '权限不够,管理员才有权限访问该接口',
    })
  }
  res.json({
    success: true,
    message: '看到这个说明你访问成功了',
    data: userInfo,
  })
});
 
 
直接访问该接口:
请求方式:GET
如下:
因为请求的时候没有携带token,所以请求会被直接拦截,无法访问到该接口。
 
设置token并访问该接口
请求方式:GET
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 && typeof options.getToken === 'function') {
  try {
    token = options.getToken(req);
  } catch (e) {
    return next(e);
  }
  //2 从authorization中获取token
} else if (req.headers && req.headers.authorization) {
  // --这是关键代码-----开始切割--------->
  var parts = req.headers.authorization.split(' ');
  if (parts.length == 2) {
    var scheme = parts[0];
    var credentials = parts[1];

    if (/^Bearer$/i.test(scheme)) {
      token = credentials; // <-------最终获取到token---------         
    } else {
      if (credentialsRequired) {
        return next(new UnauthorizedError('credentials_bad_scheme', { message: 'Format is Authorization: Bearer [token]' }));
      } else {
        return next();
      }
    }
    //3 以上两个途径都没有token时 就报错呗
  } else {
    return next(new UnauthorizedError('credentials_bad_format', { message: 'Format is Authorization: Bearer [token]' }));
  }
}

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

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

export default function getData() {
  return axios.request({
    url: 'http://localhost:3000/getData',
    method: 'get',
    headers: {
      Authorization: 'Bearer eyJhbGciCJ9.eyJ1c2VwMjB9.vDDhhaLNYufXlQ'
    } }).then((res) => {
      if (res.status === 200) {
        return Promise.resolve({ success: true, data: res.data });
      } else {
        return Promise.resolve({ success: false });
      }
    }).catch((err) => {
      return Promise.resolve({ success: false });
    });
}
 
         感谢源码时代教学讲师提供此文章!
   本文为原创文章,转载请注明出处!
#标签:JWT,前后端,权限