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

news2024/11/13 9:26:36

桥接模式-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 {

    @Autowired
    private UserRepository userRepository;

    @PostConstruct
    private void initFuncMap() {
        RegisterLoginComponentFactory.funcMap.put("Default", this);
    }

    @Override
    public String login(String account, String password) {
        return super.commonLogin(account, password, userRepository);
    }

    @Override
    public String register(UserInfo userInfo) {
        return super.commonRegister(userInfo, userRepository);
    }

    @Override
    public 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;

    @Autowired
    private UserRepository userRepository;

    @PostConstruct
    private void initFuncMap() {
        RegisterLoginComponentFactory.funcMap.put("GITEE", this);
    }

    @Override
    public 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,并携带code
        String tokenUrl = giteeTokenUrl.concat(code);
        JSONObject tokenResponse = HttpClientUtils.execute(tokenUrl, HttpMethod.POST);
        String token = String.valueOf(tokenResponse.get("access_token"));
        System.out.println(token);
        //请求用户信息,并携带 token
        String 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);
    }

    @Override
    public String login(String username, String password) {
        return funcInterface.login(username, password);
    }

    @Override
    public String register(UserInfo userInfo) {
        return funcInterface.register(userInfo);
    }

    @Override
    public boolean checkUserExists(String userName) {
        return funcInterface.checkUserExists(userName);
    }

    @Override
    public String login3rd(HttpServletRequest request) {
        return funcInterface.login3rd(request);
    }
}

  1. 工厂模式对实现类进行懒加载
  • 子实现类的创建运用了工厂模式+单例模式实现懒加载
public class RegisterLoginComponentFactory {
    // 缓存 AbstractRegisterLoginComponent(左路)。根据不同的登录方式进行缓存
    public static Map<String, AbstractRegisterLoginComponent> componentMap
            = new ConcurrentHashMap<>();
    // 缓存不同类型的实现类(右路),如:RegisterLoginByDefault,RegisterLoginByGitee
    public static Map<String, RegisterLoginFuncInterface> funcMap
            = new ConcurrentHashMap<>();

