express基于JWT实现用户登录受权控制

你是否和我同样,在对接后端大佬的接口时,对于请求头authorization认证感到疑惑;
你是否和我同样,在向node后端领域扩展时,对于用户登录注册受权感到挠头;
你是否和我同样,在浏览器访问某个页面时,对于访问权限控制感到好奇;
那么,请花上几分钟时间阅读,让下文来帮你解惑。javascript

本文主要经过express来实现用户登录受权的逻辑,这里的JWT只是一个标准,全称:JSON Web Token。有兴趣的小伙伴能够去官方说明加深了解。html

初识JWT

了解http协议的同窗都知道,http协议是无状态的,因此就须要客户端在每次请求的时候携带一些标识来代表身份,因此就有了CookieAuthorizationTokensession_id等,客户端认证访问服务端的模式通常以下:前端

  1. 客户端提交用户信息登录
  2. 服务端验证经过后,存储相关对话信息,生成对应的标识字符(token、Authorization、session_id),返回给客户端
  3. 客户端接收服务器返回内容,存储到对应的位置(cookie、localstorage)
  4. 客户端每次都携带这个标识字符进行服务端数据请求
  5. 服务端根据标识字符校验身份,进行数据处理

JWT字符由三部分组成,例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTU3NzM5NjM1LCJleHAiOjE1NTgzNDQ0MzV9.L4PqLf7PatEf_TVrNG2GgyBlU7YR8iuEoXOOeu7i15g
按照格式排列就是这样Header.Payload.Signaturejava

Header: JWT的元数据,通常是一些要加密的json对象,好比{username: 'Tom'}
Payload: JWT的负载对象,通常是JWT官方规定的字段,好比exp(过时时间),详见字段说明
Signature: 对前两部分和私有秘钥进行签名node

本文案例使用npm包jsonwebtoken,点击查看用法ios

项目初始配置

初始化一个express项目,配置数据库链接和加载bodyParser插件。web

//connect mongoDB
let mongoose = require('mongoose');
let mongoURL = 'mongodb://localhost/dataBase';
mongoose.connect(mongoURL);
mongoose.Promise = global.Promise;
let db = mongoose.connection;
db.on('error',console.error.bind(console, 'MongoDB connection error:'));

let bodyParser = require('body-parser');
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
// parse application/json
app.use(bodyParser.json());
复制代码

用户注册

编写用户model

用于链接数据库的数据Schema模型mongodb

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var AuthSchema = new Schema({
    username: String,
    userpswd: String
});

// 参数:导出模块名称、Schema实例、数据表名称
module.exports = mongoose.model('AuthInfo', AuthSchema, 'authinfo');
复制代码

编写注册route

基于restful API的接口路由数据库

var express = require('express');
var router = express.Router();
var bodyParser = require('body-parser');

var mongoose = require('mongoose');
var AuthInfo = require('../models/authModel'); // 导入model模块

router.post('/',function(req, res, next){
    console.log('open post register');

    var username = req.body.username;
    var password = req.body.password;

    //是否合法的参数
    if (username == null || username.trim() == '' || password == null || password.trim() == '') {
        res.send({code: 500, message: '用户名密码不能为空'})
        return
    }
    
   // md5
    var md5String = require('crypto').createHash('md5').update(password).digest('hex');

    //验证帐号是否存在
    var queryString = {username: username};
    res.set({'Content-type': 'application/json;charset=utf-8'});

    AuthInfo.findOne(queryString).then(data => {
        return new Promise((resolve, reject)=>{
            if(data){
                res.send({code: 500, message: '用户已经注册'});
                reject();
            }else{
                resolve();
            }
        }).then(()=>{
            //保存
            return new AuthInfo({
                username: username,
                password: md5String
            }).save();
        }).then(data => {
            if(data){
                //返回
                res.send({code: 1, message: '注册成功'})
                return;
            }
            // 返回
            res.send({code: 500, message: '注册失败'});
        }).catch(err => {
            // 异常
            if(err){
                res.status(500).send(err);
                console.log(err);
            }
        })
    })

});

module.exports = router;
复制代码

编写登录route

基于restful API的接口路由express

var express = require('express');
var router = express.Router();
var bodyParser = require('body-parser');

var mongoose = require('mongoose');
var AuthInfo = require('../models/authModel');

