聊聊 微服务 架构中的用户认证方案 上

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
简介: 聊聊 微服务 架构中的用户认证方案 上



今天来聊聊微服务中一个重要的话题:如何设计微服务架构下的用户认证方案。今天主要涉及三个方面的内容:

  • 传统的用户认证方案;
  • JWT 与 JJWT;
  • 基于网关的统一用户认证。

传统的用户认证方案

我们直奔主题,什么是用户认证呢?对于大多数与用户相关的操作,软件系统首先要确认用户的身份,因此会提供一个用户登录功能。用户输入用户名、密码等信息,后台系统对其进行校验的操作就是用户认证。用户认证的形式有多种,最常见的有输入用户名密码、手机验证码、人脸识别、指纹识别等,但其目的都是为了确认用户的身份并与之提供服务。

用户认证

在传统的单体单点应用时代,我们会开发用户认证的服务类,从登录界面提交的用户名密码等信息通过用户认证类进行校验,然后获取该用户对象将其保存在 Tomcat 的 Session 中,如下所示:

单点应用认证方案

随着系统流量的增高,单点应用以无法支撑业务运行,应用出现高延迟、宕机等状况,此时很多公司会将应用改为 Nginx 软负载集群,通过水平扩展提高系统的性能,于是应用架构就变成了这个样子。

Java Web 应用集群

虽然改造后系统性能显著提高,但你发现了么,因为之前用户登录的会话数据都保存在本地,当 Nginx 将请求转发到其他节点后,因为其他节点没有此会话数据,系统就会认为没有登录过,请求的业务就会被拒绝。从使用者的角度会变成一刷新页面后,系统就让我重新登录,这个使用体验非常糟糕。

我们来分析下,这个问题的根本原因在于利用 Session 本地保存用户数据会让 Java Web 应用变成有状态的,在集群环境下必须保证每一个 Tomcat 节点的会话状态一致的才不会出问题。因此基于 Redis 的分布式会话存储方案应运而生,在原有架构后端增加 Redis 服务器,将用户会话统一转存至 Redis 中,因为该会话数据是集中存储的,所以不会出现数据一致性的问题。

Redis 统一存储用户会话

但是,传统方案在互联网环境下就会遇到瓶颈,Redis 充当了会话数据源,这也意味着 Redis 承担了所有的外部压力,在互联网数以亿计的庞大用户群规模下,如果出现突发流量洪峰,Redis 能否经受考验就会成为系统的关键风险,稍有差池系统就会崩溃。

那如何解决呢?其实还有一种巧妙的设计,在用户认证成功,后用户数据不再存储在后端,而改为在客户端存储,客户端每一次发送请求时附带用户数据到 Web 应用端,Java 应用读取用户数据进行业务处理,因为用户数据分散存储在客户端中,因此并不会对后端产生额外的负担,此时认证架构会变成下面的情况。

客户端存储用户信息

当用户认证成功后,在客户端的 Cookie、LocalStorage 会持有当前用户数据,在 Tomcat 接收到请求后便可获取用户数据进行业务处理。但细心的你肯定也发现,用户的敏感数据是未经过加密的,在存储与传输过程中随时都有泄密的风险,决不能使用明文,必须要对其进行加密。

那如何进行加密处理呢?当然,你可以自己写加解密类,但更通用的做法是使用 JWT 这种标准的加密方案进行数据存储与传输。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

Json Web Token(JWT)介绍

无论是微服务架构,还是前后端分离应用,在客户端存储并加密数据时有一个通用的方案:Json Web Token(JWT),JWT是一个经过加密的,包含用户信息的且具有时效性的固定格式字符串。下面这是一个标准的JWT字符串。

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoyLFwidXNlcm5hbWVcIjpcImxpc2lcIixcIm5hbWVcIjpcIuadjuWbm1wiLFwiZ3JhZGVcIjpcInZpcFwifSJ9.NT8QBdoK4S-PbnhS0msJAqL0FG2aruvlsBSyG226HiU  

这段加密字符串由三部分组成,中间由点“.”分隔,具体含义如下。

  • 第一部分 标头(Header):标头通常由两部分组成:令牌的类型(即 JWT)和所使用的签名算法,例如 HMAC SHA256 或 RSA,下面是标头的原文:
{  
  "alg": "HS256",  
  "typ": "JWT"  
}  

