Open API即开放API,也称开放平台。 所谓的开放API(OpenAPI)是服务型网站常见的一种应用,网站的服务商将自己的网站服务封装成一系列API(Application Programming Interface,应用编程接口)开放出去,供第三方开发者使用,这种行为就叫做开放网站的API,所开放的API就被称作OpenAPI(开放API)。
以前的软件开发都是针对特定的用户或群体进行设计,但用户的需求是千差万别的,众口难调就是这个道理。随着软件开发的发展,人们的关注点和设计模式也在悄悄发生着变化,我与其针对特定客户进行开发,不如站在大众的角度进行设计,把自己从甲方乙方的魔咒中解禁出来,自己成为甲方。也就是说我以前开发的软件只给你用,你还不满意,现在我只提供核心业务,你自己来对接,你想设计成什么样子就设计成什么样子。于是 Opean Api 应运而生!!淘宝、阿里、腾讯都是这么干的,人家是大公司,规则的制定者,人家怎么规定接口,你就得怎么对接!!有一个网站叫 聚合数据 专门提供一些免费的和收费的优秀 API,有兴趣的可以上去注册玩玩。
关于如何开发这样的一套接口,刨除实际的业务逻辑不说,我们来看看怎么设计?下面是我在设计的时候遇到问题的思考,可能不全面仅供参考!
先来看看有那几点需要考量?我也是凭经验和 Google。
签名鉴权(我需要知道请求我的是谁,有没有访问这个接口的权限)
流量控制(我不可能让你随随便便就无节制的访问我,我要能随时关停你)
请求转发(请求过来的 url,需要找到真正的业务处理逻辑,api最好只负责接口的规范,不负责业务的实现)
日志处理(你可以保持沉默,但你说的每一句话都会成为呈堂供证)
异常处理(即使老子内部出了问题,你也不会看到我的异常堆栈信息,我会告诉你我病了,请稍后再试)
参数规范(顺我者倡,逆我者亡)
多机部署(我有九条命)
异步回调(别想阻塞我,但我可以玩死你)
错误信息(你要认识到自己错误)
做API,尤其是 Open API 最重要的就是安全、安全、安全,试想一下,你的接口我可以随随便便就猜对账号密码,随随便便就可以DDOS 你,你都没有办法保证用户信息的安全,谁还敢用你。其实你是在做一件反黑客的设计,呵呵,是不是高大尚了很多。
Client:你怎么知道一个请求是我而不是别人呢?
API:你如果对接我,我给你起一个全世界唯一名字就吧,你请求的时候就告诉我你叫 xxx,赋值到请求中的app_key中就行,至于这个字段叫app_key还是叫 UserName还是叫 client_id,这个看你心情了。于是就有了接口的第一个功能,给客户起一个唯一名字。
Client:如果别人也知道了这个用户名,冒充我怎么办?
API:我还会给你一个配套的密码,密码只有你我知道,你请求的时候,把密码带过来,我先验证密码,密码通过了我才允许访问。这样就没人能冒充的了你了吧。你密码丢了,那就是你自己的事情了。
Client:现在 HTTP 都是明文通信,我每次访问都带上我的账号密码,那不等于广而告之天下吗?就像拿着一袋子钱在路上边走边喊“快来抢我呀!快来抢我呀!”,一个小小的嗅探器就能把用户的密码拿到手,如果用户习惯在所有地方用一个密码,那么你闯大祸了,黑客通过撞库的方法能把用户的所有信息一锅端。
API:那就就用你的账号密码先到我这儿换取一个口令吧,我们叫 token,这样携带口令且口令正确的我们就认,没有携带口令或口令不正确的我们几回绝,这样你就不用带着密码满世界跑了。
Client:但是那如果 Token 被截获了呢?黑客重放怎么办!!
API:这个口令是有一个过期时间的比如10分钟、一个小时、一天、3个月等等,你们也可以做到单点登录,我这边也可以有效的控制外人的入侵,同时避免了重复信息的反复查询数据库和对比等操作,绝对可以提高响应速度,校验 token 的有效性花费的时间绝对比查数据库要来的快啊。我们在服务器端接口被调用时就可以对发起请求的ip地址、user-agent之类的信息作比对,以防止伪造。再然后,如果token的有效期设得小,过一会儿它就过期了,除非黑客可以持续截获你的token,否则他只能干瞪眼。
Client:这个可以,但是我第一次输入密码的时候还是有可能被窃取啊?
API:那就上 HTTPS ,密码什么的用 SSL 加密协议传输,我看谁还能窃取到呢?你要还纠结呢咱就上非对称?
Client:…
API:还有话说么?没话说就老老实实对接
网络攻防
CORS
CSRF
劫持攻击
DDOS
XSS
SQL注入
文件上传漏洞
缓冲区溢出
如何设计一个符合 RESTful 的 OpenAPI
协议
API 与用户的通信协议,建议使用 HTTPs 协议。
域名
应该尽量将 API 部署在专用域名之下。
https://api.example.com
如果确定 API 很简单,不会有进一步扩展,可以考虑放在主域名下。
https://example.org/api/
版本(Versioning)
应该将 API 的版本号放入URL。
https://api.example.com/v1/
另一种做法是,将版本号放在 HTTP 头信息中,但不如放入 URL 方便和直观。Github 采用这种做法。
路径(Endpoint)
路径又称"终点"(endpoint),表示 API 的具体网址。
在 RESTful 架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以 API 中的名词也应该使用复数。
举例来说,有一个 API 提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
HTTP 动词
对于资源的具体操作类型,由 HTTP 动词表示。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
GET(SELECT):从服务器取出资源(一项或多项)。
POST(INSERT):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。
还有两个不常用的 HTTP 动词。
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
下面是一些例子。
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的 HTTP 动词)。
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
状态码的完全列表参见这里。
错误处理(Error handling)
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
{
error: "Invalid API key"
}
返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
Hypermedia API
RESTful API 最好做到 Hypermedia,即返回结果中提供链接,连向其他 API 方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向 api.example.com 的根目录发出请求,会得到这样一个文档。
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。
rel 表示这个 API 与当前网址的关系(collection 关系,并给出该 collection 的网址),href 表示 API 的路径,title 表示 API 的标题,type 表示返回类型。
Hypermedia API 的设计被称为 HATEOAS。Github 的 API 就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}
从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}
上面代码表示,服务器给出了提示信息,以及文档的网址。