桥接模式-多类型登录方式的思考

桥接模式-SSO单点登录

    • 背景:(业务细节已脱敏)
      • 需求:
      • 问题:
      • 解决方式:
    • OAuth2.0 实现单点登录
    • 四种授权模式
    • 桥接模式优化
      • 问题
      • 代码实现

背景:(业务细节已脱敏)

基于实习期间的一个代码重构的思考——业务细节已脱敏
基于内部旧框架实现业务toB管理系统,需要迁移数据并新的内部技术框架对进行代码重构

需求:

登录接口是旧项目已有的,包括原有系统的账号密码,邮箱,短信,以及第三方客户OA系统等方式进行授权,现需要对旧框架实现的登录功能迁移到中台进行统一集成

问题:

  1. 原有旧框架独立于中台,旧框住不再维护,需要迁移

  2. 业务有所变更,并且时常有变更且仍与k讨论,带有不确定性

  3. 原有登录方式都写到service类中,函数方法全部堆积在service,代码数量较大,结构混乱

解决方式:

通过桥接模式对登录方式的结构进行整理,完全符合开闭原则,提高代码的扩展性,应对仍在商讨的业务

Autho2详解文章

OAuth2.0 实现单点登录

  1. 什么是OAuth2.0

OAuth2.0 来实现第三方授权,基于第三方应用访问用户信息的权限(本质上就是给别人调用自己服务接口的权限)。

四种授权模式

  1. 客户端模式

这是最简单的一种模式,我们可以直接向验证服务器请求一个 Token(这里可能有些小伙伴对Token的概念不是很熟悉,Token 相当于是一个令牌,我们需要在验证服务器 (User Account And Authentication) 服务拿到令牌之后,才能去访问资源,比如用户信息、借阅信息等,这样资源服务器才能知道我们是谁以及是否成功登录了)
图片.png

  • 虽然这种模式比较简便,但是已经失去了用户验证的意义,
  • 压根就不是给用户校验准备的,而是更适用于服务内部调用的场景
  1. 密码模式

密码模式相比客户端模式,就多了用户名和密码的信息,用户需要提供对应账号的用户名和密码,才能获取到 Token
图片.png

  • 虽然这样看起来比较合理,但是会直接将账号和密码泄露给客户端,
  • 需要后台完全信任客户端不会拿账号密码去干其他坏事,所以这也不是我们常见的
  1. 隐式授权模式

首先用户访问页面时,会重定向到认证服务器,接着认证服务器给用户一个认证页面,等待用户授权,用户填写信息完成授权后,认证服务器返回 Token
图片.png
它适用于没有服务端的第三方应用页面,并且相比前面一种形式,验证都是在验证服务器进行的,敏感信息不会轻易泄露,但是 Token 依然存在泄露的风险 。

  1. 授权码模式

这种模式是最安全的一种模式,也是推荐使用的一种,比如我们手机上的很多 App 都是使用的这种模式。

相比隐式授权模式,它并不会直接返回 Token,而是返回授权码,真正的 Token 是通过应用服务器访问验证服务器获得的。在一开始的时候,应用服务器(客户端通过访问自己的应用服务器来进而访问其他服务)和验证服务器之间会共享一个 secret,这个东西没有其他人知道,而验证服务器在用户验证完成之后,会返回一个授权码,应用服务器最后将授权码和 secret 一起交给验证服务器进行验证,并且 Token 也是在服务端之间传递,不会直接给到客户端
图片.png

  • 这样就算有人中途窃取了授权码,也毫无意义,因为,Token 的获取必须同时携带授权码和 secret ,
  • 但是 secret 第三方是无法得知的,并且 Token 不会直接丢给客户端,大大减少了泄露的风险。

安全性高的原因

  1. 是在应用服务器上进行验证,不会返回token给前端

为什么先获取到code,再申请取token

  1. 要换取access_token的三要素:用户的同意授权+第三方appid+第三方app_secret
  2. 用户输入验证信息后拿到code,说明用户同意授权,但是用户拿不到服务器的第三方appid+第三方app_secret
  3. 服务器将返回的code,同第三方appid和app_secret一起发送,才能满足三要素拿到真正的token

