基于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应用来作为博客服务的后端。目前为止,在本例 中将有两个后端以及一个前端。

下面介绍一下应用间通信的实现机制。

前后端通信机制

  1. ExpressJS实现了前端应用的用户注册和登陆。
  2. 如果认证成功,ExpressJS应用将返回一个JWT令牌。
  3. 前端将这一令牌附加在请求的消息头中用以访问Sinatra应用数据。

服务间通信机制

当我们需要实现后端之间的通信时,就需要利用这样的机制。作为示例场景,假设还有一个Flask API后端用于爬取网络上的内容,并更新Sinatra博客应用中的数据。这样我们就一共有了三个后端和一个前端。

  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 2048openssl rsa -in private.pem -outform PEM -pubout -out public.pem

签名令牌

在用户或者API的登陆路由中实现令牌签名。下面的代码示例了ExpressJS认证服务的用户登陆路由。只要用户身份是合法的,代码将访问私钥rsa2048priv.pem并且签名一个新的JWT令牌。

// User sign-in route with JWT RSA algorithm examplevar 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.'});    }};

(左右滑动查看全部代码)

Sinatra认证和授权中间件代码:

# 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

(左右滑动查看全部代码)

五、项目数据库同步问题

将博客服务和认证服务分离,将引发同步问题。原因之一是,两者都需要各自保存用户信息。ExpressJS需要用到用户的身份信息,而Sinatra需要用到其他的用户信息(比如头像,个人描述以及发帖、评论数据之间的关联关系等),对于这个问题可以有多种解决方案

  • 方案一:在认证服务的用户表中保存全部用户信息。在博客服务的用户表中将仅保存用户的ExpressJS服务ID(即user_id)以用来在认证服务中索引和查询用户数据。
  • 方案二:在博客服务中不设用户表。所有涉及到用户数据的博客数据库表都将保存ExpressJS用户ID作为索引。
  • 方案三:在认证服务中仅保存身份信息(如邮件地址和密码),其余的信息保存在博客服务中。当需要在博客服务中引用认证服务的用户数据时,以用户ID或者邮件地址作为唯一索引来关联,当使用邮件地址时,需要在博客服务中同时保存用户的邮件地址。

可以按自己的实际情况从上述的方案中做出选择。我会选择第三个方案,让每个服务仅保存自己所需要的合理的数据。这样,只需要少量的代码修改,我就可以在未来的项目中复用这一认证服务,以期在Sinatra应用中充分利用Ruby的Active Record机制来进行用户关系建模和查询。要谨慎的时刻保持应用间的用户数据同步,比如,如果在ExpressJS应用中删除或者新建了一条用户信息,确保这一变更同步到Sinatra应用。


关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享

Image placeholder
IT头条
未设置
  44人点赞

没有讨论,发表一下自己的看法吧

推荐文章
基于JS的高性能Flutter动态化框架MXFlutter

导语:18年10月份,手机QQ看点团队尝试使用Flutter,做为iOS开发,一接触到Flutter就马上感受到,Flutter虽然强大,但不能像RN一样动态化是阻碍我们使用她的唯一障碍了。看Goog

基于jquery开发的UI框架有哪些?

基于jquery开发的UI框架有哪些?1、国产jQueryUI框架(jUI)DWZDWZ富客户端框架(jQueryRIAframework),是中国人自己开发的基于jQuery实现的AjaxRIA开源

Hyperf 框架使用 JWT 进行用户认证

配置上一篇文章中我们已经安装好phper666/jwt-auth组件,并发布了配置。配置文件config/autoload/jwt.php已经有详细配置说明,如果要改默认设置,只需要在.env文件中加

SpringBoot 集成 JWT 实现 token 验证,token 注销

什么是JWT Jsonwebtoken(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519).定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形

微服务架构之「 服务注册 」

微服务架构是一个庞大复杂的工程,为什么说它庞大复杂呢?因为想要做好微服务,就必须先要建设好微服务所需的一系列基础设施和组件。我在前面的文章《架构设计之「微服务入门」》中已经初步介绍过了这些组件,包括:

一站式入口服务|爱奇艺微服务平台 API 网关实战

写在前面在互联网业务微服务化改造过程中,按照以往的服务治理体系,各服务需要单独实现限流、鉴权、监控、日志等通用功能,构建入口时资源申请、工单批复、多系统配置等一系列流程对精力消耗极大,学习成本较高

微服务架构中如何构建一个数据报告服务?

场景描述在微服务架构中,每个微服务负责自己的数据库,微服务A是不允许直接连接微服务B的数据库进行操作的。现在有2个微服务,一个是订单服务,一个是用户服务。有一个数据报告的需求:生成一份包含用户信息的订

一个知名网站的微服务架构最佳实现

译者:蓝梦,十余年研发经验,现就职于某上市互联网公司。作者:小马,Medium 首席架构师。译者有话说,如果你的项目正在从单体升级为微服务而忧心;或者你在实践微服务过程中手忙脚乱,本文都是你不容错过的

