设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?

这篇具有很好参考价值的文章主要介绍了设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        面向对象的三个环节:面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程(OOP)。只知道OOA、OOD、OOP只能说有一个宏观了解,我们更重要的还是要知道“如何做”,也就是,如何进行面向对象分析、设计与编程。

        本文结合一个真实的开发案例,从基础的需求分析职责划分类的定义交互组装运行讲起,将最基础的面向对象分析、设计、编程的套路给你讲清楚,为后面学习设计原则、设计模式打好基础。

案例介绍和难点剖析

假设,你正在参与开发一个微服务。微服务通过HTTP协议暴露接口给其他系统调用,说直白点就是,其他系统通过URL来调用微服务的接口。有一天,你的leader找到你说,“为了保证接口调用的安全性,我们希望设计实现一个接口调用鉴权功能只有经过认证之后的系统才能调用我们的接口,没有认证过的系统调用我们的接口会被拒绝。我希望由你来负责这个任务的开发,争取尽快上线。”

分析两点:

  • 接口是通过HTTP协议,进行访问。类似访问百度一下。
  • 只有认证之后的系统才能调用我们的接口。

        以上两点是设计一个这个系统的要求。软件编程就像算法题一样,第一次可能得不到最优解,然后通过分析不足,逐步迭代最终形成一个可执行,可落地的方案。

第一轮基础分析

        最简单的解决方案就是,通过用户名加密码来做认证。我们给每个允许访问我们服务的调用方,派发一个应用名(或者叫应用ID、AppID)和一个对应的密码(或者叫秘钥)。这个密钥和ID可以双方提前已经生成的,在两边都有记录。调用者每次访问时候都携带ID和密钥,微服务在接收到请求接口之后,会解析出AppID和密钥跟存储在微服务端的AppID和密码进行比对。如果一致,说明认证成功,则允许接口调用请求;否则,就拒绝接口调用请求。

第二轮分析优化

        不过,这样的验证方式,每次都要明文传输密码。密码很容易被截获。未认证系统可以携带这个加密之后的密码以及对应的AppID,伪装成已认证系统来访问我们的接口。这就是典型的“重放攻击”。

        我们可以借助OAuth的验证思路来解决。调用方将请求接口的URL跟AppID、密码拼接在一起,然后进行加密,生成一个token。调用方在进行接口请求的的时候,将这个token及AppID,随URL一块传递给微服务端。微服务端接收到这些数据之后,根据AppID从数据库中取出对应的密码,并通过同样的token生成算法,生成另外一个token。用这个新生成的token跟调用方传递过来的token对比。如果一致,则允许接口调用请求;否则,就拒绝接口调用请求。

设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?

第三轮分析优化

        不过,这样的设计仍然存在重放攻击的风险,还是不够安全。每个URL拼接上AppID、密码生成的token都是固定的。未认证系统截获URL、token和AppID之后,还是可以通过重放攻击的方式,伪装成认证系统,调用这个URL对应的接口。

        为了解决这个问题,我们可以进一步优化token生成算法,在原来的基础上拼接一个时间戳作为随即变成,这样就可以生成动态的token。

        微服务端在收到这些数据之后,会验证当前时间戳跟传递过来的时间戳,是否在一定的时间窗口内(比如一分钟)。如果超过一分钟,则判定token过期,拒绝接口请求。如果没有超过一分钟,则说明token没有过期,就再通过同样的token生成算法,在服务端生成新的token,与调用方传递过来的token比对,看是否一致。如果一致,则允许接口调用请求;否则,就拒绝接口调用请求。

优化之后的认证流程如下图所示。

设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?

到此基本需求以及满足了,可能随着时间的推移,我们的密码不仅仅存到数据库,可能会在zookeeper、redis等等其他地方。需要选择良好的设计模式进行兼容。

最终确定需求

到此,需求已经足够细化和具体了。现在,我们按照鉴权的流程,对需求再重新描述一下。如果你熟悉UML,也可以用时序图、流程图来描述。不过,用什么描述不是重点,描述清楚才是最重要的。这里我给出的最终需求描述是文字版本和图片版本的。

  • 调用方进行接口请求的时候,将URL、AppID、密码、时间戳拼接在一起,通过加密算法生成token,并且将token、AppID、时间戳拼接在URL中,一并发送到微服务端。
  • 微服务端在接收到调用方的接口请求之后,从请求中拆解出token、AppID、时间戳。
  • 微服务端首先检查传递过来的时间戳跟当前时间,是否在token失效时间窗口内。如果已经超过失效时间,那就算接口调用鉴权失败,拒绝接口调用请求。
  • 如果token验证没有过期失效,微服务端再从自己的存储中,取出AppID对应的密码,通过同样的token生成算法,生成另外一个token,与调用方传递过来的token进行匹配;如果一致,则鉴权成功,允许接口调用,否则就拒绝接口调用。

