IAM之登录设计

1,824 阅读10分钟

1.概述

登录功能包括两个主要的组成部分:认证登录

认证 的功能依赖于具体的方案选择,登录 的功能则依赖于具体的业务需求。

本文的主要目标,是识别出影响登录功能的设计因素。

设计目标

总的来说,登录的‘意义’可以用下图表示:

登录的使用场景

  1. 登录的目的是为了在后续的业务请求中,能够识别用户身份;
  2. 认证的方式本身是多种多样的;
  3. 用户的‘身份’在不同的系统中,表现形式是多种多样的;

所以登录设计应该达到以下几个目标:

  1. 能够支持多种认证手段;
  2. 能够通过很容易的扩展,支持新的认证手段;
  3. 对业务处理透明

2.相关因素

2.1.通用因素

开始‘认证登录设计’之前,肯定要和用户进行沟通,从而确定‘登录’这件事的影响范围,有多少事情要做。一般的,我们会问客户几个问题:

- 要不要用手机号登录啊?
- 用微信小程序吗?
- 新用户登录后怎么办啊?
- 需要客服代理用户身份吗?
- 有几种用户啊?比方说 客户/销售/财务 各自需要各自的登录入口
- 扫码登录了解一下?
...

归纳一下,是以下4个方面的信息,称之为"登录上下文":

  1. 登录渠道
  2. 登录方式
  3. 登录信息
  4. 登录目标

2.1.1.登录渠道

通过哪个客户端,哪个界面登录的。

例如,电商网站,卖家登录后台,和买家登录账号,都是通过PC端网页登录的,在实现中, 一个使用类似 https://...webservice/merchantLogin/ 这样的接口登录, 另外一个通过 https://...webService/userLogin/ 这样的接口来登录。

在移动端,买家还可以通过一个APP登录,通过调用 https://...mobileService/login/ 来登录

我们说,它们的登录渠道不同。

不同的登录渠道,通过以下信息来识别:

  1. 客户端。(从哪里发起的请求,例如通过header中的字段来标识 web 和 APP的不同场景)
  2. service bean。(是哪个服务接收的登录请求。可以使用controller的类,bean name等来标识,例如上面的webService和mobileService)
  3. service name。(使用哪个具体的接口处理登录请求。一般可以映射到方法名,例如上面的 merchantLogin和userLogin)

2.1.2.登录方式

使用什么方法登录的

例如,使用微信登录;使用密码登录;使用扫脸登录...

2.1.3.登录信息

使用不同的登录方式,需要提供相应的各种信息

例如,使用微信登录,需要前台提供code;使用密码登录,需要提供ID+密码;使用扫脸登录,需要提供匹配因子...

2.1.4.登录目标

登录完成后,我以一个什么样的身份进行后续的操作。

例如,前面电商系统,不同的登录方式成功后,应该能够确定‘我’是一个买家还是一个卖家。

2.2.业务因素

登录的结果,常见的有这么几种

  • 认证通过,身份确定
  • 认证通过,但是没有身份
  • 认证失败

认证通过,身份也确定” 这种情况通常会问客户的问题是“登录成功以后,是去’我的‘页面啊,还是去’首页‘啊? " 不同的业务系统有不同的要求,例如一个任务管理系统,登录以后一般会要求去到‘我的’页面,查看用户的dashboard,处理‘待处理任务’。而一个交易系统,登录之后一般会希望去到‘交易市场’类的页面。设计时,这里要允许业务模块自由的处理登录成功后的页面流向。

认证通过,但是没有身份” 这种情况,一般需要引导注册或者自动创建用户。
例如,使用微信登录,用户授权后,通过后台验证此用户有效,但是用它包含的open-id却不能找到对应的买家(或者其他什么业务所需的角色),这个时候怎么办?问用户!这是个业务层面的问题。

有3种可能:

  • 认为登录失败。 例如某内部工作流系统,微信登录后发现没有对应的‘员工’,那么就告诉他登录失败,需要管理员先为其创建员工,指定部门和权限,然后绑定微信以后,才能使用微信快速登录。
  • 引导注册。 例如某众包系统,微信登录后需要补充一些必要的信息,才能继续进行后续操作,此时需要将页面流导向注册页面。
  • 自动创建。例如某社区类应用,微信登录后就可以(金银铜铁锡之)‘锡牌’级别用户身份进行浏览。

这3种可能需要根据业务来确定,甚至还有其他可能,所以我们的设计应该识别出这是一个“业务”选择,在设计中,让业务能够介入处理。设计目标是“让业务介入”而不是“如何引导注册”这样的具体实现。

认证失败” 这种情况指认证本身失败。例如,密码输错,令牌过期,扫码失败等等。这时一般的处理方式时告知客户登录失败,然后由业务模块决定下一步是继续重试啊,还是换个登录方式啊--总之由业务决定。

从上面的描述可以看到,业务模块要能够介入:

  • 登录后的处理
  • 登录处理完成后的页面流向

框架部分和业务部分的关系是这个样子:

