写php到现在也写好几个app的api,感觉有三个比较关键的地方:

  1. 不同异常的捕获
  2. 日志系统的完备
  3. 接口的安全验证

前面两点我还没想好怎么写,感觉还是差了点啥,这篇博客就对‘接口开发的安全验证’做些总结。

最简单的验证

最简单的验证就是token(口令)验证,即第一次登陆的成功后服务端更新口令并且返回给客户端,后续的操作需要客户端携带这个口令去请求数据,服务器端验证这个token是否正确,然后继续操作。

1
2
3
4
{
data: [{},...],
token: 'asfdjl52f1df1'
}

1
2
3
if (check($token)) {
...
}

显然现在基本上很少就这么简单验证了的,因为只要客户端嗅探到了这个token就可以伪造请求,这种情况下有个补救的方法:

  1. 替换不安全的http为https,防止被嗅探到明文。
  2. 给token设置有效时限。

其实只要数据明文不被嗅探到,就是不做安全验证也无妨,但是谁能保证呢?

使用签名验证

签名验证就是请求中加了一些有用没用的字段,组合起来后通过后台跟客户端达成共识的一个方法生成签名,服务器端就依照这个方法进行签名验证。

1
2
3
4
5
6
7
8
{
data: [{},...],
timestamp: 1501296071, //时间戳
token: 'asfdjl52f1df1', //口令
r_i: 'aljdajsd', //随机字符串
...
sign: '6D6D5DFB8DF6F4DD1A7C7E16C710A42E'
}

1
2
3
if (check($input)) {
...
}

这后台可以验证的地方就多了,一般就验证签名,然后再验证下口令以及口令的时效就行了,这种接口验证方法用得比较多,具体代码就不写了。

使用rsa跟aes加密

大学都学过,rsa是非对称加密的,aes是对称加密的,虽然我已经忘了rsa是怎么算的了,只记得e跟d…

不过不要紧,用的时候知道aes会使用某长度的数据通过几轮参入转换来加密原始数据,而rsa就用来加密跟解密这个数据的就行了。

这里写个大概:

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
$curl = function ($url, $method = 'get', $data = '{}') {
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);

if (strtolower($method) === 'post') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json;charset=UTF-8",
]);
}

return curl_exec($ch);
};

$create_16_r_key = function () {
$arr = range(0, 15);
$len = sizeof($arr);

while (-- $len) {
$arr[$len] = mt_rand(0, 9);
}

return implode('', $arr);
};

$aes_encrypt_data = function ($data, $key) {
$cipher = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$iv = "0102030405060708";

$cip = mcrypt_encrypt($cipher, $key, $data, $mode, $iv);

return base64_encode($cip);
};

$rsa_encrypt_password = function ($public_key, $password) {
if (false === openssl_pkey_get_public($public_key)) {
throw new Exception('The server public key fail.');
}

openssl_public_encrypt($password, $cip, $public_key);

return base64_encode($cip);
};

function dump_exception(\Exception $e)
{
echo $e->getMessage() . PHP_EOL;
}

set_exception_handler('dump_exception');

//随手生成一些json数据
$json = json_encode([
'data' => array_chunk(
array_combine(
array_map(function ($value) {
return (string)$value . '_k';
},
range(1, 10)
),
range(1, 10)
), 3, true)
]);

//创建16位长度随机密码
$aes_16_key = $create_16_r_key();

//aes使用随机密码加密原始数据
$data = $aes_encrypt_data($json, $aes_16_key);

//获取rsa公钥
$get_public_key_url = 'http://scaffolding.1907.me/index.php?action=getPublicKey';
if (false === $public_key = $curl($get_public_key_url)) {
throw new Exception('failed to get the public key.');
}

//使用rsa公钥加密随机密码
$rsa_encrypt = $rsa_encrypt_password($public_key, base64_encode($aes_16_key));

print_r($curl(
'http://scaffolding.1907.me/index.php',
'post',
json_encode(['key' => $rsa_encrypt, 'data' => $data])
)
);

服务器端解密:

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
$rsa_private_key = '-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDZIMrlH6rcpPJR5+JVom5oz2KQ2zo35gcg/fBVXNrKsZSjPQ2A
amT88oB3qad+sENNCPHAnMU316jdTGQvXHtOBJwfX7K5epX1exYjkraGo5GT3xPw
tSryVr0bGb0yqb0gyFgmdVkAZhTqx2hoRc7krJ6ocVOeFITC0cmdyVym4QIDAQAB
AoGBAI1cmDxMPcWhblJ9EhKGyjNaseV1lZXHIWUNb2dkKN5Gd2s/2IZ+vnkguRsv
TWliAK8q35pzdsNAmSRbE+7x2yRg5Ue3b7aaYwjJOq5LyGteu46mR+Eg2DLJSOk8
r3amid8cFxcN4LTjCeNJ9Gq/0Jw8cMuiw3s/qcsovfJIAv5BAkEA97eTTELJEOgl
+tNNtwIHedXD6hXxQcWCp4ut4g3iM2CNFWUKHf0ypHC9h4kXmw3qqk1gzC4+8mcL
GG8L0RRUxQJBAOBjYSk1P6tzeSyUMhi4B5qVpS3nOeJ08SbCV8z0uWAD48v3uUQ7
jmA8V4+lyOC7YmxpeHx4+RHQeJ1jVfXBQ20CQQC6rki+RvJZ4GmG3ikKCuhxY6xy
Q7j99QfilfwjiIz4ZQHNpsh6Ey9QB3p9os38Vv+K+idBmHRtn0QYVM9V8Hl1AkEA
hvbfiwSvPjXfbZPZqgqO8EkQKFMK+w3xuqlsXCfalEjirF1dPxA/a9z/obRK5flv
ktvBj8THsxJcafZEzuOm5QJBAPPaUjw6bNuRgK5phh/H0ymYz0FB/XPHc1qmO3oT
DG+H0uc13wibzLhti3NoyzgyMvbnTDZxNEthUlJkgvnh3Z0=
-----END RSA PRIVATE KEY-----';

$rsa_public_key = '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZIMrlH6rcpPJR5+JVom5oz2KQ
2zo35gcg/fBVXNrKsZSjPQ2AamT88oB3qad+sENNCPHAnMU316jdTGQvXHtOBJwf
X7K5epX1exYjkraGo5GT3xPwtSryVr0bGb0yqb0gyFgmdVkAZhTqx2hoRc7krJ6o
cVOeFITC0cmdyVym4QIDAQAB
-----END PUBLIC KEY-----';

if (isset($_GET['action']) and $_GET['action'] === 'getPublicKey') {
echo $rsa_public_key;
return ;
}

$input = $_POST ?: file_get_contents("php://input");

list($key, $data) = array_values(json_decode($input, true));

openssl_private_decrypt(base64_decode($key), $pass, $rsa_private_key);

//拿到aes随机密码
$pass = base64_decode($pass);

$iv = '0102030405060708';

//解密aes
$decrypted = mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
$pass,
base64_decode($data),
MCRYPT_MODE_CBC,
$iv
);

echo $decrypted;

大概就是这样,实际开发中我没用到这种设计,就粗略写写。