设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?

这就是我们需求分析的整个思考过程,从最粗糙、最模糊的需求开始,通过“提出问题-解决问题”的方式,循序渐进地进行优化,最后得到一个足够清晰、可落地的需求描述。

总结,遇到问题或者需求,尽可能的慢慢拆解分析。着手慢慢开始一步步做,逐步迭代后就可以找到一个较优的方案了。


既然需求已经明确,如何进行面向对象设计呢?

面向对象设计?

我们知道,面向对象分析的产出是详细的需求描述,那面向对象设计的产出就是类。在面向对象设计环节,我们将需求描述转化为具体的类的设计。我们把这一设计环节拆解细化一下,主要包含以下几个部分:

  • 划分职责进而识别出有哪些类;
  • 定义类及其属性和方法;
  • 定义类与类之间的交互关系;
  • 将类组装起来并提供执行入口。

划分职责进而识别出有哪些类

根据需求描述,把其中涉及的功能点,一个一个罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,是否应该归为同一个类。我们来看一下,针对鉴权这个例子,具体该如何来做。

前文中,我们已经给出了详细的需求描述,为了方便你查看,我把它重新贴在了下面。

  • 调用方进行接口请求的时候,将URL、AppID、密码、时间戳拼接在一起,通过加密算法生成token,并且将token、AppID、时间戳拼接在URL中,一并发送到微服务端。
  • 微服务端在接收到调用方的接口请求之后,从请求中拆解出token、AppID、时间戳。
  • 微服务端首先检查传递过来的时间戳跟当前时间,是否在token失效时间窗口内。如果已经超过失效时间,那就算接口调用鉴权失败,拒绝接口调用请求。
  • 如果token验证没有过期失效,微服务端再从自己的存储中,取出AppID对应的密码,通过同样的token生成算法,生成另外一个token,与调用方传递过来的token进行匹配。如果一致,则鉴权成功,允许接口调用;否则就拒绝接口调用。

首先,我们要做的是逐句阅读上面的需求描述,拆解成小的功能点,一条一条罗列下来。注意,拆解出来的每个功能点要尽可能的小。每个功能点只负责做一件很小的事情(专业叫法是“单一职责”,后面章节中我们会讲到)。下面是我逐句拆解上述需求描述之后,得到的功能点列表:

  1. 把URL、AppID、密码、时间戳拼接为一个字符串;
  2. 对字符串通过加密算法加密生成token;
  3. 将token、AppID、时间戳拼接到URL中,形成新的URL;
  4. 解析URL,得到token、AppID、时间戳等信息;
  5. 从存储中取出AppID和对应的密码;
  6. 根据时间戳判断token是否过期失效;
  7. 验证两个token是否匹配;

最后根据供能描述设计相应的类。

        我们发现,1、2、6、7都是跟token有关,负责token的生成、验证;3、4都是在处理URL,负责URL的拼接、解析;5是操作AppID和密码,负责从存储中读取AppID和密码。所以,我们可以粗略地得到三个核心的类:AuthToken、Url、CredentialStorage。AuthToken负责实现1、2、6、7这四个操作;Url负责3、4两个操作;CredentialStorage负责5这个操作。

当然初步是进行这样设计,如果发现有不合理的地方可以进行调整。

 定义类及其属性和方法

AuthToken类相关的功能点有四个:

  • 方法1:把URL、AppID、密码、时间戳拼接为一个字符串;
  • 方法2:对字符串通过加密算法加密生成token;
  • 方法3:根据时间戳判断token是否过期失效;
  • 方法4:验证两个token是否匹配。
  • 属性1:String token
  • 属性2:时间戳
  • 属性3:过期时间间隔。

设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?

从上面的类图中,我们可以发现这样三个小细节。

  • 第一个细节:并不是所有出现的名词都被定义为类的属性,比如URL、AppID、密码、时间戳这几个名词,我们把它作为了方法的参数。
  • 第二个细节:我们还需要挖掘一些没有出现在功能点描述中属性,比如createTime,expireTimeInterval,它们用在isExpired()函数中,用来判定token是否过期。
  • 第三个细节:我们还给AuthToken类添加了一个功能点描述中没有提到的方法getToken()。

Url类相关的功能点有两个:

  • 将token、AppID、时间戳拼接到URL中,形成新的URL;
  • 解析URL,得到token、AppID、时间戳等信息。

