真香定律!我用这种模式重构了第三方登录

分享是最有效的学习方式。

博客:https://blog.ktdaddy.com/

老猫的设计模式专栏已经偷偷发车了。不甘愿做crud boy?看了好几遍的设计模式还记不住?那就不要刻意记了,跟上老猫的步伐,在一个个有趣的职场故事中领悟设计模式的精髓吧。还等什么?赶紧上车吧。

故事

办公室里,小猫托着腮帮对着电脑陷入了思考。就在刚刚,他接到了领导指派的一个任务,业务调整,登录方式要进行拓展。例如需要接入第三方的微信登录,企业微信授权登录等等。

原因大概是这样,现在大环境不好,原来面向B端企业员工的电商业务并不好做,新客拓展比较困难,业务想要有更好的起色着实比较困难,所以决策层决定要把登录的口子放开,原来支持手机密码登录以及手机验证码进行登录,现在为了更好地推广,需要支持微信扫码关注企业公众号后登录,企业微信,微博等等一些列的第三方登录模式。

说白了未来到底会有多少种登录方式不得而知,那么面对这样一个棘手的问题,小猫又该何去何从?

概述

登录问题相信后端小伙伴都有接触过,最简单的可能就是做一个权限系统就会用到登录名+密码+验证码进行登录,继而稍微复杂一些可能会涉及手机验证码登录。现在随着第三方平台的层出不穷,我们很多网站其实都提供了联合登录。用户掏出手机简单地一个扫码动作即可完成初步的注册登录功能。这种方式一定程度上能够给当前的网站带来更多的流量。

关于小猫遇到的问题,咱们尝试从下面几个点去解决。

在这里插入图片描述

登录演化

聊到登录,我们首先去了解一下整个登录认证的发展阶段,以及目前比较常见也相对比较复杂的微信公众号授权登录流程。

基于Cookie/Session进行验证登录

在早期,也就是可能是单体系统的时代,亦或者站在java开发者角度来说是jsp时代的时候,我们用的登录方式就是Cookie/Session验证的方式。关于Cookie以及Session相信很多后端的小伙伴都应该知道,当然若真有不清楚的,大家可以自己查阅一下相关资料。
利用这种方式登录的流程其实还是比较简单的,如下流程:

在这里插入图片描述

基于上述登录成功后,服务端将用户的身份信息存储在Session里,并将session ID通过cookie传递给客户端。后续的数据请求都会带上cookie,服
务端根据cookie中携带的session id来得到辨别用户身份。

简单的java伪代码如下:

...
session.setAtrrbuite("user",user);
...
session.getAttrbuite("user");

当然上述的伪代码还是基于最最原始的写法去写的,关于这种登录的框架,其实目前市面上也有比较成熟的,例如轻量级的shiro,spring本身自带的权限认证框架也有。

随着业务的发展,系统访问量级的增大,我们渐渐发现这种方式存在着一些问题:

  1. 由于服务端需要对接大量的客户端,也就需要存放大量的Seesion ID,这样就会导致服务器压力过大。如果服务器是个集群,为了同步登录的状态,需要将Session ID同步到每一台服务器上,无形中增加了服务器端的维护成本。
  2. 由于Session ID存放在Cookie中,所以无法避免CSRF攻击(跨站请求伪造)。

当然其他问题也欢迎小伙伴们进行补充。
为了解决这一些列的问题,我们渐渐演化出了另外一种登录认证方式————基于token进行认证登录。

基于TOKEN进行认证登录

现在的系统大部分都是前后端分离开发的。后端大多使用了WEB API,此时token无疑是处理认证的最好方式。

Session 方案中用户信息(以Session记录形式)存储在服务端。而Token方案中(以Token形式)存储在客户端,服务端仅验证Token合法性即可。基于Token的身份验证是无状态的,不将用户信息存在服务器中。这种概念解决了在服务端存储信息时的许多问题。NoSession意味着咱们的程序可以根据需要去增减机器,而不用去担心用户是否登录。

