基於JWT規範實現的認證微服務

基於JWT規範實現的認證微服務

本文由公眾號EAWorld翻譯發表,轉載需註明出處。

作者:Marcelo Fonseca

譯者:白小白

原題:Building an authentication micro-service with JWT standard

原文:http://t.cn/EI67VmL

全文2326字,閱讀約需要5分鐘

目錄:

一、微服務介紹

二、隨之而來的認證和授權問題

三、項目架構通信

四、用於簽名以及驗證的公鑰和私鑰令牌

五、項目數據庫同步問題

一、微服務介紹

微服務日漸流行,幾乎所有流行語言都提供了兩種框架實現,一是面向Web開發的大型框架,一是面向小型應用的微框架。輕量級框架作為微服務架構來說,是個好的選擇。微服務架構有很多優勢,諸如高可維護性,獨立部署等等。微服務架構讓我們可以針對特定語言選擇最優的解決方案來建立特定的服務,比如,針對爬蟲類應用或者AI場景,我們可以選擇建立一個Python服務;針對加密庫的場景建立JS服務;針對Active Record的場景建立Ruby服務等等。基於這樣的理念,我們不需要受限於使用單一語言來建立整個後端服務

下面我列出了各種語言提供的微框架列表:

  • Python - Flask
  • Javascript - ExpressJS
  • Ruby - Sinatra
  • Go - Martini
  • Java - Spark
  • C# - nancy
  • C++ - Crow
  • PHP - silex

二、隨之而來的認證和授權問題

在微服務架構下,前後端的認證邏輯相比常規的CS應用要複雜的多。客戶端與後端的API服務器並不是一對一的關係,我們需要管理很多的後端服務,需要對更多的應用路由提供保護。為了解決這一問題,人們實踐了很多方式來建立微服務架構下的認證和授權邏輯。本文展示了其中一種方案,基於JSON Web Tokens(JWT)標準來實現一個簡單的認證和授權服務。

三、項目架構通信

簡化起見,示例中只實現了兩個後端服務。我將建立一個用於認證和授權的expressJS應用,以及一個Sinatra應用來作為博客服務的後端。目前為止,在本例 中將有兩個後端以及一個前端。

下面介紹一下應用間通信的實現機制。

前後端通信機制

基於JWT規範實現的認證微服務

  1. ExpressJS實現了前端應用的用戶註冊和登陸。
  2. 如果認證成功,ExpressJS應用將返回一個JWT令牌。
  3. 前端將這一令牌附加在請求的消息頭中用以訪問Sinatra應用數據。


服務間通信機制

當我們需要實現後端之間的通信時,就需要利用這樣的機制。作為示例場景,假設還有一個Flask API後端用於爬取網絡上的內容,並更新Sinatra博客應用中的數據。這樣我們就一共有了三個後端和一個前端。

基於JWT規範實現的認證微服務

  1. Flask應用向ExpressJS應用請求JWT令牌。
  2. 請求成功後,ExpressJS應用返回令牌。
  3. Flask應用將令牌附加在請求的消息頭,並訪問Sinatra應用的後端路由。

此處需要注意兩件事。無論是用戶發出請求或者後端發出請求,都需要合法的身份來進行認證以及訪問其他後端。但作為後端服務來講是不會使用郵件和密碼的,而是以API祕鑰作為身份的證明代之。比如,Flask應用向ExpressJS應用的路由發送一個登陸祕鑰,只要祕鑰是正確的,就可以授權Flask服務獲得JWT令牌。

四、用於簽名以及驗證的公鑰和私鑰令牌

在這套架構下,所有的微服務應用將使用其自身的JWT庫來對訪問請求進行認證並保護其API路由。此處我們將使用JWT RSA256策略。認證服務ExpressJS將同時持有私鑰和公鑰。使用私鑰來對用戶或應用的令牌進行簽名,用公鑰對令牌進行解碼和驗證。其他服務將僅持有公鑰來進行驗證。