百亿流量微服务网关的设计与实现

本文从百亿流量交易系统微服务网关(APIGateway)的现状和面临的问题出发,阐述微服务架构与API网关的关系,理顺流量网关与业务网关的脉络,分享API网关知识与经验。API网关概述“计算机科学领域

理解 cookie、session、token、jwt

发展史 1、很久以前,Web基本上就是文档的浏览而已,既然是浏览,作为服务器,不需要记录谁在某一段时间里都浏览了什么文档,每次请求都是一个新的HTTP协议,就是请求加响应,尤其是我不用记住是谁刚刚发了

一步步教你如何在 Django REST API 中构建使用 JWT 验证

基于令牌的身份验证,允许后端服务与前端(无论是web端,原生移动端或其他端)分离,并驻留在不同域中。JSONWebTokens(JWT)是一种流行的令牌认证实现,在本文中,我们使用它来验证,通过Dj

REST framework JWT Auth

JWT认证的REST框架 概述 这个包提供对DjangoRESTframework的JSONWebToken认证支持。 需要满足条件 Python(2.7,3.3,3.4,3.5) Django(1.

ThinkPHP5-使用 think-API 部署 JWT

因为下一个项目要用TP5开发一个小程序,所以就使用到了,TP框架,因为小程序开发需要后台来编写api接口,所以就上网查了一下有没有相关的依赖,在此推荐一下think-api扩展工具,因为主要想使用其中

【翻译】REST framework JWT Auth

JWT认证的REST框架原文链接概述这个包提供对DjangoRESTframework的JSONWebToken认证支持。需要满足条件Python(2.7,3.3,3.4,3.5) Django(1.

前端微服务在字节跳动的落地之路

不少前端团队都面临着独石应用的工程巨大、理解困难和合作混乱的种种问题,微前端或许是一种比较好的解决方案,它允许我们为应用加入新功能而不影响整体结构。但同时,我们可能会付出一些代价,例如重复依赖、团

🚀 Hyperf 发布 v1.1.8 版本 | 企业级的 PHP 微服务云原生协程框架

更新内容 新增 #965新增RedisLua模块,用于管理Lua脚本; #1023hyperf/metric组件的Prometheus驱动新增CUSTOM_MODE模式; 修复 #1013修复Js

🚀 Hyperf 发布 v1.1.9 版本 | 企业级的 PHP 微服务云原生协程框架

更新内容 本周更新主要为DI组件新增了懒加载功能,配置为懒加载后,注入的对象为一个代理对象,在使用到时,才会实现对象的初始化。以及为DIContainer增加了set和define方法来动态的增加对象

🚀 Hyperf 发布 v1.1.9 版本 | 企业级的 PHP 微服务云原生协程框架

更新内容本周更新主要为DI组件新增了懒加载功能,配置为懒加载后,注入的对象为一个代理对象,在使用到时,才会实现对象的初始化。以及为DIContainer增加了set和define方法来动态的增加对象管

《PHP 微服务练兵》系列教程

本系列教程将从零开始使用PHP搭建微服务,涉及知识docker、mysql、Elasticsearch+Kibana日志分析系统、minio文件储存、阿里ACM配置中心、jenkens自动化测试部署、

微服务架构:拆分单体应用的难点

拆分单体应用为服务的难点从表面上看,通过定义与业务能力或子域相对应的服务来创建微服务架构的策略看起来很简单。但是,你可能会遇到几个障碍:网络延迟。同步进程间通信导致可用性降低。 在服务之间维持数据一致

微服务架构的四大金刚利器

Photo@ChristopherCampbell 文 | 孔凡勇概述互联网应用发展到今天,从单体应用架构到SOA以及今天的微服务,随着微服务化的不断升级进化,服务和服务之间的稳定性变得越来越重要,分

大神讲解微服务治理的技术演进和架构实践

摘要:随着业务的发展,规模扩大,服务越来越多,需要协调线上运行的各个服务,保障服务的SLA;基于服务调用的性能KPI数据进行容量管理,合理分配各服务的资源占用;对故障业务做服务降级、流量控制、流量迁移

从五个方面入手,保障微服务应用安全

随着计算机、互联网技术的飞速发展,信息安全已然是一个全民关心的问题,也是各大企业非常重视的问题。企业一般会从多个层次着手保障信息安全,如:物理安全、网络安全、系统安全(主机和操作系统)、应用安全等。对

微服务配置中心完全解读

本文作者:风卿,Nacos社区committer.在撰写这篇技术选型的文章之前,是比较犹豫的。因为,以其中一个开源项目开发者的身份,去写一篇三个开源项目的对比,即便很克制的去客观的比较,也很难有信服力

可伸缩的微服务告警系统设计指南

Uber的软件架构由成千上万的微服务组成,有赖于此,我们的团队可以快速的自主迭代并支撑公司的全球扩张。这一架构支撑了大量的上层解决方案,如移动应用,内部基础设施服务,以及拥有复杂配置的产品,相关配置会