虽然需求描述中,我们都是以URL来代指接口请求,但是,接口请求并不一定是以URL的形式来表达,还有可能是Dubbo、RPC等其他形式。为了让这个类更加通用,命名更加贴切,我们接下来把它命名为ApiRequest。下面是我根据功能点描述设计的ApiRequest类。

设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?

CredentialStorage类相关的功能点有一个:

  • 从存储中取出AppID和对应的密码。

设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?

CredentialStorage类非常简单,类图如下所示。为了做到抽象封装具体的存储方式(上文提到可能是MySQL,Redis和Zookeeper等等),我们将CredentialStorage设计成了接口,基于接口而非具体的实现编程。

设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?

 定义类与类之间的交互关系

类与类之间都有哪些交互关系呢?UML统一建模语言中定义了六种类之间的关系。它们分别是:泛化、实现、关联、聚合、组合、依赖。关系比较多,而且有些还比较相近,比如聚合和组合,接下来我就逐一讲解一下。

泛化(Generalization)可以简单理解为继承关系。具体到Java代码就是下面这样:

public class A { ... }
public class B extends A { ... }

实现(Realization)一般是指接口和实现类之间的关系。具体到Java代码就是下面这样:

public interface A {...}
public class B implements A { ... }

组合(Composition)也是一种包含关系。A类对象包含B类对象,B类对象的生命周期依赖A类对象的生命周期,B类对象不可单独存在,比如鸟与翅膀之间的关系。具体到Java代码就是下面这样:

public class A {
  private B b;
  public A(B b) {
    this.b = b;
  }
}
或者
public class A {
  private B b;
  public A() {
    this.b = new B();
  }
}

依赖(Dependency)是一种比关联关系更加弱的关系,包含关联关系。不管是B类对象是A类对象的成员变量,还是A类的方法使用B类对象作为参数或者返回值、局部变量,只要B类对象和A类对象有任何使用关系,我们都称它们有依赖关系。具体到Java代码就是下面这样

public class A {
  private B b;
  public A(B b) {
    this.b = b;
  }
}
或者
public class A {
  private B b;
  public A() {
    this.b = new B();
  }
}
或者
public class A {
  public void func(B b) { ... }
}

如何进行面向对象编程?

面向对象设计完成之后,我们已经定义清晰了类、属性、方法、类之间的交互,并且将所有的类组装起来,提供了统一的执行入口。接下来,面向对象编程的工作,就是将这些设计思路翻译成代码实现。有了前面的类图,这部分工作相对来说就比较简单了。


public interface ApiAuthenticator {
    void auth(String url);
    void auth(ApiRequest apiRequest);
}


/**
 * 提供接口供外界调用
 */
public class DefaultApiAuthenticatorImpl implements ApiAuthenticator{

    private CredentialStorage credentialStorage;

    public DefaultApiAuthenticatorImpl(CredentialStorage credentialStorage) {
        this.credentialStorage = credentialStorage;
    }

    public DefaultApiAuthenticatorImpl() {

    }

    @Override
    public void auth(String url) {
        ApiRequest apiRequest = ApiRequest.buildFromUrl(url);
        auth(apiRequest);
    }

    @Override
    public void auth(ApiRequest apiRequest) {
        String appId = apiRequest.getAppId();
        String token = apiRequest.getToken();
        long timestamp = apiRequest.getTimestamp();
        String originalUrl = apiRequest.getBaseUrl();

        AuthToken clientAuthToken = new AuthToken(token, timestamp);
        if (clientAuthToken.isExpired()) {
            throw new RuntimeException("Token is expired.");
        }

        String password = credentialStorage.getPasswordByAppId(appId);
        AuthToken serverAuthToken = AuthToken.generate(originalUrl, appId, password, timestamp);
        if (!serverAuthToken.match(clientAuthToken)) {
            throw new RuntimeException("Token verfication failed.");
        }
    }
}


/**
 * 仅仅定义接口,没有写具体的sql实现
 */
public interface CredentialStorage {
    String getPasswordByAppId(String AppId);
}


public class ApiRequest {
    private String baseUrl;
    private String token;
    private String appId;
    private long timeStamp;

    public ApiRequest(String baseUrl, String token, String appId, long timeStamp) {
        this.baseUrl = baseUrl;
        this.token = token;
        this.appId = appId;
        this.timeStamp = timeStamp;
    }