桥接模式优化

图片.png桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化 。

问题

  1. 为什么不直接在service里面直接每种登录方式写一个方法,何必像桥接模式每种登录方式写一个实现类
  1. 在service里面给每种登录写一个实现方法,我们需要滑动整个文件才能知道有多少个方法,而桥接模式一个登录方式一个类单独维护,一目了然。
  2. 如果某种登录方式在service实现非常复杂,像需要Autho2等方式的授权,需要挤下很多的方法,肯定是不利于维护的。
  1. 为什么不用策略模式
  1. 我觉得桥接模式中包含了策略模式,比如第三方登录的就可以根据不同的需要,选择不同的登录策略,引入桥接模式可以针对登录这一模块添加跟登录相关的其他接口,比如注册,退出登录等接口,注册的话也可以选择不同策略。另外像支付一样比较单一功能的接口一般使用策略模式就足够。 桥接模型更像: 多个接口x多个实现类, 策略模式更像:一个接口类x多实现类。
  2. 此桥接模式的使用,还根据情况使用了单例模式和工厂模式
  1. 桥接模式有什么好处

完全开闭原则,新增方法无需修改原代码
类结构清晰,不会全部接口积压在一个service中

代码实现

完整结构图
图片.png
但是下面的代码对该图的一些瑕疵进行了一下优化

  1. 瑕疵:上图中的右侧子实现类必须实现右侧接口的全部方法
  • 通过在接口层和实现层两者中间引入 抽象层 来解决这个问题
  1. 瑕疵:每次使用右侧子实现类都要new出来吗?
  • 通过工厂模式+单例模式实现右侧子实现类的单例懒加载

具体代码:

  1. 右侧登录方式接口层
public interface RegisterLoginFuncInterface {public String login(String account, String password);public String register(UserInfo userInfo);public boolean checkUserExists(String userName);public String login3rd(HttpServletRequest request);
}
  1. 右侧登录方式的抽象层
public abstract class AbstractRegisterLoginFunc implements RegisterLoginFuncInterface {protected String commonLogin(String account, String password, UserRepository userRepository) {UserInfo userInfo = userRepository.findByUserNameAndUserPassword(account, password);if(userInfo == null) {return "account / password ERROR!";}return "Login Success";}protected String commonRegister(UserInfo userInfo, UserRepository userRepository) {if(commonCheckUserExists(userInfo.getUserName(), userRepository)) {throw new RuntimeException("User already registered.");}userInfo.setCreateDate(new Date());userRepository.save(userInfo);return "Register Success!";}protected boolean commonCheckUserExists(String userName, UserRepository userRepository) {UserInfo user = userRepository.findByUserName(userName);if(user == null) {return false;}return true;}public String login(String account, String password) {throw new UnsupportedOperationException();}public String register(UserInfo userInfo){throw new UnsupportedOperationException();}public boolean checkUserExists(String userName){throw new UnsupportedOperationException();}public String login3rd(HttpServletRequest request) {throw new UnsupportedOperationException();}
}
  1. 右侧具体登录方式的实现类
@Component
public class RegisterLoginByDefault extends AbstractRegisterLoginFunc implements RegisterLoginFuncInterface {@Autowiredprivate UserRepository userRepository;@PostConstructprivate void initFuncMap() {RegisterLoginComponentFactory.funcMap.put("Default", this);}@Overridepublic String login(String account, String password) {return super.commonLogin(account, password, userRepository);}@Overridepublic String register(UserInfo userInfo) {return super.commonRegister(userInfo, userRepository);}@Overridepublic boolean checkUserExists(String userName) {return super.commonCheckUserExists(userName, userRepository);}
}

Gitee只是第三方登录的一种,其余微信,支付宝等第三方登录可以按需要无缝接入,这里只写一个Gitee作为案例