使用RSA算法需要生成一個公鑰/私鑰對。可以通過如下的代碼在終端中實現,作為執行結果,代碼將生成.pem文件:

$ openssl genrsa -des3 -out private.pem 2048
$ openssl rsa -in private.pem -outform PEM -pubout -out public.pem

簽名令牌

在用戶或者API的登陸路由中實現令牌簽名。下面的代碼示例了ExpressJS認證服務的用戶登陸路由。只要用戶身份是合法的,代碼將訪問私鑰rsa2048priv.pem並且簽名一個新的JWT令牌。

// User sign-in route with JWT RSA algorithm example
var User = require('../models/user')
var express = require('express');
var router = express.Router();
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const fs = require('fs');
router.route('/sign-in').post(function(req, res, next){
User.find({ email: req.body.email}).then(user => {
if (user.length < 1)
return res.status(400).json({message: 'Authentication failed.'});

bcrypt.compare(req.body.password, user[0].passwordHash, (err, success) => {
if(success){
let cert = fs.readFileSync('../rsa_2048_priv.pem');
const token = jwt.sign(
{
email: user[0].email,
//id: user[0]._id,
},
cert,
{
expiresIn: '1h',
algorithm: 'RS256',
issuer: user[0].role,
}
);
res.status(200).json({token: token, message: 'Successfully authenticated.'});

}else
return res.status(400).json({message: 'Authentication failed.'});
});
});
});

驗證令牌

所有的服務都需要對持有合法JWT令牌的進站請求進行驗證。這可以通過在應用中建立一箇中間件來實現。這一中間件將訪問公鑰pem文件來對令牌進行解碼和驗證。在ExpressJS或者Sinatra服務中,這樣的中間件代碼類似如下所示。

ExpressJS認證和授權中間件代碼:

// JWT authentication middleware example.
// Uses RS256 strategy with .pem key pair files.
const fs = require('fs');
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
let publicKey = fs.readFileSync('../rsa_2048_pub.pem');
try{
const token = req.headers.authorization.split(' ')[1]; //req.headers.token;
console.log(token);
var decoded = jwt.verify(token, publicKey)
console.log(decoded);

next();
}catch(err){
return res.status(401).json({error: err, message: 'Invalid token.'});
}
};

五、項目數據庫同步問題

將博客服務和認證服務分離,將引發同步問題。原因之一是,兩者都需要各自保存用戶信息。ExpressJS需要用到用戶的身份信息,而Sinatra需要用到其他的用戶信息(比如頭像,個人描述以及發帖、評論數據之間的關聯關係等),對於這個問題可以有多種解決方案

  • 方案一:在認證服務的用戶表中保存全部用戶信息。在博客服務的用戶表中將僅保存用戶的ExpressJS服務ID(即user_id)以用來在認證服務中索引和查詢用戶數據。
  • 方案二:在博客服務中不設用戶表。所有涉及到用戶數據的博客數據庫表都將保存ExpressJS用戶ID作為索引。
  • 方案三:在認證服務中僅保存身份信息(如郵件地址和密碼),其餘的信息保存在博客服務中。當需要在博客服務中引用認證服務的用戶數據時,以用戶ID或者郵件地址作為唯一索引來關聯,當使用郵件地址時,需要在博客服務中同時保存用戶的郵件地址。

可以按自己的實際情況從上述的方案中做出選擇。我會選擇第三個方案,讓每個服務僅保存自己所需要的合理的數據。這樣,只需要少量的代碼修改,我就可以在未來的項目中複用這一認證服務,以期在Sinatra應用中充分利用Ruby的Active Record機制來進行用戶關係建模和查詢。要謹慎的時刻保持應用間的用戶數據同步,比如,如果在ExpressJS應用中刪除或者新建了一條用戶信息,確保這一變更同步到Sinatra應用。


關於EAWorld:微服務,DevOps,數據治理,移動架構原創技術分享

相關推薦

推薦中...