咱们一起来看一下如果使用TOKEN整个流程。

在这里插入图片描述

关于上述token机制的特点有以下几点:

  • 无状态、可扩展:在客户端存储的Token是无状态的,并且能够被扩展。基于这种无状态的和不存储Session信息,所以不会对服务器端造成压力,负载均衡器能够将用户信息从一个服务器传到其他服务器上,即使是服务器集群,也不需要增加维护成本。

  • 可扩展性:Tokens能够创建与其它程序共享权限的程序。(即,我们所说的第三方平台联合登录的时候,token的生成机制以及验证可以由第三方系统进行联合验证登录)

  • 安全性:请求中发送Token而不是发送Cookie,能够防止CSRF(跨站请求伪造)。即客户端使用Cookie存储了Tooken,Cookie也仅仅是一个存储机制而不是用于认证。不将信息存储在Session中,让我们少了对Session的操作。Token也可以存放在前端任何地方,可以不用保存在Cookie中,提升了页面的安全性。Token是会失效的,一段时间之后用户需要重新验证。

  • 多平台跨域:对应用程序和服务进行扩展的时候,需要介入各种各种的设备和应用程序。只要用户有一个通过了验证的token,数据和资源就能够在任何域上被请求到。

微信扫码跳转公众号认证登录

这也是后续小猫遇到的问题,以及需要和其他第三方Api主要对接的。其实关于扫码认证登录也是基于token机制的一种拓展。只不过第三方的平台在token机制上新增了获取二维码进行二次确认的过程。咱们以微信扫码跳转公众号登录为例来看一下整个流程。其他的第三方登录流程其实也是大同小异,咱们了解一个流程即可,不同的平台只是对接不同的api而已。流程图如下:

在这里插入图片描述

从上面这幅图看到,扫码登录其实复杂就复杂在获取token这个步骤上,当获取完毕token之后,其后续的业务逻辑其实基本也是一样的。

其实其他第三方的登录其实也是大同小异,最主要的难点是在如何获取token上,我们只要认真看完对接的api,其实问题也基本都能迎刃而解。

说明一下,老猫这里绘图用了drawio工具,如果想要知道老猫的绘图思路,大家可以看看这里《绘图思路》

如何兼容多套?

看完上述之后,相信大家会对认证登录心里有杆秤了。细节方面其实只要去查询相关平台的api,然后去撸代码就好了。但是实现一套倒是还好,但是现在小猫遇到的问题是需要在原逻辑上去丰富登录的代码。如果在老的代码上通过if else的方式去实现多套登录逻辑,那估计后面又是屎山。

这里,其实我们可以引入“适配器设计模式”去解决这样的问题。

什么是适配器模式?

适配器模式(英文名:Adapter Pattern)是指将一个类的接口转换成用户期望的另一个接口,使得原本接口不兼容的类可以一起工作。

适配器模式可以分为两类:对象适配器模式和类适配器模式。对象适配器模式通过组合实现适配,而类适配器模式则通过继承实现适配。

此外,还有一种特殊的适配器模式——缺省适配器模式它由一个抽象类实现,并在其中实现目标接口中所规定的所有方法,但这些方法的实现通常是空方法,由具体的子类来实现具体的功能。适配器模式的应用可以提高代码的复用性和可维护性,同时帮助解决不同接口之间的兼容性问题。

上面的概念比较抽象,其实在咱们的日常生活中也有这样的例子,例如手机充电转换头,显示器转接头等等。

适配器模式重构第三方登录

话不多说,直接开干,我们就针对小猫的遇到这个第三方登录的场景,咱们用代码重构一把。(当然,这里我们侧重的还是伪代码)。跟着老猫,咱们一步步走好代码的演化。

咱们先看一下老的业务代码,如下:

public class UserLoginService {public ApiResponse<String> regist(String userName,String password) {//...dosomethingreturn ApiResponse.success("success");}public ApiResponse login(String userName, String password) {return null;}
}

接下来由于小猫的业务会发生变更,新的登录方式会层出不穷,所以,我们得遵循之前提到的软件设计原则去更好地写一下业务代码。我们遵循之前提到的开闭原则,于是我们迈出了重构代码的第一步,我们将创建一个新的第三方登录的类来专门处理第三方的登录对接。如下:

public class ThirdPartyUserLoginService extends UserLoginService {public ApiResponse loginForQQ(String openId) {/*** openid 全局唯一,咱们直接作为用户名* 默认密码QQ_EMPTY* 注册(原来父类中有注册实现)* 调用原来的登录*/return loginForRegist(openId, null);}public ApiResponse loginForWechat(String openId) {return null;}public ApiResponse loginForToken(String token) {return null;}public ApiResponse loginForTel(String tel, String code) {return null;}public ApiResponse<String> loginForRegist(String userName, String password) {super.login(userName, password);return super.login(userName, password);}
}

写到这里,其实咱们已经集成了多种登录方式的代码兼容,但是这种实现方式显然是不太优雅的,看起来比较死板,在登录的时候我们甚至还得去判断客户到底是用什么去做登录的,然后去分别调用不同第三方平台的认证方式。

我们接下来演化开始用适配器。如下代码:
首先我们定义出一个标准的适配接口:

public interface LoginAdapter {boolean support(Object adapter);ApiResponse login(String id,Object adapter);
}

根据上面我们看到,我们有QQ方式登录,有微信方式登录,有电话验证码方式登录。所以我们对应的就应该有相关的这些方式的适配器的实现。由于代码重复,所以在此老猫就写QQ和微信这两种伪代码,其他的暂时先偷个懒。

/*** @author 公众号:程序员老猫* @date 2024/3/3 22:47*/
public class LoginForQQAdapter implements LoginAdapter {@Overridepublic boolean support(Object adapter) {return adapter instanceof LoginForQQAdapter;}@Overridepublic ApiResponse login(String id, Object adapter) {return null;}
}public class LoginForWeChatAdapter implements LoginAdapter {@Overridepublic boolean support(Object adapter) {return adapter instanceof LoginForWeChatAdapter;}@Overridepublic ApiResponse login(String id, Object adapter) {return null;}
}

有了这些适配器之后,我们就统一对外给出去接口:


public interface IPassportForThird {ApiResponse loginForQQ(String openId);ApiResponse loginForWechat(String openId);ApiResponse<String> loginForRegist(String userName, String password);
}

最后创建统一适配器。

@Slf4j
public class PassportForThirdAdapter extends UserLoginService implements IPassportForThird{@Overridepublic ApiResponse loginForQQ(String openId) {return doLogin(openId,LoginForQQAdapter.class);}@Overridepublic ApiResponse loginForWechat(String openId) {return doLogin(openId,LoginForWeChatAdapter.class);}@Overridepublic ApiResponse<String> loginForRegist(String userName, String password) {super.login(userName, password);return super.login(userName, password);}//用到简单工厂模式以及策略模式private ApiResponse doLogin(String openId,Class<? extends LoginAdapter> clazz) {try {LoginAdapter adapter = clazz.newInstance();if(adapter.support(adapter)){return adapter.login(openId,adapter);}}catch (Exception e) {log.error("exception is",e);}return null;}
}

最终我们看一下实现的类图:

在这里插入图片描述

上述我们就用了适配器的模式简单重构了现有的第三方登录的代码,当然上述可能还存在一些代码的缺陷,大家也不要太过较真,在此给大家在日常开发中多点思路。

大家可能会对每个适配器的support()方法有点疑问,用来决断兼容。这里support()方法的参数也是Object类型的,而support()方法来自接口。适配器的实现并不依赖接口,其实我们也可以直接将LoginAdapter移除。

在上述重构的例子中,其实咱们不仅仅用到了适配器模式,其实还用到了简单工厂模式的特性。

总结

其实在我们日常的开发中,适配器模式是比较常用的一种设计模式,不仅仅使用上述场景,其实在很多其他api的对接的场景也有适用。例如,在电商业务场景中会涉及到各种对接,说到买卖就会牵扯到供应商的对接,第三方分销渠道客户的对接,其中必然涉及模型不一致需要适配转换的场景,比如供应商商品信息和标准商城商品信息等等。当然老猫在此也只是做了一下简单罗列。希望大家在后面的工作中可以参考用到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/719780.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023人机交互期末复习

考试题型及分值分布 1、选择题&#xff08;10题、20分&#xff09; 2、填空题&#xff08;10题、20分&#xff09; 3、判断题&#xff08;可选、5题、10分&#xff09; 4、解答题&#xff08;5~6题、30分&#xff09; 5、分析计算题&#xff08;1~2题、20分&#xff09; 注意&…

PHP+MySQL实现后台管理系统增删改查之够用就好

说明 最近要给博客弄个后台&#xff0c;不想搞得很复杂&#xff0c;有基本的增删改查就够了&#xff0c;到网上找了一圈发现这个不错&#xff0c;很实用&#xff0c;希望可以帮到大家&#xff0c;需要的朋友评论区留下邮箱&#xff0c;我安排发送。 演示效果 项目介绍 本项目…

带使能控制的锂电池充放电解决方案

一、产品概述 TP4594R 是一款集成线性充电管理、同步升压转换、电池电量指示和多种保护功能的单芯片电源管理 SOC&#xff0c;为锂电池的充放电提供完整的单芯片电源解决方案。 TP4594R 内部集成了线性充电管理模块、同步升压放电管理模块、电量检测与 LED 指示模块、保护模块…

关于python函数参数传递

参数传递 在 python 中&#xff0c;类型属于对象&#xff0c;对象有不同类型的区分&#xff0c;变量是没有类型的&#xff1a; 在下面的代码示例重&#xff0c;[1,2,3] 是 List 类型&#xff0c;“qayrup” 是 String 类型&#xff0c;而变量 a 是没有类型&#xff0c;它仅仅…

#WEB前端

1.实验&#xff1a;vscode安装&#xff0c;及HTML常用文本标签 2.IDE&#xff1a;VSCODE 3.记录&#xff1a; &#xff08;1&#xff09;网页直接搜索安装vscode &#xff08;2&#xff09;打开vscode&#xff0c;在下图分别安装以下插件&#xff1a; Html Css Support …

C++11线程同步之互斥锁

C11线程同步之互斥锁 std::mutex成员函数线程同步 std::lock_guardstd::recursive_mutexstd::timed_mutex 进行多线程编程&#xff0c;如果多个线程需要对同一块内存进行操作&#xff0c;比如&#xff1a;同时读、同时写、同时读写对于后两种情况来说&#xff0c;如果不做任何的…

《互联网的世界》第四讲-拥塞控制与编码

需要澄清的一个误区是&#xff0c;拥塞绝不是发送的数据量太大导致&#xff0c;而是数据在极短的时间段内到达了同一个地方以至于超过了网络处理容量导致&#xff0c;拥塞的成因一定要考虑时间因素。换句话说&#xff0c;拥塞由大突发导致。 只要 pacing&#xff0c;再多的数据…

动态规划(算法竞赛、蓝桥杯)--树形DP树形背包

1、B站视频链接&#xff1a;E18 树形DP 树形背包_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N110; int n,V,p,root; int v[N],w[N]; int h[N],to[N],ne[N],tot; //邻接表 int f[N][N];void add(int a,int b){to[tot]b;ne[tot]h[a];h[a…

数仓项目6.0(一)

尚硅谷大数据项目【电商数仓6.0】企业数据仓库项目_bilibili 数据流转过程 用户➡️业务服务器➡️数据库存储➡️数仓统计分析➡️数据可视化 数据仓库处理流程&#xff1a;数据源➡️加工数据➡️统计筛选数据➡️分析数据 数据库不是为了数据仓库服务的&#xff0c;需要…

B084-SpringCloud-Zuul Config

目录 zuul系统架构和zuul的作用zuul网关实现配置映射路径过滤器 Config概述云端管理本地配置 zuul zuul是分布式和集群后前端统一访问入口 系统架构和zuul的作用 zuul把自己注册进eureka&#xff0c;然后可通过前端传来的服务名发现和访问对应的服务集群 为了预防zuul单点故…

【OJ】求和与计算日期

文章目录 1. 前言2. JZ64 求123...n2.1 题目分析2.2 代码 3. HJ73 计算日期到天数转换3.1 题目分析3.2 代码 4. KY222 打印日期4.1 题目分析4.2 代码 1. 前言 下面两个题目均来自牛客&#xff0c;使用的编程语言是c&#xff0c;分享个人的一些思路和代码。 2. JZ64 求123…n …

毕业生信息招聘平台|基于springboot+ Mysql+Java的毕业生信息招聘平台设计与实现(源码+数据库+文档+PPT)

目录 论文参考 摘 要 数据库设计 系统详细设计 文末获取源码联系 论文参考 摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 毕业生信息招聘平台&#xff0c;主要的模块包括查看管理员&a…

基于Springboot的无人智慧超市管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的无人智慧超市管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

【每日刷题】数组-LC56、LC238、随想录1、LC560

1. LC56 合并区间 题目链接 Arrays.sort先让intervals里的子数组按照子数组的第一个数字值从小到大排列。开一个新数组&#xff0c;newInterval&#xff0c;存放合并好的子数组让intervals的当前子数组i的第一个数字与newInterval的当前子数组index的最后一个数字比较大小&am…

Intel/国产化无人叉车机器视觉专用控制器

无人叉车和机器视觉是两个独立的技术领域&#xff0c;但它们可以结合使用以实现更高效的物流自动化。无人叉车是一种自动化运输工具&#xff0c;可以在没有人为干预的情况下完成货物的搬运和运输。机器视觉是一种人工智能技术&#xff0c;可以让计算机识别和理解图像或视频中的…

FPGA时序约束与分析--建立时间与保持时间

文章目录 前言一、定义二、举例说明2.1 建立时间违规2.2 保持时间违规前言 时序约束的定义–设计者根据实际的系统功能,通过时序约束的方式提出时序要求; FPGA 编译工具根据设计者的时序要求,进行布局布线;编译完成后, FPGA 编译工具还需要针对布局布线的结果,套用特定的…

搜索回溯算法(DFS)1------递归

目录 简介&#xff1a; 递归问题解题的思路模板 例题1&#xff1a;汉诺塔 例题2&#xff1a;合并两个有序链表 例题3&#xff1a;反转链表 例题4&#xff1a;两两交换链表中的节点 例题5&#xff1a;Pow&#xff08;x,n&#xff09;-快速幂 结语&#xff1a; 简介&…

嵌入式驱动学习第二周——断言机制

前言 这篇博客来聊一聊C/C的断言机制。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程&#xff0c;未来预计四个月将高强度更新本专栏&#xff0c;喜欢的可以关注本博主并订阅本专栏&#xff0c;一起讨论一起学习。现在关注就是老粉啦&#xff01; 目录 前言1. 断言介绍…

贪心 Leetcode 134 加油站

加油站 Leetcode 134 学习记录自代码随想录 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发&#xff0c;开始时油…

串联所有单词的子串

题目链接 串联所有单词的子串 题目描述 注意点 words[i] 和 s 由小写英文字母组成1 < words.length < 5000可以以 任意顺序 返回答案words中所有字符串长度相同 解答思路 根据滑动窗口哈希表解决本题&#xff0c;哈希表存储words中所有的单词及单词的出现次数&#…