然后,此 JSON 被 Base64 编码以形成 JWT 的第一部分。

eyJhbGciOiJIUzI1NiJ9  
  • 第二部分 载荷(Payload):载荷就是实际的用户数据以及其他自定义数据。载荷原文如下所示。
{  
  "sub": "1234567890",  
  "name": "John Doe",  
  "admin": true  
}  

然后对原文进行 Base64 编码形成 JWT 的第二部分。

eyJzdWIiOiJ7XCJ1c2VySWRcIjoyLFwidXNlcm5hbWVcIjpcImxpc2lcIixcIm5hbWVcIjpcIuadjuWbm1wiLFwiZ3JhZGVcIjpcInZpcFwifSJ9  
  • 第三部分 签名(Sign):签名就是通过前面两部分标头+载荷+私钥再配合指定的算法,生成用于校验 JWT 是否有效的特殊字符串,签名的生成规则如下。
HMACSHA256(base64UrlEncode(header) + "." +  base64UrlEncode(payload),  secret)  

生成的签名字符串为:

NT8QBdoK4S-PbnhS0msJAqL0FG2aruvlsBSyG226HiU  

将以上三部分通过“.”连接在一起,就是 JWT 的标准格式了。

JWT 的创建与校验

此时,你肯定有疑问 JWT 是如何生成的,又是如何完成有效性校验呢?因为 JWT 的格式与算法是固定的,在 Java 就有非常多的优秀开源项目帮我们实现了JWT 的创建与验签,其中最具代表性的产品就是 JJWT。JJWT 是一个提供端到端的 JWT 创建和验证的 Java 库,它的官网是:https://github.com/jwtk/jjwt,有兴趣的话你可以到官网阅读它的源码。

JJWT 的使用是非常简单的,下面我们用代码进行说明,关键代码我已做好注释。

  • 第一步,pom.xml 引入 JJWT 的 Maven 依赖。
<dependency>  
    <groupId>io.jsonwebtoken</groupId>  
    <artifactId>jjwt-api</artifactId>  
    <version>0.11.2</version>  
</dependency>  
<dependency>  
    <groupId>io.jsonwebtoken</groupId>  
    <artifactId>jjwt-impl</artifactId>  
    <version>0.11.2</version>  
    <scope>runtime</scope>  
</dependency>  
<dependency>  
    <groupId>io.jsonwebtoken</groupId>  
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->  
    <version>0.11.2</version>  
    <scope>runtime</scope>  
</dependency>  
  • 第二步,编写创建 JWT 的测试用例,模拟真实环境 UserID 为 123 号的用户登录后的 JWT 生成过程。
@SpringBootTest  
public class JwtTestor {  
    /**  
     * 创建Token  
     */  
    @Test  
    public void createJwt(){  
        //私钥字符串  
        String key = "1234567890_1234567890_1234567890";  
        //1.对秘钥做BASE64编码  
        String base64 = new BASE64Encoder().encode(key.getBytes());  
        //2.生成秘钥对象,会根据base64长度自动选择相应的 HMAC 算法  
        SecretKey secretKey = Keys.hmacShaKeyFor(base64.getBytes());  
        //3.利用JJWT生成Token  
        String data = "{\"userId\":123}"; //载荷数据  
        String jwt = Jwts.builder().setSubject(data).signWith(secretKey).compact();  
        System.out.println(jwt);  
    }  
}  

运行结果产生 JWT 字符串如下:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxMjN9In0.1p_VTN46sukRJTYFxUg93CmfR3nJZRBm99ZK0e3d9Hw  
  • 第三步,验签代码,从 JWT 中提取 123 号用户数据。这里要保证 JWT 字符串、key 私钥与生成时保持一致。否则就会抛出验签失败 JwtException。
/**  
 * 校验及提取JWT数据  
 */  
