百度百科是这样定义开放平台:
开放平台(Open Platform) 在软件行业和中,开放平台是指软件系统通过公开其(API)或(function)来使外部的程序可以增加该软件系统的功能或使用该系统的资源,而不需要更改该软件系统的。
在互联网时代,把网站的服务封装成一系列计算机易识别的数据接口开放出去,供第三方开发者使用,这种行为就叫做Open API,提供开放的平台本身就被称为开放平台。通过开放平台,网站不仅能提供对Web的简单访问,还可以进行复杂的数据交互,将它们的Web网站转换为与操作系统等价的开发平台。第三方开发者可以基于这些已经存在的、公开的Web网站而开发丰富多彩的应用。其次,开放平台包含多种含义,这些不一一赘述,笔者主要是围绕笔者在第一次参与开放平台开发到第一个上线版本的过程,其他相关的理论知识请自行脑补。
笔者接到的需求是:使用oauth2.0协议完成开放平台的搭建,形如微信。
那么什么是oauth2.0协议呢?
1、OAuth(Open Authorization,开放授权)是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息,并且这是安全的。
2、OAuth2.0是OAuth的最新版本
3、https://oauth.net/2/
两眼一抹黑,在公司内部技术分享会有对其做过介绍,但是当时却没有投入相应时间去研究与琢磨,导致浪费多数时间在学习oauth2.0协议,实属不该。
要说目前网络上的博客作者能把oauth2.0讲得最人性化,最通俗易懂,当属阮一峰老师。阮老师的博客中将oauth2.0协议讲的非常通俗易懂,关键是讲得也很透彻,所以所有初学者都从阮老师的博客中入门。
oauth2.0协议中多个授权模式:
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
四个模式各有优缺点,都有自己适合的场景,在此不一一赘述,但是笔者最终还是选择采用授权码模式来做,因为它功能最完整、流程最严密,并且笔者之前简单对接过微信的开发平台,授权流程多采用该模式,所以想来它一定会更合适(后边即便采用其他的模式也可以切换自如)。
笔者接到的第二个需求是:寻找合适的授权框架。
因为笔者所在公司很早就已经开始研究微服务,并且技术上也已经落地了一整套微服务架构,目前处于持续拆分服务阶段,所以毫无疑问,如果在框架上做选择,为了与spring cloud微服务架构完美契合,最佳理想的选择恐怕是spring security这个框架。从官方文档介绍来看,它确实与spring cloud完美结合,而相比较其他框架,比如apache 的shiro框架其实也可以接入,并且它的接入成本也不会很高,但是从整个技术生态圈来说,不合适。
然而接入spring security的成本恐怕不低,因为spring security本身非常重,因为它本身就是一套非常完善的安全框架,除了授权之外,还有诸如服务于权限控制等功能模块,而笔者手头上的项目是自己实现的授权框架,并不需要权限控制等功能模块,如果投入时间将原先的权限框架进行改造,完全采用spring security来做,成本无疑是公司不能接受的。
思来想去,毫无头绪,没有一个合适的框架可以完美解决眼下遇上的问题,该如何是好【其实投入了很多时间在研究spring security框架,但是从架构上并不符合我们的当前的技术生态(也许没有找到合适的方案),主要问题在于我们的api-gateway的实现迥异】无奈之下,硬着头皮去请教我的顶头上司,寻求一个较好的解决方案。
上司的原话如下:
我们目前的项目之所以不好采用spring security的原因,无非是我们最开始就没有使用它来解决软件的安全问题,所以接入成本很大,既然如此,那就自己实现一套ouath2.0的协议,不难处理,第一版本先把授权码模式实现出来可以投入使用,后续版本迭代再把其他授权模式实现起来即可。
纠结了一会最后还是正式投入时间来研究开发oauth2.0协议,本身对于协议理解只能算入门,幸亏有阮一峰老师的博客。
先理解相关的名词定义:
Resource Owner:资源所有者,即用户
Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
Client:第三方应用程序
Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器
oauth2.0的授权协议流程大致如下:
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
授权码模式认证流程如下:
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
使用通俗的说法可以理解为:用户需要访问系统资源,此时应当检查用户是否有可用(没有或者过期)令牌(access token),如果没有可用令牌,则向资源服务发起授权服务(从笔者的项目来说,既是外部应用请求认证服务器发起授权请求)。这个时候需要认证用户是否授信(即要求用户登录系统,比如通过app打开登录),登录之后跳转至是否授权页面,该页面需要展示的内容有:授权用户协议,以及用户授权的资源(比如用户授权获取用户昵称,手机号等资源)等相关信息。用户确认授权之后,认证服务器发送授权码(code),此时客户端的服务端拿到授权码向认证服务器申请令牌。认证服务器发放令牌,客户端服务器获取令牌后向资源服务器申请开放资源,资源服务器校验令牌合法性后决定是否发放资源。
笔者在实现第一个版本的简陋开放平台遇上的几个问题整理如下:
A、授权相关接口。
1、发起授权接口:authorize
2、确认授权接口:confirm_access
3、换取token/刷新token接口:access_token
4、校验token:check_token
接口的实现不会复杂,spring security也就通过这种方式实现,内置的几个授权相关接口,相关的代码设计可以优先参考spring security简单实现。spring security通过filter做了很多事,但是只需要提前出授权相关的模块。
B、授权资源
用户进入授权页面需要确定需要授予那些资源给到客户端来请求,这个肯定会跟随不同的业务做不同的设计了。不过大致上是采用一种设计:接口即为资源,设计好资源组。
资源是授权双方都需要非常谨慎的一个问题,用户谨慎授权,资源服务器谨慎开放资源,保证整个过程的合法以及安全。其次,将资源分配安排好,对后期的维护以及扩展都将非常友好。比如资源是对特定的接入方开放特定的数据权限,或者统一开放可开放的数据权限等等。
C、应用接口设计
主要考虑作为第三方接入,如何让他们快速接入我们的应用,为我们带来价值。如果一套设计非常 不合理的接口将会造成对方接入混乱,成本高,难以维护等各种问题。一套好的设计方案,双方都非常爽,接入方可以快速接入,并且极易维护,岂不快哉?目前从市面上了解得开放平台api设计大多为以下两类:
1、裸露的api接口:比如微信的公众号开放,翻开微信公众号的开放平台文档可以看到他们罗列的一堆的接口列表。每个接口都提供了非常的demo,调用以及调试都非常简单,所以接入成本不高。
2、接口统一:比如淘宝的开放平台,对外是统一的api地址,通过参数method作为api名称。淘宝的开放提供了非常友好的SDK集成,所以集成快速,并且成本会更低。
两边的接口设计各有优缺点。不好说那种更优。但是就目前个人喜好来说,笔者倾向喜欢淘宝这种api设计风格,调用目标非常清晰,作为接入方,只需要维护一个method列表,而不是一个接口列表,总觉得接口列表维护会更费劲一些,而且如果提供相应的SDK可以让接入更加方便快捷。
D、token生命周期管理
最合适的应该是通过redis的定时过期,不过这里涉及了几种情况来讨论(站在对方接入的角度来讨论)
1、token永不过期,这种设计其实不是很好,即用户授权一次便可永久使用。网关中心最好对其做白名单控制。
2、token过期时间非常常,1个月至一年,这种token其实不太适合将其放在redis上,因为这个token存在时间太长了,维护成本太高了,何况接入方的使用率不见得很高。
3、token过期时间非常短,一般为几个小时至几天,该token完全可以交给redis来管理。
4、此处省略……
基于接入方可能会使用以上几种情形来考虑该如何设计token过期时间会略微合适一点。
E、接口文档
好的接口文档一目了然。有些接口文档一整套都自己实现,非常辛苦,有些则通过第三方插件直接生成,这种方式比较理想,比如Swagger文档。
F、错误码
这是非常关键的一套接入方与被接入方之间通信的信息。接入方通过提供的错误码可以非常快速的定位问题并解决问题,所以一套优良的错误设计也是相当重要的。