JWT上手: Express+Passport

本文是此文的縮略翻譯版, 更詳細的內(nèi)容請參考原文. 本文在原文基礎上更正了Bearer的問題, 還有自己的一些更新.

本文講解下如何在express環(huán)境下, 使用passport進行JWT身份驗證.

目標:

  • /login用于登錄獲取token
  • /secret僅對有合法token的用戶可訪問

工具:

  • Postman用于測試發(fā)送請求
  • Node, npm

準備工作

創(chuàng)建項目文件夾. 運行以下命令初始化及安裝必要的包.

npm init -y
npm install --save express body-parser passport passport-jwt jsonwebtoken

創(chuàng)建如下index.js

// file: index.js

var express = require("express");
var app = express();


app.get("/", function(req, res) {
  res.json({message: "Express is up!"});
});

app.listen(3000, function() {
  console.log("Express running");
});

運行node index.js即可啟動服務器. 強烈建議安裝使用nodemon, 它可以監(jiān)聽文件變化, 自動重啟服務器, 啟動服務器的命令為nodemon index.js.

index

登錄

// file: index.js
var express = require("express");
var bodyParser = require("body-parser");
var jwt = require('jsonwebtoken');

var passport = require("passport");
var passportJWT = require("passport-jwt");

var ExtractJwt = passportJWT.ExtractJwt;
var JwtStrategy = passportJWT.Strategy;

創(chuàng)建測試用的用戶數(shù)組:

var users = [
  {
    id: 1,
    name: 'jonathanmh',
    password: '%2yx4'
  },
  {
    id: 2,
    name: 'test',
    password: 'test'
  }
];

注意, 實際應用中絕對不要明文保存密碼.

  1. 使用bcrypt加密
  2. 讀一讀The OWASP wikis Password Storage Cheat Sheet

passport.js有策略(strategy)的概念. strategy是一些預定義的方法, 它們會在請求抵達真正的路由之前執(zhí)行. 如果你定義的strategy認定某個請求非法, 則該路由不會被執(zhí)行, 而是返回401 Unauthorized.

var jwtOptions = {}
jwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
jwtOptions.secretOrKey = 'tasmanianDevil';

var strategy = new JwtStrategy(jwtOptions, function(jwt_payload, next) {
  console.log('payload received', jwt_payload);
  // usually this would be a database call:
  var user = users.find(user => user.id === jwt_payload.id);
  if (user) {
    next(null, user);
  } else {
    next(null, false);
  }
});

passport.use(strategy);

接下來添加登錄路由:

var app = express();
app.use(passport.initialize());

// parse application/x-www-form-urlencoded
// for easier testing with Postman or plain HTML forms
app.use(bodyParser.urlencoded({
  extended: true
}));

// parse application/json
app.use(bodyParser.json())

app.post("/login", function(req, res) {
  if(req.body.name && req.body.password){
    var name = req.body.name;
    var password = req.body.password;
  }
  // usually this would be a database call:
  var user = users.find(user => user.name === name);
  if( ! user ){
    res.status(401).json({message:"no such user found"});
  }

  if(user.password === req.body.password) {
    // from now on we'll identify the user by the id and the id is the only personalized value that goes into our token
    var payload = {id: user.id};
    var token = jwt.sign(payload, jwtOptions.secretOrKey);
    res.json({message: "ok", token: token});
  } else {
    res.status(401).json({message:"passwords did not match"});
  }
});

我們定義的payload中只有id一個claim.

打開Postman:

login failed
login success

創(chuàng)建JWT驗證的秘密路由

app.get("/secret", passport.authenticate('jwt', { session: false }), function(req, res){
  res.json("Success! You can not see this without a token");
});

打開Postman:

  • method: GET
  • URL: http://localhost:3000/secret
  • inside Headers: 添加一項, Key為Authorization, 字段為Bearer {token}. 其中{token}代表前面獲得的token字符串.
secret

測試秘密路由

可以創(chuàng)建一個測試用的秘密路由用于打印接收到的JWT token.

app.get("/secretDebug",
  function(req, res, next){
    console.log(req.get('Authorization'));
    next();
  }, function(req, res){
    res.json("debugging");
});
[nodemon] restarting due to changes...
[nodemon] starting `node index.js`
Express running
JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNDc3MTM0NzM4fQ.Ky3iKYcguIstYPDbMbIbDR5s7e_UF0PI1gal6VX5eyI

更新

如何在自定義的路由中訪問payload?

自己定義的strategy中的next(null, user);會將這個user的信息寫入req.user

因此, 你可以自定義next的第二個參數(shù), 比如向其中寫入一些payload的數(shù)據(jù), 然后通過req.user訪問那些數(shù)據(jù).

注意文中直接返回了user, 也就是將所有user信息, 包含password都返回給了req.user. 自己實現(xiàn)的時候返回必要的信息就行了.

如何在browser訪問userId?

login方法返回了{ message: 'ok', token: token }, 其中token包含了userId的信息.

token的結構是header.payload.signature.

Token中的header和payload是base64url編碼的, 它本身是用HMACSHA256算法進行簽名的.

所以對payload進行base64解壓縮即可, 瀏覽器有相應的atob解壓, btoa壓縮, 詳見Base64 encoding and decoding.

function getToken() {
  let token = localStorage.getJson('token');
  if (!token) {
    return undefined;
  }

  let parts = token.split('.');
  if (parts.length !== 3) {
    return undefined;
  }

  let payload = parts[1];
  return JSON.parse(base64url.decode(payload));
}

更新
注意! 踩了個坑!! header和payload是base64url編碼的(詳見rfc7519), 不是base64! 它們之間有一些細微的差別, 比如base64中的+/在base64url中需要被轉換為-_!

總之, 有一個包叫做base64url, 用這個庫解壓payload就對了! 別用base64!

我還整理了一個base64編解碼的文章, 結果搞了半天不是用base64...蛋疼.

參考

  1. Express, Passport and JSON Web Token (jwt) Authentication for Beginners
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容