@Test  
public void checkJwt(){  
    String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxMjN9In0.1p_VTN46sukRJTYFxUg93CmfR3nJZRBm99ZK0e3d9Hw";  
    //私钥  
    String key = "1234567890_1234567890_1234567890";  
    //1.对秘钥做BASE64编码  
    String base64 = new BASE64Encoder().encode(key.getBytes());  
    //2.生成秘钥对象,会根据base64长度自动选择相应的 HMAC 算法  
    SecretKey secretKey = Keys.hmacShaKeyFor(base64.getBytes());  
    //3.验证Token  
    try {  
        //生成JWT解析器   
        JwtParser parser = Jwts.parserBuilder().setSigningKey(secretKey).build();  
        //解析JWT  
        Jws<Claims> claimsJws = parser.parseClaimsJws(jwt);  
        //得到载荷中的用户数据  
        String subject = claimsJws.getBody().getSubject();  
        System.out.println(subject);  
    }catch (JwtException e){  
        //所有关于Jwt校验的异常都继承自JwtException  
        System.out.println("Jwt校验失败");  
        e.printStackTrace();  
    }  
} 

运行结果如下:

{"userId":123}  

以上便是 JWT 的生成与校验代码,你会发现在加解密过程中,服务器私钥 key 是保障 JWT 安全的命脉。对于这个私钥在生产环境它不能写死在代码中,而是加密后保存在 Nacos 配置中心统一存储,同时定期更换私钥以防止关键信息泄露。

讲到这应该你已掌握 JWT 的基本用法,但是在微服务架构下又该如何设计用户认证体系呢?

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

相关文章
|
16天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
14天前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
15天前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
33 1
服务架构的演进:从单体到微服务的探索之旅
|
13天前
|
消息中间件 监控 安全
后端架构演进:从单体到微服务####
在数字化转型的浪潮中,企业应用的后端架构经历了从传统单体架构到现代微服务架构的深刻变革。本文探讨了这一演进过程的背景、驱动力、关键技术及面临的挑战,揭示了如何通过微服务化实现系统的高可用性、扩展性和敏捷开发,同时指出了转型过程中需克服的服务拆分、数据管理、通信机制等难题,为读者提供了一个全面理解后端架构演变路径的视角。 ####
31 8
|
14天前
|
Cloud Native 安全 API
云原生架构下的微服务治理策略与实践####
—透过云原生的棱镜,探索微服务架构下的挑战与应对之道 本文旨在探讨云原生环境下,微服务架构所面临的关键挑战及有效的治理策略。随着云计算技术的深入发展,越来越多的企业选择采用云原生架构来构建和部署其应用程序,以期获得更高的灵活性、可扩展性和效率。然而,微服务架构的复杂性也带来了服务发现、负载均衡、故障恢复等一系列治理难题。本文将深入分析这些问题,并提出一套基于云原生技术栈的微服务治理框架,包括服务网格的应用、API网关的集成、以及动态配置管理等关键方面,旨在为企业实现高效、稳定的微服务架构提供参考路径。 ####
41 5
|
15天前
|
Kubernetes 负载均衡 Cloud Native
云原生架构下的微服务治理策略
随着云原生技术的不断成熟,微服务架构已成为现代应用开发的主流选择。本文探讨了在云原生环境下实施微服务治理的策略和方法,重点分析了服务发现、负载均衡、故障恢复和配置管理等关键技术点,以及如何利用Kubernetes等容器编排工具来优化微服务的部署和管理。文章旨在为开发者提供一套实用的微服务治理框架,帮助其在复杂的云环境中构建高效、可靠的分布式系统。
32 5
|
15天前
|
负载均衡 监控 Cloud Native
云原生架构下的微服务治理策略与实践####
在数字化转型浪潮中,企业纷纷拥抱云计算,而云原生架构作为其核心技术支撑,正引领着一场深刻的技术变革。本文聚焦于云原生环境下微服务架构的治理策略与实践,探讨如何通过精细化的服务管理、动态的流量调度、高效的故障恢复机制以及持续的监控优化,构建弹性、可靠且易于维护的分布式系统。我们将深入剖析微服务治理的核心要素,结合具体案例,揭示其在提升系统稳定性、扩展性和敏捷性方面的关键作用,为读者提供一套切实可行的云原生微服务治理指南。 ####
|
19天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
73 6
|
19天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
30 1
|
3月前
|
安全 应用服务中间件 API
微服务分布式系统架构之zookeeper与dubbo-2
微服务分布式系统架构之zookeeper与dubbo-2