共享是最有效的学习办法。
博客: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) {
        //...dosomething
        return 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 {
    @Override
    public boolean support(Object adapter) {
        return adapter instanceof LoginForQQAdapter;
    }
    @Override
    public ApiResponse login(String id, Object adapter) {
        return null;
    }
}
public class LoginForWeChatAdapter implements LoginAdapter {
    @Override
    public boolean support(Object adapter) {
        return adapter instanceof LoginForWeChatAdapter;
    }
    @Override
    public 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{
    @Override
    public ApiResponse loginForQQ(String openId) {
        return doLogin(openId,LoginForQQAdapter.class);
    }
    @Override
    public ApiResponse loginForWechat(String openId) {
        return doLogin(openId,LoginForWeChatAdapter.class);
    }
    @Override
    public 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的对接的场景也有适用。例如,在电商事务场景中会涉及到各种对接,说到买卖就会牵扯到供货商的对接,第三方分销渠道客户的对接,其间必然涉及模型不一致需求适配转化的场景,比如供货商商品信息和规范商城商品信息等等。当然老猫在此也只是做了一下简略罗列。期望咱们在后面的工作中能够参阅用到。