    // TODO
    public static ApiRequest buildFromUrl(String url) {
        String[] split = url.split("&");
        String baseUrl = split[0];
        String appid = split[1].split("=")[1];
        String pwd = split[2].split("=")[1];
        String timestamp = split[3].split("=")[1];
        // 生成token
        String token = createToken(baseUrl, appid, pwd, Long.parseLong(timestamp));
        return new ApiRequest(baseUrl,token,appid,Long.parseLong(timestamp));

    }

    /**
     * token 生成算法, 这里进行了简写;可以使用hash算法,或者自定义加密算法。
     *
     * @param baseUrl
     * @param appid
     * @param pwd
     * @param timestamp
     * @return
     */
    public static String createToken(String baseUrl, String appid, String pwd, long timestamp) {
        return "123";
    }

    public String getBaseUrl() {
        return baseUrl;
    }

    public String getToken() {
        return token;
    }

    public String getAppId() {
        return appId;
    }

    public long getTimestamp() {
        return timeStamp;
    }
}


import java.util.Date;

public class AuthToken {
    // 国企间隔
    private static final long DEFAULT_EXPIRED_TIME_INTERVAL = 1 * 60 * 1000;
    private String token;
    private long createTime;
    private long expiredTimeInterval = DEFAULT_EXPIRED_TIME_INTERVAL;


    public AuthToken(String token, long createTime) {
        this.token = token;
        this.createTime = createTime;
    }

    public AuthToken(String token, long createTime, long expiredTimeInterval) {
        this.token = token;
        this.createTime = createTime;
        this.expiredTimeInterval = expiredTimeInterval;
    }

    /**
     * 生成服务端的token
     *
     * @param baseUrl
     * @param appId
     * @param password
     * @param createTime
     * @return
     */
    public static AuthToken generate(String baseUrl, String appId, String password, long createTime) {
        String token = ApiRequest.createToken(baseUrl, appId, password, createTime);
        return new AuthToken(token, new Date().getTime());
    }

    public String getToken() {
        return token;
    }

    public boolean isExpired() {
        long curTime = new Date().getTime();
        return (curTime - (createTime + expiredTimeInterval)) >= 0;
    }

    public boolean match(AuthToken authToken) {
        String token = authToken.getToken();
        // 1. 根据AppID,url
        return this.getToken().equals(token);
    }
}




辩证思考与灵活应用

        在之前的讲解中,面向对象分析、设计、实现,每个环节的界限划分都比较清楚。而且,设计和实现基本上是按照功能点的描述,逐句照着翻译过来的。这样做的好处是先做什么、后做什么,非常清晰、明确,有章可循,即便是没有太多设计经验的初级工程师,都可以按部就班地参照着这个流程来做分析、设计和实现

        不过,在平时的工作中,大部分程序员往往都是在脑子里或者草纸上完成面向对象分析和设计,然后就开始写代码了,边写边思考边重构,并不会严格地按照刚刚的流程来执行(不推荐)。而且,说实话,即便我们在写代码之前,花很多时间做分析和设计,绘制出完美的类图、UML图,也不可能把每个细节、交互都想得很清楚。在落实到代码的时候,我们还是要反复迭代、重构、打破重写。

        毕竟,整个软件开发本来就是一个迭代、修修补补、遇到问题解决问题的过程,是一个不断重构的过程。我们没法严格地按照顺序执行各个步骤。这就类似你去学驾照,驾校教的都是比较正规的流程,先做什么,后做什么,你只要照着做就能顺利倒车入库,但实际上,等你开熟练了,倒车入库很多时候靠的都是经验和感觉。

重点回顾

今天的内容到此就讲完了。我们来一块总结回顾一下,你需要掌握的重点内容。

面向对象分析的产出是详细的需求描述。面向对象设计的产出是类。在面向对象设计这一环节中,我们将需求描述转化为具体的类的设计。这个环节的工作可以拆分为下面四个部分。

1.划分职责进而识别出有哪些类

        根据需求描述,我们把其中涉及的功能点,一个一个罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,可否归为同一个类。

2.定义类及其属性和方法

        我们识别出需求描述中的动词,作为候选的方法,再进一步过滤筛选出真正的方法,把功能点中涉及的名词,作为候选属性,然后同样再进行过滤筛选。

3.定义类与类之间的交互关系

        UML统一建模语言中定义了六种类之间的关系。它们分别是:泛化、实现、关联、聚合、组合、依赖。我们从更加贴近编程的角度,对类与类之间的关系做了调整,保留四个关系:泛化、实现、组合、依赖

4.将类组装起来并提供执行入口

        我们要将所有的类组装在一起,提供一个执行入口。这个入口可能是一个main()函数,也可能是一组给外部用的API接口。通过这个入口,我们能触发整个代码跑起来。文章来源地址https://www.toymoban.com/news/detail-484981.html

