如何保证接口的安全性:加密协议(HTTPS)系统权限 接口签名
开发过程中经常会使用到接口,而接口的安全性是首要考虑的问题。防止其他非预期的任意调用或者是恶意攻击接口,对系统带来风险。
使用加密协议(HTTPS)来保证数据传输的安全性
关于HTTP和HTTPS :
超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。
为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS,为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
参考:HTTP与HTTPS的区别
使用系统身份认证/权限管理来限制接口的非法访问
对于身份认证失败或者不具有接口访问权限的接口访问,系统直接拒绝对应的请求。可自主实现也可使用第3方的权限框架,如Shrio等。
使用接口签名来验证请求的签名值sign是否和接口生成的签名值一致
大概意思:请求方和接口方根据特定同一规则生成签名值sign, 在请求时对比签名值,如果签名值一致则是合法的,否则视为非法—直接返回。
生成签名值方法:将数据经过md5运算生成字符串。
生成签名值例子 一
private String generateSignatureInfo(String checkNo,String flag,String timestamp) throws NoSuchAlgorithmException, UnsupportedEncodingException {
String appkey = "xyz"; // 与接口提供方(联通)的约定值
String appSecret = "66666"; // 与接口提供方(联通)的约定值
String info = checkNo + flag + timestamp + appkey + appSecret;
MessageDigest mdEnc = MessageDigest.getInstance("MD5"); // 使用md5签名算法
mdEnc.update(info.getBytes("UTF-8"));
// 在mdEnc.digest()的首位刚好等于0的时候,使用new BigInteger(1,mdEnc.digest())会导致0被省略,以致生成的签名信息错误, 这是个坑!!(所以不用这种方式)
// String sign = new BigInteger(1,mdEnc.digest()).toString(16); // 生成签名信息
/*
* 使用以下方式生成签名信息:
* 将每一个字节取出来,单独转成十六进制
*/
byte[] bytes = mdEnc.digest();
Formatter formatter = new Formatter();
for (byte b : bytes) {
formatter.format("%02x", b);
}
String sign = formatter.toString();
formatter.close();
return sign;
}
这里的appkey和 appSecret的值是请求方和接口提供方共同约定的。
生成签名值例子 二
sign值计算说明
这里的sign就是回调数据body中数据经过字典排序加上厂商appSecret后经过md5运算后生成的字符串。
主要分三步:
第一步,将回调数据中body里参数按照字母排序,保证固定顺序,因日后系统更新等原因,回调数据中body里的参数会有所增加,所以最好遍历整个body,确保将每一组key=value进行排序拼接,防止回调数据中参数增加导致验证sign错误;
第二步,遍历组装字符串之后拼接接入账号的appSecret当salt,产生如key1=value1&key1=value2#appSecret形式字符串;
第三步:对拼接字符串计算hash,目前统一采用md5算法,并转成16进制字符串。此16进制字符串就是回调数据head中的sign值。
将本地计算sign值同回调中的sign对比,比对一致说明数据来源可靠且未变更篡改。
代码示例:
public static void main(String[] args) {
// 回调数据json中取body,
String jsonStr="{\"process\":\"2\",\"stamp\":\"KonWykAGc9t9rgtd8E8AJJZD+OD23kaVBgWL6MHJM3oSSrOje2ynpBjP+JDRPPje0iuyDtgSIP9hKUk6HsgkBpxmH4D9KlFdepngFZPYkx4yRl2h5zph2YnayfJ1RHHOrfm/ykwjRgLV4UUKEA4LZ70xqGSyT0zkQE11:55:33\",\"phoneNum\":\"13333333333\",\"stampStatus\":\"10\",\"openId\":\"bf61fe5f9807ed3bqe5f4w5b33333ca\"}";
Map<String, String> bodyMap = new HashMap<String, String>();
JSONObject jsonObj = JSON.parseObject(jsonStr);
// 遍历json,将key和value存入map
for (Map.Entry<String, Object> entry : jsonObj.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
bodyMap.put(entry.getKey(), entry.getValue().toString());
}
String sign=buildThirdSign(bodyMap, "111111");
System.out.println(sign);
}
public static String buildThirdSign(Map<String, String> mapBody, String clientSecret) {
try {
StringBuffer paramStr = new StringBuffer();
String resultStr = "";
// 字母排序,使用key=value,并用&分割的形式拼接字典排序字符串
if (mapBody != null && mapBody.size() > 0) {
List<String> paramsKeyList = new ArrayList<String>(mapBody.keySet());
Collections.sort(paramsKeyList);
for (String key : paramsKeyList) {
String value = String.valueOf(mapBody.get(key));
if (value != null && !"".equals(value)) {
paramStr.append("&" + key);
paramStr.append("=" + value);
}
}
}
// 去掉最左侧第一个&符号,并用#把clientSecret与字典排序字符串进行拼接
String signSourceStr = paramStr.subString(1) + "#" + clientSecret;
// 计算字符串md5 16进制字符串,此值就是回调数据中的sign
resultStr = getMd5(signSourceStr);
return resultStr;
} catch (Exception e) {
}
return clientSecret;
}
public static String getMd5(String s) {
char[] hexDigits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
'f' };
try {
byte[] e = s.getBytes("UTF-8");
MessageDigest mdTemp = MessageDigest.getInstance("md5");
mdTemp.update(e);
byte[] md = mdTemp.digest();
int j = md.length;
char[] str = new char[j * 2];
int k = 0;
for (int i = 0; i < j; ++i) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 15];
str[k++] = hexDigits[byte0 & 15];
}
return new String(str);
} catch (Exception arg9) {
return null;
}
}