背景:


最近和公司的同事一起做了一个项目,主要是把公司里的一些东西做成接口,供第三方手机APP来调用。这个接口要做成通用的。任何第三方都可以
来调用。而且要保证第三方有权限来调用这些接口、能调用这些接口中的哪些接口以及用户身份认证。最后讨论决定用RESTful API。目前在三种
主流的web服务实现方案中,REST模式服务相比复杂的SOAP和XML-RPC对比来讲,更加简洁,越来越多的WEB服务开始使用REST设计并实现。但其缺少
安全特性。为了解决这个缺少安全特性,请看下面。

REST API简介


REST是无状态传输,无需session,所以每次请求都得带上身份认证信息。REST是基于http协议。它的安全特性需要我们自己来实现。RESTful web
services概念的核心就是”资源”。资源可以用URI来表示。客户端使用HTTP协议订阅的方法来发送请求到这些URIS

HTTP方法 行为 实例

GET 获取资源的信息 http://xx.com/api/orders

GET 获取某个特定资源的信息 http://xx.com/api/orders/123

POST 创建新资源 http://xx.com/api/orders

PUT 更新资源 http://xx.com/api/orders/123

DELETE 删除资源 http://xx.com/api/orders/123

对于请求的数据一般用json。

身份认证


身份认证包含很多种,有HTTP Basic,APIkey Oauth等,下面简单介绍一下:

HTTP Basic


REST由于是无状态传输,所以每一次请求都得带上身份认证信息,第一种身份认证就是http basic。这种方式在客户端要求简单。在服务端实现
也很简单,只需要配置apache等web服务器即可实现,所以对于简单的服务来说还是挺方便的。但是这种方式安全性较低,就是简单的将用户名和
密码base64编码放到header中。base64编码前: Basic admin:admin base64编码后: Basic YWRtaW46YWRtaW4= 放到Header中:
Authorization: Basic YWRtaW46YWRtaW4=
正是因为是简单的base64编码存储,切记在这种方式下一定要使用SSL,不然就裸奔了。

API KEY


API Key就是经过用户身份认证之后服务端给客户端分配一个API Key。流程如下:
client端向服务端注册,服务端给客户端发送响应的api_key以及security_key,这个是一一对应的。注意保存不要泄露,然后客户端根据
api_key,security_key,timestrap,rest_url(请求路径)采用hmacsha256算法得到一个hash值sign,构造URL发送到服务端。服务端收到该请求
后,首先验证api_key是否存在,存在则获取该api_key对应的security_key。接着验证timestrap是否超过时间限制,可依据系统而定。这样
就防止部分重放攻击,获取rest_url来计算sign。之后和url中的sign值做校验。这样的设计就防止了数据被篡改。
client:
api_key,security_key,timestrap,rest_url ——->sign

/rest/v1/interface/eth0/?api_key=jdfkla132&timestrap=14232423423423&sign=13sdfsdfsdsd
server:
/rest/v1/interface/eth0/?api_key=jdfkla132&timestrap=14232423423423&sign=13sdfsdfsdsd ----->api_key,timestrap,rest_url,sign

api_key--->security_key
api_key,security_key,timestrap,rest_url  ------->sign
sign <---> sign

Oauth2


腾讯QQ第三方登录。

我们现在所实现是:


APP认证,接口认证。身份认证。使用过滤器Filter来实现。


SDK-API系统为对接App分配appId和appSecret
下面参数,每次访问的时候必须放在HTTP Header部分。

X-Security:APP认证,所有的api请求均要带上次参数。
X-Security=(appId + “:” + currentTimestampInMillis +”:” + checkSum) checkSum = HexString(MD5Hash(appId + “:”+
currentTimestampInMillis + “:” +appSecret))。
X-Security例子:iHGWFNWR23:1412342342342:5454c3345sdf323sdfsf3
服务器端获取到X-Security之后,先判断是否为null,然后通过appid来获取appSecret来判断appId是否有效。然后根据currentTimestampInMillis
来判断时间是否过期。然后判断传过来的checkSum和自己算出来的是否一致。如果一致就APP接口认证通过。
appId做接口认证。即有没有权限访问其中某一个接口。在服务端把每一个appId对应接口访问路径记录下来。


X-User-ID:用户登录之后xxxx分配的用户ID,非合作方ID。登录需要单独写个接口。


X-Token:登录成功之后分配的Access token。和X-User-ID配对使用。如果HTTP返回407错误,客户端必须对用户重新验证登录,重新分配Access token


X-UUID:为每一次API请求生成的唯一标识,用以跟踪数据。

解析:


当请求到达服务端的时候,APP认证和接口认证通过之后,就开始身份认证了。首先从请求中获取X-User-ID和X-Token。然后需要校验登录的
URL进行登录校验。注意:有一些接口是不需要校验登录的。比如:主播列表,排行榜等信息。因为X-User-ID和X-Token是我们分配给第三方的
所以我们自己就清楚该怎么校验了。

SDK API实现详解


我们使用swagger文档来生成spring boot框架的java代码。
swagger地址:editor.swagger.io/#/
下面有个登录接口。用于第三方登录来获取X-User-Id和X-Token。
api文档内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

11:02:22
ccc 2016/8/20 11:02:22
swagger: "2.0"

info:
title: 艾米直播 RESTful APIs
description: |

** xxxx SDK-API 系统首先为对接App分配`appId` 和`appSecret` **

*** 下面参数,每次访问时候必须在`HTTP Header`部分 ***

* `X-Security`: App接口认证, 所有的api 请求均需要带有此参数。

X-Security=(appId + ":" + currentTimestampInMillis + ":" + checkSum); checkSum=HexString(MD5Hash(appId + ":" + currentTimestampInMillis + ":" + secret)).

X-Security例子: iHGWFNWR23:1445823337551:6355c774a13223sdfse518510ee63b9

* `X-User-ID`: 用户登录之后xxxx分配的用户ID,非合作方ID

* `X-Token`: 用户成功登录之后分配的 Access token ,和 `X-User-ID` 配对使用。如果HTTP返回`407`错误,客户端必须对用户重新验证登录。SDK-API将为用户重新分配Access token。

* `X-UUID`: 为每次API请求生成的唯一标志,用以跟踪数据。

version: "1.0.0"
contact:
name: xxg
email: taixiang.shi@mobimtech.com

host: localhost:8080
basePath: 1
schemes:
- http

securityDefinitions:
X-Security:
type: apiKey
name: X-Security
in: header
X-User-ID:
type: apiKey
name: X-User-ID
in: header
X-Token:
type: apiKey
name: X-Token
in: header
X-UUID:
type: apiKey
name: X-UUID
in: header
paths:
/user/third:
post:
tags:
- user
summary: 第三方账号登录
description: 根据合作方appId创建xxxx账号
operationId: createUserWithThirdApp
produces:
- application/json
parameters:
- in: body
name: body
description: Created user object
required: false
schema:
$ref: "#/definitions/ThirdToken"
responses:
"404":
description: 无效的token
"200":
description: ThirdToken 校验成功,xxxx为第三方账号分配对应的
schema:
$ref: "#/definitions/LoginResp"
default:
description: 业务异常
schema:
$ref: '#/definitions/Response'
security:
- X-Security: []
- X-UUID: []
definitions:
ThirdToken:
type: object
properties:
thirdUid:
type: string
token:
type: string
LoginResp:
type: object
properties:
userId:
type: string
xToken:
type: string
Response:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
description: 10000:成功;权限or安全:20001+;应用:301xx
message:
type: string