Restful规范
1、关于Restful
随着前后端分离越来越普遍,后端接口规范也就越来越重要了。一套良好的接口规范可以提升工作效率,减少沟通障碍。通常我们都会采用RestfulApi方式来提供接口,使用JSON来传输数据。
Restful是一种软件架构风格,不是标准。既然不是标准,可以遵守,也可以不遵守!!!
2、Api设计6要素
-
资源路径(URI)
-
HTTP动词(Method)
-
过滤信息(query-string)
-
状态码(Status-code)
-
错误信息(Error)
-
返回结果(Result)
3、Restful规范要求
在实际开发中,本着提高工作效率、降低沟通障碍
的初衷,使用Restful规范去开发接口时,对于6要素有着以下的要求:
3.1、资源路径(URI)
资源:所有在服务器(电脑)保存的数据(如:音乐/视频/文章/个人信息…)都是服务器端资源.(项目中资源通常都是指的数据表的一条数据)
URI(Uniform Resource Identifier):统一资源标志符,包含URL和URN。
URL(Uniform Resource Locator):统一资源定位符
URN(Uniform Resource Name):统一资源名
在HTTP协议中, URI的组成如下:
-
接口通信协议
- http
- https
-
接口域名
- 最好是独立解析的域名,如www.xxx.com,域名是需要购买的
-
端口号
- http默认为80
- https默认为443
-
接口版本
- 将版本信息放在URI中 http://www.xxx.com/v1
-
请求路径
- 网络中的任何东西都为资源,均使用名词表示(一般为复数形式),如针对用户模块请求路径可以为http://www.xxx.com/v1/users/1
3.2、HTTP动词(Method)
对于资源,一般有4个操作:CURD(增/删/改/查)
- GET: 从服务器获取资源(一项或多项)(查)
- POST: 在服务器新建一个资源(增)
- PUT: 在服务器更新资源,服务器返回完整的属性(改)
- DELETE: 从服务器删除资源(删)【假删,逻辑删除】
以新闻资源为例:URI及HTTP动词设计如下 (资源名称推荐写复数形式)
HTTP动词 | URI路径 | 说明 |
---|---|---|
GET | http://域名/news | 获取列表数据 |
GET | http://域名/news/:id | 根据id获取一条数据 |
POST | http://域名/news | 添加一条数据 |
PUT | http://域名/news/:id | 根据id修改一条数据 |
DELETE | http://域名/news/:id | 根据id“删除“一条数据 |
上述形式的URI路径(路由)称之为资源路由
。
3.3、过滤信息(query-string)
通常也叫做请求参数或查询字符串(“?id=1&age=213
”)。
3.4、状态码(Status-code)
成功发送的请求必有响应(响应可能没有响应体,但是状态码肯定有),每个响应都会有一个状态码。在Restful风格编写接口时常见的HTTP请求状态码有:
状态码 | 含义 | 说明 |
---|---|---|
200 | OK | 操作成功, 并返回数据 |
400 | Bad Request | 请求语法错误 |
403 | Forbidden | 请求没有权限的资源 |
404 | Not Found | 没有找到请求的资源 |
405 | Method Not Allowed | 请求动词使用错误 |
500 | Internal Server Error | 服务器(配置)内部错误 |
更为详尽的状态码参考:
**GET **
200(OK) - 表示已在响应中发出
204(No Content) - 资源有空表示
301(Moved Permanently) - 资源的URI已被更新
303(See Other) - 其他(如,负载均衡)
304(not modified)- 资源未更改(缓存)
400 (bad request)- 指代坏请求(如,参数错误)
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求
**POST **
200(OK)- 如果现有资源已被更改
201(created)- 如果新资源被创建
202(accepted)- 已接受处理请求但尚未完成(异步处理)
301(Moved Permanently)- 资源的URI被更新
303(See Other)- 其他(如,负载均衡)
400(bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求
**PUT **
200 (OK)- 如果已存在资源被更改
201 (created)- 如果新资源被创建
301(Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他(如,负载均衡)
400 (bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求
**DELETE **
200 (OK)- 资源已被删除
301 (Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他,如负载均衡
400 (bad request)- 指代坏请求
404 (not found)- 资源不存在
409 (conflict)- 通用冲突
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求
3.5、错误信息(Error)
如果状态码是4xx
或者5xx
, 需要告诉客户端对应的错误信息。通常以Json格式返回,形式如下:
{
'error': '错误信息',
'code': '错误编号',
'msg': '资源没找到',
'message': 'xxxx',
'text': 'xxxx',
'reason': 'xxxxxx'
}
3.6、返回结果(Result)
针对不同的操作,服务需要返回的结果应该符合这样的规范:
-
GET /collections – 返回资源列表(数组对应的json格式)
-
GET /collections/:id – 返回单个资源 eg. /collections/1
-
POST /collections – 返回新生成的资源
-
PUT /collections/:id – 返回资源的完整属性
-
DELETE /collections/:id – 返回204状态码+空文档
实际开发中,通常会将状态码、错误信息、返回数据,都放到返回结果中(一般以json格式返回)。例如:
{
"code":200,
"msg":"success",
"goods":{
"id":1,
"goods_name":"华为P100 pro plus",
"goods_price": 9999.99,
"currency": "CNY",
"stock": 1288,
.......
}
}
返回的数据中,节点的
key
名字要遵循见名知义
4、接口安全
前后端分离式开发需要进行数据交互,传输的数据被偷窥、被抓包、被伪造时有发生,那么如何设计一套比较安全的API接口方案呢?
并不是所有的接口都需要考虑安全的,有些接口是公开的,任何人只要知道地址都可以调用,对于一些项目中需要用户登录才能访问的接口才需要考虑安全问题。
一般解决的方案有以下几类:
- token令牌认证(jwt)
- AK(app key)&SK(secret key)【用户名&密码】
- 时间戳超时验证+签名算法字符串
- URL签名(算法,非对称算法)
- 数据脱敏(防范数据库数据泄露)
- HTTPS
- 数字证书(防运营商)
- IP黑/白名单(服务器层面的限制,apache、nginx)
- oAuth2.0
关于
JWT
:
Json web token(JWT),是基于token的鉴权机制,类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息,为应用的扩展提供了便利。JWT具备以下几个优点:
-
因json的通用性,所以JWT是可以进行跨语言
-
JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息
-
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的
-
它不需要在服务端保存会话信息,所以它非常适合应用在前后端分离的项目上
使用JWT进行鉴权的工作流程如下(重点):
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息(查数据库)
- 服务器通过验证发送给用户一个token(令牌)
- 客户端存储token(Vuex+localStorage),并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
JWT是由三段信息构成的(头部、载荷、签名),将这三部分使用.
连接在一起就组成了JWT字符串,形如:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IjNmMmc1N2E5MmFhIn0.eyJpYXQiOjE1NTk1Mjk1MjksImlzcyI6Imh0dHA6XC9cL3d3dy5weWcuY29tIiwiYXVkIjoiaHR0cDpcL1wvd3d3LnB5Zy5jb20iLCJuYmYiOjE1NTk1Mjk1MjgsImV4cCI6MTU1OTUzMzEyOSwianRpIjoiM2YyZzU3YTkyYWEiLCJ1c2VyX2lkIjoxfQ.4BaThL6_TbIMBGLIWZgpnoDQ-JlAjzbiK3y3BcvNiGI
其中:
- 头部(header),包含了两(可以更多)部分信息,分别是类型的声明和所使用的加密算法。
一个完整的头部就像下面的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64加密/编码(该加密是可以对称解密的),这就得到了jwt的第一部分。
- 载荷(payload),载荷就是存放有效信息的地方。这些有效信息包含三个部分
- 标准中约定声明(建议但不强制)
- 公共的声明
- 私有的声明
定义一个payload:
{
"sub": "1234567890",
"name": "John",
"admin": true
}
依旧进行base64加密,这就得到了jwt的第二部分。
- 签名(signature),这个签证信息由三部分组成:
- 经过base64编码后的
- header
- payload
- secret(就是一个字符串,自己定义,值是什么无所谓)
- 经过base64编码后的
例如:
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret');
这样就得到了jwt的第三部分。
最终将三部分信息通过.
进行连接就得到了最终的jwt字符串。
需要注意的是
- secret是保存在服务器端的
- jwt的签发生成也是在服务器端的
- secret是用来进行jwt的签发和jwt的验证
所以,secret它就是服务端的私钥,在任何场景都不应该泄露出去。一旦其他人(包括客户端的用户)得知这个secret,那就意味着他们可以自我签发jwt,接口就没有安全性可言了。