high level

3.概念

3.1.sec-user (安全用户)

使用不同的登录方式,获得的被认证的身份类型是多种多样的。为了达到“业务透明”的设计目标,需要一个统一的逻辑概念来表达‘用户’。

在我们团队使用的DaaS的实现中,引入了“sec-user”这个概念。它表示“确认过的安全的用户”。

不同方式登录进来的各种身份,都会关联到一个 sec-user, 然后在业务中,就可以统一的使用sec-user来表示用户。

“用户”这个概念,并不局限于使用终端的‘人’。在物联网中,任何需要具备身份信息和系统交互的实体,都是‘用户’。例如一个买家,一个门禁,一辆卡车,一个托盘...

3.2.identification handler(认证器)

为了达到设计目标的“ 支持多种认证手段;可以扩展 ”,引入“identification handler"(以下简称ID-handler)的概念。 它的任务是针对不同的 登录方式 ,完成对 登录信息 的认证功能。

例如”微信小程序认证器”,“短信验证码认证器”,“刷脸认证器”

目前我们团队使用的DaaS支持以下几种ID-handler

  • ID/email/手机号+密码认证

    用户需要提供用户ID,或者手机号,以及登录密码。登录结果不支持“认证通过但是没有身份”这种情况;

  • 手机号+验证码认证

    用户需要先获取验证码,然后用手机号和验证码一起完成认证

  • 微信小程序认证

    使用微信官方的小程序认证规范完成认证,需要客户端获取code

  • 企业微信认证

    使用微信官方的企业微信认证,需要客户端获取企业微信code

  • 公私钥对认证

    客户端使用私钥提供一个签名,服务器使用其对应公钥进行验证。需要客户端首先和服务器协商签名,并且通过其他渠道预先分发秘钥。大部分用于物联网设备

  • 微信扫码认证

    客户端显示一个微信二维码,用户使用手机微信扫码,登录客户端;

  • QR-ID/RFID

    在特定场景下,只需确定用户ID,无需认证。例如物流平台中签到,核单等

  • email+验证码认证

    用户需要先通过邮箱获取验证码,然后用验证码登录

  • 更多认证方式...

3.2.business handler (业务处理器)

为了让业务模块能够介入 登录目标 在不同 登录渠道 的处理,我们以business handler(以下简称biz-handler)这个概念来代指业务处理逻辑相关部分,以之描述登录功能与具体业务的边界和交互。

3.3.小结

登录部分的主要流程如下:

high-level-flow

4. High level Design

4.1.登录部分

从上面的描述中,可以知道,设计的核心概念就是

  1. 选取正确的 ID-handler,进行认证
  2. 保存身份信息到统一的用户模型(sec-user)
  3. 选取正确的biz-handler,处理业务因素

基于我们团队使用的DaaS,一个high-level-design如下图所示:

4.2.鉴权部分

登录的目的是为了 在后续的业务处理中,能够识别用户身份 (见‘概述’),所以这里将基于DaaS实现的鉴权部分也简单描述一下。

  • DaaS中,每个请求都会做鉴权。最终会调用到对应的bean的 checkAccess 方法,所以此图以checkAccess作为入口
  • token key的优先级是:先header,再cookie,最后 session ID
  • 匿名用户也拥有自己的‘身份’,因此也有对应的一套token
  • OOTB的鉴权结果只有两种:accessOK 或者 accessFail。(在DaaS中提供了简单的方法来重载checkAccess,鉴权结果可以根据需要定制)

5.实现上的考虑

此设计并未限定实现方式,它只是明确了职责划分和业务依赖。可以使用普通的代码,一段一段的实现,也可以使用‘响应式编程’的风格来处理;同步事件既可以是直接方法调用,也可以是阻塞式消息响应。

重要的部分在于:

  1. 框架和业务的职责要划分清楚。 大部分系统不易维护的原因,在于职责边界混淆,当发生变更的时候才发现牵一发而动全身;
  2. 边界事件要定义良好。在模块之间,子系统之间,要提供完备的,稳定的事件定义。不要使用任何“潜规则”。通常来说,每种事件都会有一个长期维护的事件代码,当事件细节发生变化时,通过新增事件代码来支持,而不是修改事件定义。‘事件代码’是一个“你懂得”的“千言万语汇成一句话”。DaaS的实现中,就通过ChangeRequest中的bindedEventType来标定每种事件,保证了良好的扩展性。
  3. 注意控制权的合理变化。登录算是一个典型的“基本上一样”,但是又“每次都要改”的功能。识别出它的通用因素和业务因素,就能明白,控制登录的,不是一个单一模块,该交给框架的时候要交给框架,该交给外部服务的时候就交给外部服务,最后的控制权还要交回给业务。通常对复杂的业务过程,例如工作流,大家很容易就处理了控制权的转移,因为在认知上就准备好了。而登录一般表现为很小的一个功能,通常开发人员都是一气呵成,忽略了它在概念上,是多个层次,多个领域合作的。