到了这里,关于设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 钉钉和金蝶云星空接口打通对接实战

    钉钉是阿里巴巴集团打造的企业级智能移动办公平台,是数字经济时代的企业组织协同办公和应用开发平台。钉钉将IM即时沟通、钉钉文档、钉闪会、钉盘、Teambition、OA审批、智能人事、钉工牌、工作台深度整合,打造简单、高效、安全、智能的数字化未来工作方式,助力企

    2024年02月15日
    浏览(29)
  • 设计模式之美——单元测试和代码可测性

    最可落地执行、最有效的保证重构不出错的手段应该就是单元测试(Unit Testing)。 什么是单元测试? 单元测试由研发工程师自己来编写,用来测试自己写的代码的正确性。我们常常将它跟集成测试放到一块来对比。单元测试相对于集成测试(Integration Testing)来说,测试的粒

    2024年02月12日
    浏览(31)
  • 旺店通·企业版和金蝶云星空接口打通对接实战

    慧策(原旺店通)是一家技术驱动型智能零售服务商,基于云计算PaaS、SaaS模式,以一体化智能零售解决方案,帮助零售企业数字化智能化升级,实现企业规模化发展。 金蝶K/3Cloud在总结百万家客户管理最佳实践的基础上,提供了标准的管理模式;通过标准的业务架构:多会

    2024年01月21日
    浏览(35)
  • api接口如何有效对接

    在平时工作中,经常会遇到的一种场景是:A公司要对接B公司的API方法,这时,A公司就要阅读B公司的接口文档,从接口文档中找到自己需要对接的API,并根据接口文档的要求,完成编码工作,最终完成对接工作。 本篇是站在A公司的角度,去对接B公司API接口的实战。 一般情

    2024年02月16日
    浏览(28)
  • 对接支付通道如何收费?支付接口收费标准

    支付接口收费标准是怎样的 反映到平台方来说,就是它的盈利模式,是维持企业生存,到发展壮大的根本保障。目前第三方支付平台费用有:手续费、广告费、服务费、沉淀资金的利息收入四种。 1、手续费 手续费是第三方支付平台费用的最传统的盈利模式之一。即第三方支

    2024年02月16日
    浏览(34)
  • 如何在PHP中对接openAI接口,PHP创建AI会话思路以及代码讲解

    https://platform.openai.com/account/api-keys 在这个地址进行注册,但是需要翻墙,可自己查找国内的试用地址。就不多赘述… php端代码: 思路:前端通过一个图标入口,点击后 发送请求,创建一个会话(也就是聊天室),并且把创建的这个会话 入库。并且后端返回一个入库生成的

    2024年01月18日
    浏览(38)
  • 设计模式学习笔记 - 开源实战五(上):Mybatis如何权衡易用性、性能和灵活性?

    前面几篇文章讲解了 Spring 框架,剖析了背后蕴含的一些通用设计思想、设计原则,以及用到的十几种设计模式。从本章开始,我们再剖析一个 Java 项目中经常用到的开发框架:Mybatis。本次也分为三篇文章进行讲解: 第一篇文章,分析 Mybatis 如何权衡代码的易用性、性能和灵

    2024年04月28日
    浏览(52)
  • 电商API接口对接电子商务平台实现电商供应链一键对接

    按照商家的技术能力,可以为商家提供多种对接方案: 通过平台提供的API接口对ERP系统进行开发,实现和平台的对接,适用于有专业技术开发能力的商家; 平台统一开发服务系统,由平台人员实施商家ERP与平台的对接,适用于无专业的技术对接能力,有ERP系统且大批量信息

    2024年02月06日
    浏览(44)
  • 京东接口对接流程(以下举例物流接口):

    1.注册成为京东宙斯开发者 2.创建应用 控制中心 – 已经应用类型选择京东物流 – 二级应用类型选择青龙应用 注:选择的接口在和京东对接联调的时候可以咨询京东开发者 3.等待审核通过 创建好了应用后等待审核 4.审核通过,设置回调URL并且获取Appkey、App Secret、access_toke

    2024年02月12日
    浏览(51)
  • Unity 与后端通信,对接口

    注意:每个后端做的接口都会有些不同, 根据实际更改。本文为案例。 本接口程序被设计用来支撑移动客户端部分功能,数据交换格式为JSON,接口若支持POST方式访问,则一般也支持GET方式访问(特殊情况除外,如:文件上传),最佳访问方式请参照各API定义中的建议。 1、

    2024年01月19日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包