您好,登錄后才能下訂單哦!
作者:Marcelo Fonseca
譯者:白小白
原題:Building an authentication micro-service with JWT standard
原文:http://t.cn/EI67VmL
全文2326字,閱讀約需要5分鐘
目錄:
一、微服務(wù)介紹
二、隨之而來的認證和授權(quán)問題
三、項目架構(gòu)通信
四、用于簽名以及驗證的公鑰和私鑰令牌
五、項目數(shù)據(jù)庫同步問題
一、微服務(wù)介紹
微服務(wù)日漸流行,幾乎所有流行語言都提供了兩種框架實現(xiàn),一是面向Web開發(fā)的大型框架,一是面向小型應(yīng)用的微框架。輕量級框架作為微服務(wù)架構(gòu)來說,是個好的選擇。微服務(wù)架構(gòu)有很多優(yōu)勢,諸如高可維護性,獨立部署等等。微服務(wù)架構(gòu)讓我們可以針對特定語言選擇最優(yōu)的解決方案來建立特定的服務(wù),比如,針對爬蟲類應(yīng)用或者AI場景,我們可以選擇建立一個Python服務(wù);針對加密庫的場景建立JS服務(wù);針對Active Record的場景建立Ruby服務(wù)等等?;谶@樣的理念,我們不需要受限于使用單一語言來建立整個后端服務(wù)。
下面我列出了各種語言提供的微框架列表:
Python - Flask
Javascript - ExpressJS
Ruby - Sinatra
Go - Martini
Java - Spark
C# - nancy
C++ - Crow
PHP - silex
二、隨之而來的認證和授權(quán)問題
在微服務(wù)架構(gòu)下,前后端的認證邏輯相比常規(guī)的CS應(yīng)用要復雜的多??蛻舳伺c后端的API服務(wù)器并不是一對一的關(guān)系,我們需要管理很多的后端服務(wù),需要對更多的應(yīng)用路由提供保護。為了解決這一問題,人們實踐了很多方式來建立微服務(wù)架構(gòu)下的認證和授權(quán)邏輯。本文展示了其中一種方案,基于JSON Web Tokens(JWT)標準來實現(xiàn)一個簡單的認證和授權(quán)服務(wù)。
三、項目架構(gòu)通信
簡化起見,示例中只實現(xiàn)了兩個后端服務(wù)。我將建立一個用于認證和授權(quán)的expressJS應(yīng)用,以及一個Sinatra應(yīng)用來作為博客服務(wù)的后端。目前為止,在本例 中將有兩個后端以及一個前端。
下面介紹一下應(yīng)用間通信的實現(xiàn)機制。
前后端通信機制
ExpressJS實現(xiàn)了前端應(yīng)用的用戶注冊和登陸。
如果認證成功,ExpressJS應(yīng)用將返回一個JWT令牌。
前端將這一令牌附加在請求的消息頭中用以訪問Sinatra應(yīng)用數(shù)據(jù)。
服務(wù)間通信機制
當我們需要實現(xiàn)后端之間的通信時,就需要利用這樣的機制。作為示例場景,假設(shè)還有一個Flask API后端用于爬取網(wǎng)絡(luò)上的內(nèi)容,并更新Sinatra博客應(yīng)用中的數(shù)據(jù)。這樣我們就一共有了三個后端和一個前端。
Flask應(yīng)用向ExpressJS應(yīng)用請求JWT令牌。
請求成功后,ExpressJS應(yīng)用返回令牌。
Flask應(yīng)用將令牌附加在請求的消息頭,并訪問Sinatra應(yīng)用的后端路由。
此處需要注意兩件事。無論是用戶發(fā)出請求或者后端發(fā)出請求,都需要合法的身份來進行認證以及訪問其他后端。但作為后端服務(wù)來講是不會使用郵件和密碼的,而是以API秘鑰作為身份的證明代之。比如,F(xiàn)lask應(yīng)用向ExpressJS應(yīng)用的路由發(fā)送一個登陸秘鑰,只要秘鑰是正確的,就可以授權(quán)Flask服務(wù)獲得JWT令牌。
四、用于簽名以及驗證的
公鑰和私鑰令牌
在這套架構(gòu)下,所有的微服務(wù)應(yīng)用將使用其自身的JWT庫來對訪問請求進行認證并保護其API路由。此處我們將使用JWT RSA256策略。認證服務(wù)ExpressJS將同時持有私鑰和公鑰。使用私鑰來對用戶或應(yīng)用的令牌進行簽名,用公鑰對令牌進行解碼和驗證。其他服務(wù)將僅持有公鑰來進行驗證。
使用RSA算法需要生成一個公鑰/私鑰對??梢酝ㄟ^如下的代碼在終端中實現(xiàn),作為執(zhí)行結(jié)果,代碼將生成.pem文件:
openssl genrsa -des3 -out private.pem 2048openssl rsa -in private.pem -outform PEM -pubout -out public.pem
(左右滑動查看全部代碼)
簽名令牌
在用戶或者API的登陸路由中實現(xiàn)令牌簽名。下面的代碼示例了ExpressJS認證服務(wù)的用戶登陸路由。只要用戶身份是合法的,代碼將訪問私鑰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.'});
});
});
});
(左右滑動查看全部代碼)
驗證令牌
所有的服務(wù)都需要對持有合法JWT令牌的進站請求進行驗證。這可以通過在應(yīng)用中建立一個中間件來實現(xiàn)。這一中間件將訪問公鑰pem文件來對令牌進行解碼和驗證。在ExpressJS或者Sinatra服務(wù)中,這樣的中間件代碼類似如下所示。
ExpressJS認證和授權(quán)中間件代碼:
// 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.'});
}
};
(左右滑動查看全部代碼)
Sinatra認證和授權(quán)中間件代碼:
# To connect this middleware.rb file to your sinatra app
# add 'use JWTAuthorization' as one of your first lines in
# your Application class.
# e.g.
# require 'middlewares.rb'
# class Application < Sinatra::Base
# use JWTAuthorization
# ...
# end
require 'sinatra/json'
require 'jwt'
class JWTAuthorization
def initialize app
@app = app
end
def call env
begin
# env.fetch gets http header
# bearer = env.fetch('HTTP_AUTHORIZATION', '').split(' ')[1] # also work
bearer = env.fetch('HTTP_AUTHORIZATION').slice(7..-1) # gets JWT token
key = OpenSSL::PKey::RSA.new File.read '../rsa_2048_pub.pem' # read public key pem file
payload = JWT.decode bearer, key, true, { algorithm: 'RS256'} # decode and verify token with pub key
claims = payload.first
# current_user is defined by env[:user].
# useful to define current_user if you are using pundit gem
if claims['iss'] == 'user'
env[:user] = User.find_by_email(claims['email'])
end
# access your claims here...
@app.call env
rescue JWT::DecodeError
[401, { 'Content-Type' => 'text/plain' }, ['A token must be passed.']]
rescue JWT::ExpiredSignature
[403, { 'Content-Type' => 'text/plain' }, ['The token has expired.']]
rescue JWT::InvalidIssuerError
[403, { 'Content-Type' => 'text/plain' }, ['The token does not have a valid issuer.']]
rescue JWT::InvalidIatError
[403, { 'Content-Type' => 'text/plain' }, ['The token does not have a valid "issued at" time.']]
# useful only if using pundit gem
rescue Pundit::NotAuthorizedError
[401, { 'Content-Type' => 'text/plain' }, ['Unauthorized access.']]
end
end
end
(左右滑動查看全部代碼)
五、項目數(shù)據(jù)庫同步問題
將博客服務(wù)和認證服務(wù)分離,將引發(fā)同步問題。原因之一是,兩者都需要各自保存用戶信息。ExpressJS需要用到用戶的身份信息,而Sinatra需要用到其他的用戶信息(比如頭像,個人描述以及發(fā)帖、評論數(shù)據(jù)之間的關(guān)聯(lián)關(guān)系等),對于這個問題可以有多種解決方案:
方案一:在認證服務(wù)的用戶表中保存全部用戶信息。在博客服務(wù)的用戶表中將僅保存用戶的ExpressJS服務(wù)ID(即user_id)以用來在認證服務(wù)中索引和查詢用戶數(shù)據(jù)。
方案二:在博客服務(wù)中不設(shè)用戶表。所有涉及到用戶數(shù)據(jù)的博客數(shù)據(jù)庫表都將保存ExpressJS用戶ID作為索引。
方案三:在認證服務(wù)中僅保存身份信息(如郵件地址和密碼),其余的信息保存在博客服務(wù)中。當需要在博客服務(wù)中引用認證服務(wù)的用戶數(shù)據(jù)時,以用戶ID或者郵件地址作為唯一索引來關(guān)聯(lián),當使用郵件地址時,需要在博客服務(wù)中同時保存用戶的郵件地址。
可以按自己的實際情況從上述的方案中做出選擇。我會選擇第三個方案,讓每個服務(wù)僅保存自己所需要的合理的數(shù)據(jù)。這樣,只需要少量的代碼修改,我就可以在未來的項目中復用這一認證服務(wù),以期在Sinatra應(yīng)用中充分利用Ruby的Active Record機制來進行用戶關(guān)系建模和查詢。要謹慎的時刻保持應(yīng)用間的用戶數(shù)據(jù)同步,比如,如果在ExpressJS應(yīng)用中刪除或者新建了一條用戶信息,確保這一變更同步到Sinatra應(yīng)用。
關(guān)于EAWorld:微服務(wù),DevOps,數(shù)據(jù)治理,移動架構(gòu)原創(chuàng)技術(shù)分享,長按二維碼關(guān)注
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。