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…

相關文章
相關標籤/搜索