    // 根据不同的登录类型,获取 AbstractRegisterLoginComponent
    public 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 {

    @Autowired
    private 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.coloradmin.cn/o/2080706.html

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

相关文章

ABB巨资收购一家电气龙头,为当年卖给日立电气业务回血

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 战略扩张&#xff1a;ABB携SEAM集团深耕电气服务市场 在电气服务领域&#xff0c;ABB再次展现了其强大的市场扩张能力。近日&#xff0c;ABB宣布…

ARP诈骗

学习资料&#xff1a; arpspoof安装和5分钟基于linux-kali的arp攻击防御示例&#xff08;保姆级图文&#xff09;【网络工程】_arspoof工具 linux无网络环境安装-CSDN博客 arp欺骗原理_arp欺骗攻击原理-CSDN博客 ARP欺骗_arp欺骗复现-CSDN博客 ARP欺骗原理及实现-CSDN博客 kali…

SEO之网站结构优化(十四-内部链接及权重分配1)

初创企业搭建网站的朋友看1号文章&#xff1b;想学习云计算&#xff0c;怎么入门看2号文章谢谢支持&#xff1a; 1、我给不会敲代码又想搭建网站的人建议 2、“新手上云”能够为你开启探索云世界的第一步 博客&#xff1a;阿幸SEO~探索搜索排名之道 前面提到网站结构优化要解…

SQLite的安装和使用

一、官网链接下载安装包 点击跳转 步骤&#xff1a;点击安装这个红框的dll以及红框下面的tools &#xff08;如果有navicat可以免上面这个安装步骤&#xff0c;安装上面这个是为了能在命令行敲SQL而已&#xff09; 二、SQLite的特点 嵌入的&#xff08;无服务器的&#x…

娱乐社交、游戏等行业共探合规前提下,实现产品可持续的增长与营收 | 网易数智x华为云泛娱乐行业沙龙-杭州站邀您前来!

随着5G、AI、区块链等前沿技术的深度融合应用&#xff0c;泛娱乐行业正经历深刻变革的同时&#xff0c;也面临着一系列挑战与问题&#xff0c;面对社交产品监管的加强、海外市场的双重机遇与风险以及增速放缓的游戏行业...... 探求新增长点与新思路成为当下泛娱乐行业从业者的关…

NFC射频--天线设计

一、NFC天线电路结构 如图3.3所示&#xff0c;13.56Mhz读卡器电路又两部分组成&#xff0c;其中引脚RX到引脚AGND部分区域是信号接收电路&#xff1b;引脚TX1到引脚TX2之间区域是信号发射电路。 信号接收电路 由四个元器件构成&#xff0c;图中电容C4用来稳定读卡芯片内部提供…

网络应用层之(2)DNS协议

网络应用层之(2)DNS协议 Author: Once Day Date: 2024年8月12日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CSDN…

SpringBoot调用通义千问

提示&#xff1a;今日花了2个小时搞定了一个简易版的AI对话功能 文章目录 目录 文章目录 SpringBoot代码 引入库 controller 返回对象类 工具类 前端代码 ​编辑 效果展示 后端返回 SpringBoot代码 当然我只做了一个简易版的AI对话&#xff0c;你可以在我的基础之上进行…

leetcode 1957 删除字符使字符串变好

leetcode 1957 删除字符使字符串变好 正文题目说明Python 常用技巧解题思路方法1方法2方法3 正文 题目说明 Python 常用技巧 Python 中在字符串中做删除元素的操作是很困难的&#xff0c;通常我们都会创建一个新的字符串或者列表&#xff0c;然后进行循环&#xff0c;将不需要…

简单分享下Python文件操作

1. 上传文件到服务器 场景描述: 使用 requests 库上传文件到服务器。 import requestsdef test_upload_file(): url "https://api.example.com/upload" file_path "path/to/file.txt" with open(file_path, "rb") as file: …

解决串口打印乱码:确保晶振频率设置正确

项目场景&#xff1a; GD32单片机通过USART1串口&#xff0c;以115200波特率每1秒发送自定义字符串&#xff08;“my_test”&#xff09;&#xff0c;PC机使用串口助手接收数据。 问题描述 使用串口助手软件&#xff08;sscom&#xff09;接收GD3232单片机通过UART发送的数据…

【STM32】PWR电源控制(低功耗模式)

本篇博客重点在于标准库函数的理解与使用&#xff0c;搭建一个框架便于快速开发 目录 PWR简介 修改主频 低功耗模式 睡眠模式 停止模式 待机模式 PWR简介 PWR&#xff08;Power Control&#xff09;电源控制 &#xff0c;负责管理STM32内部的电源供电部分&#xff0c;可…

基于麻雀SSA优化BP神经网络多输入多输出的数据回归预测Matlab程序SSA-BP 含预测新数据程序

基于麻雀SSA优化BP神经网络多输入多输出的数据回归预测Matlab程序SSA-BP 含预测新数据程序 文章目录 一、基本原理1. SSA&#xff08;麻雀搜索算法&#xff09;2. BP&#xff08;反向传播神经网络&#xff09;3. SSA-BP回归预测的整合 二、实验结果三、核心代码四、代码获取五、…

使用idea快速创建springbootWeb项目(springboot+springWeb+mybatis-Plus)

idea快速创建springbootWeb项目 详细步骤如下 1&#xff09;创建项目 2&#xff09;选择springboot版本 3&#xff09;添加web依赖 4&#xff09;添加Thymeleaf 5&#xff09;添加lombok依赖 然后点击create进入下一步 双击pom.xml文件 6&#xff09;添加mybatis-plus依赖 …

Ubuntu搭建FTP服务器

目录 1.ftp简介 2.vsftpd 2.1.介绍 2.2.安装与卸载 2.3.综合案例 - 本地用户模式 2.4.1.创建FTP用户 2.4.2.配置vsftpd 2.4.3.配置防火墙 1.ftp简介 一般来讲&#xff0c;人们将计算机联网的首要目的就是获取资料&#xff0c;而文件传输是一种非常重要的获取资料的方…

盘点 8 月份 火火火 的开源项目

01 Rnote&#xff1a;释放创意&#xff0c;手绘与笔记的开源之选 Rnote是一个基于矢量的开源绘图应用&#xff0c;专为手绘、手写笔记以及文档和图片注释设计。 它适用于学生、教师以及拥有绘图板的用户&#xff0c;提供了 PDF 和图片的导入导出功能&#xff0c;无限画布以及适…

单词拆分[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你一个字符串s和一个字符串列表wordDict作为字典。如果可以利用字典中出现的一个或多个单词拼接出s则返回true。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 示例 1&#x…

【第54课】XSS跨站Cookie盗取表单劫持网络钓鱼溯源分析项目平台框架

免责声明 本文发布的工具和脚本&#xff0c;仅用作测试和学习研究&#xff0c;禁止用于商业用途&#xff0c;不能保证其合法性&#xff0c;准确性&#xff0c;完整性和有效性&#xff0c;请根据情况自行判断。 如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利&#xff0…

004、架构_计算节点

架构总览 重要线程 管理线程:主要负责元数据相关的管理,涉及启动、DDL、切换;执行线程:是CN最重要的核心线程组,涉及解析、执行计划、分发、聚合;路由线程:主要负责向DN节点分发语句,涉及读写分离、子语句;GTM代理线程:主要负责与GTM交互、涉及申请、活跃GTID查询、释…

YASKAWA机器人维修操作命令攻略-移动命令运用案例

移动命令 1. MOVJ 命令运用案例&#xff1a; MOVJ VJ50.00 PL2 NWAIT UNTIL IN(1)ON 含义&#xff1a;在这个点以关节坐标&#xff0c;按 50.00%的再现速度&#xff0c;定位精度为 2&#xff0c;同时执行下一条非移动 指令&#xff0c;判断输入信号 1 为 on 后&#xff0c;执行…