13:接口安全
安全571 字
■ 网络限制
场景:内外部调用的接口,如果可以属于内部系统间或第三方合作的资源调用情况。
说明:调用方网络限制,比如通过防火墙、主机host 和Nginx deny等技术措施进行校验。
配置示例:使用网络防火墙配置IP白名单。
iptables -I INPUT -s 10.1.24.12/32 -p tcp -m tcp --dport 8080 -j ACCEPT
在生产环境不建议使用 iptables 的方式进行限制,因为这样会增加服务器的维护成本,在迁移服务器时,如果缺少文档或者管理人员疏忽,会导致环境不一致的情况。
使用Nginx deny配置IP白名单:
location / {
allow 10.1.24.12/32;
deny all;
}
■ 身份认证
场景:验证调用者身份。
说明:调用方身份认证,比如Key、Secret、证书等技术措施进行校验,禁止共享凭证。
Java示例:校验Key等是否有效。
public class APIInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
String accessKeyId = request.getParameter("AccessKeyId");
if (validKeyId(accessKeyId) == false)
return false;
...
}
private boolean validKeyId(String AccessKeyId) {
User user = getUserById(AccessKeyId).getPermission;
if (user)
return true;
return true;
}
...
}
PHP 示例:校验Key等是否有效。
function verifyAccessKey($key) {
$secret = getSecretByAccessKey($Key);
if ($secret && getPermission($secret)) {
return $secret;
}
return null;
}
■ 完整性校验
场景:验证参数没有被修改。
说明:调用数据安全,对全部参数使用SHA1等摘要运算进行数字签名,识别数据被篡改。
Java示例:通过SHA1验证参数完整性。
public class APIInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String SignatureNonce = request.getParameter("SignatureNonce");
if (SignatureNonce == null)
return false;
...
Enumeration paraKeys = request.getParameterNames();
String encodeStr = "";
while (paraKeys.hasMoreElements()) {
String paraKey = (String) paraKeys.nextElement();
if (paraKey.equals("SignatureNonce"))
break;
String paraValue = request.getParameter(paraKey);
encodeStr += paraValue;
}
encodeStr += Default.TOKEN_KEY;
if (!SignatureNonce.equals(DigestUtils.sha1Hex(encodeStr))) {
response.setStatus(500);
return false;
}
return true;
}
}
PHP 示例:通过SHA1验证参数完整性。
function verifySign($secret, $data)
{
if (!isset($data['sign']) || !$data['sign']) {
echo 'sign error.';
return false;
}
$sign = $data['sign'];
unset($data['sign']);
ksort($data);
$params = http_build_query($data);
if ($sign == sha1($params.$secret)) {
echo 'request success.';
return true;
} else {
echo 'request fail.';
return false;
}
}
■ 合法性校验
场景:接收到参数是否完整、是否存在重放攻击、越权问题等。
说明:调用的参数检查,如参数是否完整,时间戳和Token是否有效,调用权限是否合法等。
Java示例:验证参数完整。
public class APIInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
String accessKeyId = request.getParameter("AccessKeyId");
if (validKeyId(accessKeyId) == false)
return false;
String token = request.getParameter("Token");
if (validToken(token) == false)
return false;
String value1 = request.getParameter("param1");
String value2 = request.getParameter("param2");
if (value1 == null || value2 == null)
return false;
String url = request.getRequestURL().toString();
if (validRequest(url, AccessKeyId) == false)
return false;
return true;
}
private boolean validRequest(String url, String AccessKeyId) {
Permission permission = getUserById(AccessKeyId).getPermission;
userUrlPattern = permission.getUrl();
if (url.contains(userUrlPattern))
return true;
return false;
}
}
PHP 示例:验证参数完整。
function verifyParams($data) {
if (!isset($data['token']) || !$data['token']) {
echo 'token null.';
return false;
}
if (!validToken($data['token'])) {
echo 'token error.';
return false;
}
if (!isset($data['param1']) || !$data['param1']) {
echo 'para1 null.';
return false;
}
if (!isset($data['param2']) || !$data['param2']) {
echo 'param2 null.';
return false;
}
return true;
}
■ 可用性要求
场景:对请求频率和次数进行限制。
说明:调用服务要求,调用满足等幂性即保持数据一致性,对调用频率和有效期进行限制。
Java示例:限制代码如下:
public class APIInterceptor extends HandlerInterceptorAdapter {
private long timeStamp = getNowTime();
private static int reqCount = 0;
private final int limit = 100;
private final long interval = 1000;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String timeStamp = request.getParameter("TimeStamp");
if (validTime(timeStamp) == false)
return false;
if (notFrequent() == false)
return false;
...
}
private boolean validTime(String timeStamp) {
if (nowStamp - timeStamp < 3*60)
return true;
return false;
}
public boolean notFrequent() {
long now = getNowTime();
if (now < timeStamp + interval) {
reqCount++;
return reqCount <= limit;
} else {
timeStamp = now;
reqCount = 1;
return true;
}
}
}
PHP 示例:限制代码如下:
function verifyTimeStamp($data, $_SESSION) {
if (!isset($data['timestamp']) || !$data['timestamp']) {
echo 'timestamp error.';
return false;
}
if (time() - $data['timestamp'] > 180) {
echo 'timestamp timeout.';
return false;
}
if ($_SESSION['reqCount'] < 100) {
$_SESSION['reqCount'] += 1;
return true;
} else if (time() > $_SESSION['starttime'] + 3600) {
$_SESSION['reqCount'] = 1;
$_SESSION['starttime'] = time();
} else {
echo 'request frequeuent.';
return false;
}
return true;
}
■ 异常处理
场景:记录请求的日志。
说明:调用的异常处理,调用行为实时检测,发现异常及时阻拦。
Java示例:根据记录日志对异常IP进行封禁。
public class APIInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
logger.info(request);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info(request);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info(request);
}
}
PHP 示例:根据记录日志对异常IP进行封禁。
<?php
session_start();
$data = $_POST;
if (verifyTimeStamp($data, $_SESSION)) {
error_log("verifyTimeStamp error!", 0);
exit();
}
secret = verifyAccessKey($data['key'])
if (!secret) {
error_log("verifyAccessKey error!", 0);
exit();
}
if (!verifySign($secret, $data)) {
error_log("verifySign error!", 0);
exit();
}
if (!verifyParams($data)) {
error_log("verifyParams error!", 0);
exit();
}
// process data here
?>
建议:在代码中使用“抛异常”还是“返回错误码”,对于公司外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess、“错误码”、“错误简短信息”。