router.post('/',function(req, res, next){

    var username = req.body.username;
    var password = req.body.password;

    //是否合法的参数
    if (username == null || username.trim() == '' || password == null || password.trim() == '') {
        res.send({code: 500, message: '用户名密码不能为空'})
        return
    }

    var md5String = require('crypto').createHash('md5').update(password).digest('hex'); // md5

    //验证帐号是否存在
    var queryString = {username: username, userpswd: md5String};
    res.set({'Content-type': 'application/json;charset=utf-8'});
    
     // md5 token
    var tokenString = require('crypto').createHash('md5').update(JSON.stringify(queryString)).digest('hex');

    AuthInfo.findOne(queryString).then(data => {
        return new Promise((resolve, reject)=>{
            if(data){
               resolve(data);
            }else{
            	res.send({code: 500, message: '用户名或密码错误'})
              reject();
            }
        }).then(data => {
        		console.log(data);
        		res.send({ code: 1, message: '登录成功', token: tokenString })
        })
    }).catch( err => {
    	if(err){
          res.status(500).send(err);
          console.log(err);
        }
    })

});

module.exports = router;
复制代码

这里和注册不一样的是咱们须要把从前端页面接收到的密码经过MD5转换才能用于数据库查询,由于数据库的密码字段也正是存着MD5转换事后的字符,当查询成功以后,咱们还须要经过对刚才登录的表单字段对象进行字符串转换,而后再经过MD加密后做为token返回给客户端。

上面是一个简单的使用MD5加密的token用户受权案例,并无使用JWT,然而,使用JWT认证,咱们只须要进行少部分的变更

这里咱们须要借助npm包: jsonwebtoken,前端方面须要在axios或者fetch的默认headers配置中配置认证信息,好比:
axios.defaults.headers['Authorization'] = sessionStorage.getItem("token");

注册逻辑不变,咱们只须要更改用户登陆成功以后的token生成方式为JWT,而且在服务中作路由拦截并对客户端携带过来的认证信息作校验便可。

JWT登录route改进

var jwt = require('jsonwebtoken'); // 借助 jsonwebtoken

// ...

AuthInfo.findOne(queryString).then(data => {
  return new Promise((resolve, reject)=>{
      if(data){
         resolve(data);
      }else{
      	res.send({code: 500, message: '用户名或密码错误'})
        reject();
      }
  }).then(data => {
  		console.log(data);

  		/***jwt生成token***/
      let content = {username: username};  // 要生成token的主题信息
      let secretOrPrivateKey= "This is perfect projects."; // 这是加密的key(密钥) 根据我的自定义
      let token = jwt.sign(content, secretOrPrivateKey, {
        expiresIn: 60 * 60 * 24 * 7  // 一周过时
      });

  		res.send({ code: 1, message: '登录成功', token: token })
  })
}).catch( err => {
	if(err){
      res.status(500).send(err);
      console.log(err);
    }
})

// ...
复制代码

JWT项目初始配置改进

var jwt = require('jsonwebtoken'); // 借助 jsonwebtoken

// ...

let allowCrossDomain = function(req, res, next) {
  // 响应头设置 添加Methods: OPTIONS、Headers: Authorization
  res.header('Access-Control-Allow-Origin', 'http://localhost:8082');
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
  res.header('Access-Control-Allow-Headers', 'content-type,token,id');
  res.header("Access-Control-Request-Headers", "content-Type, Authorization");
  res.header('Access-Control-Allow-Credentials','true');
  next();
};
app.use(allowCrossDomain);

//添加拦截器
app.use(function(req, res, next){
  // 获取请求头中的Authorization认证字符
  let authorization = req.get('Authorization'); 
  // 排除不须要受权的路由
  if(req.path === '/api/login'){
    next()
  }else if(req.path === '/api/register'){
    next();
  }else{
    let secretOrPrivateKey= "This is perfect projects.";
    jwt.verify(authorization, secretOrPrivateKey, function (err, decode) {
      if (err) {  // 认证出错
        res.status(403).send('认证无效,请从新登陆。');
      } else {
        next();
      }
    })
  }
})

//...
复制代码

JWT认证方案相对于简单token认证方式变更不大,只是变动了token生成方式和用户身份的校验方式,了解JWT认证,对于咱们理解先后端交互具备更大的帮助。

本文参考:
juejin.im/post/5c2a2f…
www.ruanyifeng.com/blog/2018/0…

相关文章
相关标签/搜索