@Component
public class RegisterLoginByGitee extends AbstractRegisterLoginFunc implements RegisterLoginFuncInterface {@Value("${gitee.state}")private String giteeState;@Value("${gitee.token.url}")private String giteeTokenUrl;@Value("${gitee.user.url}")private String giteeUserUrl;@Value("${gitee.user.prefix}")private String giteeUserPrefix;@Autowiredprivate UserRepository userRepository;@PostConstructprivate void initFuncMap() {RegisterLoginComponentFactory.funcMap.put("GITEE", this);}@Overridepublic String login3rd(HttpServletRequest request) {String code = request.getParameter("code");String state = request.getParameter("state");if(!giteeState.equals(state)) {throw new UnsupportedOperationException("Invalid state!");}//请求Gitee平台获取token,并携带codeString tokenUrl = giteeTokenUrl.concat(code);JSONObject tokenResponse = HttpClientUtils.execute(tokenUrl, HttpMethod.POST);String token = String.valueOf(tokenResponse.get("access_token"));System.out.println(token);//请求用户信息,并携带 tokenString userUrl = giteeUserUrl.concat(token);JSONObject userInfoResponse = HttpClientUtils.execute(userUrl, HttpMethod.GET);//获取用户信息,userName添加前缀 GITEE@, 密码保持与userName一致。讨论过程请参见2.3小节String userName = giteeUserPrefix.concat(String.valueOf(userInfoResponse.get("name")));String password = userName;return autoRegister3rdAndLogin(userName, password);}private String autoRegister3rdAndLogin(String userName, String password) {//如果第三方账号已经登录过,则直接登录if(super.commonCheckUserExists(userName, userRepository)) {return super.commonLogin(userName, password, userRepository);}UserInfo userInfo = new UserInfo();userInfo.setUserName(userName);userInfo.setUserPassword(password);userInfo.setCreateDate(new Date());//如果第三方账号是第一次登录,先进行“自动注册”super.commonRegister(userInfo, userRepository);//自动注册完成后,进行登录return super.commonLogin(userName, password, userRepository);}
}
  1. 左侧抽象调用入口类
public abstract class AbstractRegisterLoginComponent {protected RegisterLoginFuncInterface funcInterface;public AbstractRegisterLoginComponent(RegisterLoginFuncInterface funcInterface) {validate(funcInterface);this.funcInterface = funcInterface;}protected final void validate(RegisterLoginFuncInterface funcInterface) {if(!(funcInterface instanceof RegisterLoginFuncInterface)) {throw new UnsupportedOperationException("Unknown register/login function type!");}}public abstract String login(String username, String password);public abstract String register(UserInfo userInfo);public abstract boolean checkUserExists(String userName);public abstract String login3rd(HttpServletRequest request);
}
  1. 左侧具体入口子类
public class RegisterLoginComponent extends AbstractRegisterLoginComponent{public RegisterLoginComponent(RegisterLoginFuncInterface funcInterface) {super(funcInterface);}@Overridepublic String login(String username, String password) {return funcInterface.login(username, password);}@Overridepublic String register(UserInfo userInfo) {return funcInterface.register(userInfo);}@Overridepublic boolean checkUserExists(String userName) {return funcInterface.checkUserExists(userName);}@Overridepublic String login3rd(HttpServletRequest request) {return funcInterface.login3rd(request);}
}
  1. 工厂模式对实现类进行懒加载
  • 子实现类的创建运用了工厂模式+单例模式实现懒加载
public class RegisterLoginComponentFactory {// 缓存 AbstractRegisterLoginComponent(左路)。根据不同的登录方式进行缓存public static Map<String, AbstractRegisterLoginComponent> componentMap= new ConcurrentHashMap<>();// 缓存不同类型的实现类(右路),如:RegisterLoginByDefault,RegisterLoginByGiteepublic static Map<String, RegisterLoginFuncInterface> funcMap= new ConcurrentHashMap<>();// 根据不同的登录类型,获取 AbstractRegisterLoginComponentpublic static AbstractRegisterLoginComponent getComponent(String type) {//如果存在,直接返回AbstractRegisterLoginComponent component = componentMap.get(type);if(component == null) {//并发情况下,汲取双重检查锁机制的设计,如果componentMap中没有,则进行创建synchronized (componentMap) {component = componentMap.get(type);if(component == null) {//根据不同类型的实现类(右路),创建RegisterLoginComponent对象,//并put到map中缓存起来,以备下次使用。component = new RegisterLoginComponent(funcMap.get(type));componentMap.put(type, component);}}}return component;}
}
  1. controller调用
@RestController
@RequestMapping("/bridge")
public class UserBridgeController {@Autowiredprivate UserBridgeService userBridgeService;@PostMapping("/login")public String login(String account, String password) {return userBridgeService.login(account, password);}@PostMapping("/register")public String register(@RequestBody UserInfo userInfo) {return userBridgeService.register(userInfo);}@GetMapping("/gitee")public String gitee(HttpServletRequest request) throws IOException {return userBridgeService.login3rd(request, "GITEE");}
}

仅个人思考,有错请指出
在这里插入图片描述

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

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

相关文章

uni-app 手记集。

1、uni-app 是一个使用 Vue.js 开发的前端应用的框架&#xff0c;所以不会Vue.js的小伙伴可以先去看看Vue.js的基础教学。 2、.vue文件结构 <template><div class"container"></div> </template><script type"text/ecmascript-6&q…

【DiskGenius硬盘分区】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

TCP系列相关内容

一、TCP上传文件 loop——本地回环测试地址。 void *memset&#xff08;void *s,int c,size_t n&#xff09;——给一个变量设定一个值。 1、“粘包”问题 两次分别发送的数据&#xff0c;被一起接收形成该现象。 原因&#xff1a;TCP流式套接字&#xff0c;数据与数据间没…

前端速通面经八股系列(二)—— HTML篇

HTML高频面经八股目录 1. src和href的区别2. 对HTML语义化的理解3. DOCTYPE(⽂档类型) 的作⽤4. script标签中defer和async的区别5. 常⽤的meta标签有哪些6. HTML5有哪些更新1. 语义化标签2. 媒体标签3. 表单4. 进度条、度量器5.DOM查询操作6. Web存储7. 其他 7. img的srcset属…

三种通过代码创建矢量文件的方法及例子

现有四个点&#xff1a;(1, 1), (2, 2), (3, 3), (4, 4) 以这四个点围起来就是一个面。 如何通过python创建矢量文件。 我们以创建一个面矢量文件为例子&#xff0c;进行阐释。 我们可以使用geopandas、fiona、gdal库完成矢量创建。 geopandas 假设我们创建的矢量文件格式…

Chrome H265 WebRTC 支持

Chrome从127版本开始支持RTC H265解码&#xff0c;这样服务器就不需要对H265转码了&#xff0c; H5S和USC会自动检测浏览器支持的解码类型并自动判断是否启动转码&#xff0c;这样客户端不用关心摄像机具体是H264还是H265&#xff0c;尽量使用带GPU的客户端&#xff0c;这样服务…

ArcGIS应用指南:近邻分析(点匹配到最近线段上)

近邻分析通常用于确定一个要素集中的要素与另一个要素集中最近要素的距离。当涉及到点匹配到最近的线时&#xff0c;这种分析可以用来确定每个点到最近线段的距离及位置&#xff0c;也就是我们常说的点匹配到最近线上&#xff0c;可以参考官方文档&#xff1a;近邻分析 (Covera…

动态规划之买卖股票篇-代码随想录算法训练营第三十八天| 买卖股票的最佳时机ⅠⅡⅢⅣ,309.最佳买卖股票时机含冷冻期,714.买卖股票的最佳时机含手续费

121. 买卖股票的最佳时机 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 讲解视频&#xff1a; 动态规划之 LeetCode&#xff1a;121.买卖股票的最佳时机1 题目描述&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定…

软件测试-Selenium+python自动化测试

目录 一、元素定位 1.1一个简单的模板 1.2单选框radio定位实战 1.3下拉操作 1.4弹窗 1.5文件上传 1.6 iframe(类似于页中页,嵌套进去了) 二、元素定位实战 会用到谷歌浏览器Chrome测试,需要下载一个Chromedriver(Chrome for Testing availability)对应自己的浏览…

华为2024 届秋招招聘——硬件技术工程师-电源方向-机试题(四套)(每套四十题)

华为 2024 届秋招——硬件-电源机试题&#xff08;四套&#xff09;&#xff08;每套四十题&#xff09; 岗位——硬件技术工程师 岗位意向——电源 真题题目分享&#xff0c;完整版带答案(有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&#xff0c;仅供参考&am…

OpenCV杂项图像变换(1)自适应阈值处理函数adaptiveThreshold()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 函数对数组应用自适应阈值。 该函数根据以下公式将灰度图像转换为二值图像&#xff1a; 对于 THRESH_BINARY: t e x t d s t ( x , y ) { maxV…

OpenAI API: How to count tokens before API request

题意&#xff1a;“OpenAI API&#xff1a;如何在 API 请求之前计算令牌数量” 问题背景&#xff1a; I would like to count the tokens of my OpenAI API request in R before sending it (version gpt-3.5-turbo). Since the OpenAI API has rate limits, this seems impor…

【网络安全】分析cookie实现PII IDOR

未经许可,不得转载。 文章目录 正文正文 目标:公共电子商务类型的网站,每月有大约6万到10万访问者,注册用户大约有5万。 存在一个查询个人资料的端点/GetProfiledetails,以下是完整的请求和响应: 我发现,cookie非常类似于base64编码后的结果,于是我将其进行base64解码…

windows虚拟机VMware共享文件

1、设置本机电脑共享目录 2、设置所有人可连接 3、记录共享文件夹路径 4、设置当前用户密码 5、在虚拟机内映射驱动 6、在虚拟机内添入路径 7、输入用户名和密码 8、链接成功

天玑9400顶级图形技术曝光,GPU新技术让光追画质超一个档次

近日&#xff0c;有关联发科旗下最新旗舰芯片天玑9400的消息引发了广泛关注。据悉&#xff0c;该芯片在图形技术上取得了显著突破&#xff0c;光追性能提升近20%&#xff0c;并首发一项新的光追技术&#xff0c;该技术堪比PC端的顶级光追技术OMM&#xff0c;有望为移动端带来前…

Oracle数据库

注意&#xff1a;其实oracle数据库跟mysql数据库基本语法大致一样只有小部分语言存在差别。 安装PL/SQL Developer 一.数据库实例 1.1 启动数据库实例 一个Oracle实例&#xff08;Oracle Instance&#xff09;有一系列的后台进程&#xff08;Backguound Processes)和内存结构…

2024最新版Python+Pycharm安装教程,安装、环境配置、汉化全搞定,保姆级教学!

一、Python下载 为了节约时间&#xff0c;我将PythonPycharm安装包、集火码全部打包上传至CSDN官方&#xff0c;可放心下载&#xff0c;完全免费&#xff01;&#xff08;安装包均为最新版本&#xff09; 二、Python安装 1.双击运行本地文件夹下的python安装包&#xff08;以…

Unity与UE,哪种游戏引擎适合你?

PlayStation vs Xbox&#xff0c;Mario vs Sonic&#xff0c;Unreal vs Unity&#xff1f;无论是游戏主机、角色还是游戏引擎&#xff0c;人们总是热衷于捍卫他们在游戏行业中的偏爱。 专注于游戏引擎&#xff0c;Unity和Unreal Engine&#xff08;简称UE4&#xff09;是目前市…

QT 与 C++实现基于[ TCP ]的聊天室界面

TCP客户端 Widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpSocket> //客户端类 #include <QMessageBox> #include <QListWidgetItem> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } …

【操作系统】实验:文件系统

目录 一、实验目的 二、实验要求 三、实验步骤 四、核心代码 五、记录与处理 六、思考 七、完整报告和成果文件提取链接 一、实验目的 1、掌握文件系统的基本结构和文件系统的管理方法 2、加深对两级文件目录认识和理解 3、对文件操作的系统命令实质内容和执行过程深入…