常用的请求认证方式有两种:
1、Signature认证
一次性的身份校验方式,常见于不同项目间的api通信
一般形式是通过 AppID/AccessKey/AppSecret 及签名算法针对通信数据生成签名
AccessKey作为公钥,AppSecret作为私钥,AppSecret不能放在网络上传输
接口数据推送时,会随带上AppID、AccessKey、Timestamp 及 Signature
在服务端同样留存着一份相同的 AppID/AccessKey/AppSecret 配置
服务端接受到请求后通过AppID对应出匹配的AppSecret,结合相同的签名算法计算签名,并与请求附带的签名做校验
时间戳可以防止恶意用户截取到签名后进行伪装请求,使签名只在一定时间范围内有效,过期后不能再请求
由于每次请求的数据载荷不同,所以一般每次请求都会产生不同的签名.
2、Token认证
状态可维持的身份校验方式,常见于第三方服务或APP接口的OAUTH2认证中
一般形式是通过 账号/密码 或 clientId/clientSecret 向认证服务请求Token
服务端Authenticate通过后返回Token,并在一定时间内对Token及相应账号信息进行缓存
后续的接口通信都将通过Token来进行认证
在一段活跃期内, 连续的请求Token是相同
本文主要介绍签名认证的主要实现方式
客户端:
(1)封装一个获取时间戳、生成签名的工具类
package com.solin.tools.utils;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Formatter;
public class EncryptedUtil {
/*
* HMACSHA256加密签名
* Hash-based Message Authentication Code SHA256
* @param sourceStr 加密的源字符串
* @param secretAccessKey 密钥
*/
public static String getSign(String sourceStr,String secretAccessKey) throws Exception{
SecretKey secretKey = new SecretKeySpec(secretAccessKey.getBytes(),"HmacSHA256");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
final byte[] hmac = mac.doFinal(sourceStr.getBytes());
StringBuilder stringBuilder = new StringBuilder(hmac.length*2);
Formatter formatter = new Formatter(stringBuilder);
for (byte b :hmac){
formatter.format("%02x",b);
}
formatter.close();
return stringBuilder.toString();
}
/*
* MD5加密
* @param sourceStr 加密的源字符串
*/
public static String MD5(String sourceStr) {
String result = "";
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(sourceStr.getBytes("UTF-8"));
byte b[] = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0)
i += 256;
if (i < 16)
buf.append("0");
buf.append(Integer.toHexString(i));
}
result = buf.toString();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/*
* 时间转换成unix时间戳
* @param dateStr 时间字符串
*/
public static Long getUnixTimestamp(String dateStr){
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
try {
Date date = simpleDateFormat.parse(dateStr);
return date.getTime();
} catch (ParseException e) {
return null;
}
}
(2)封装一个简单的日期工具类
package com.solin.tools.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 日期工具类
*/
public class DateUtils {
//log
private final static Logger logger = LoggerFactory.getLogger(DateUtils.class);
private DateUtils(){}
//Srting转date
public static Date changeStringToDate(String dateStr) {
Date date = new Date();
try {
date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateStr);
} catch (ParseException e) {
logger.error(e.getMessage());
}
return date;
}
//Srting转date精确到毫秒
public static Date changeStringToMillisecondDate(String dateStr) {
Date date = new Date();
try {
date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse(dateStr);
} catch (ParseException e) {
logger.error(e.getMessage());
}
return date;
}
//date转String精确到毫秒
public static String changeDateToMillisecondString(Date date) {
String dateStr = "";
try {
dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(date);
} catch (Exception e) {
logger.error(e.getMessage());
}
return dateStr;
}
}
(3)生成时间戳和签名测试
package com.solin.tools;
import com.solin.tools.utils.SignResult;
/**
* 生成签名测试
*/
public class SignTest {
public static void main(String[] args) throws Exception{
String appId = "123456";
String accessKey = "e16188442d8f460696bddf2b";
String secretKey = "c0248ad5cced4820835ad54d90ba1fe0";
String timestamp = DateUtils.changeDateToMillisecondString(new Date());
// 将timestamp转化为unix时间戳
long timestampLong = EncryptedUtil.getUnixTimestamp(timestamp);
// 拼接字符串
String requestStr = timestampLong + appId + accessKey;
// MD5字符串
String requestMd5Str = EncryptedUtil.MD5(requestStr);
// 生成签名
String sign = EncryptedUtil.getSign(requestMd5Str ,secretKey);
System.out.println("timestamp ===>>> " + timestamp);
System.out.println("sign ===>>> " + sign);
}
}
服务端:
(1)时间戳及签名校验工具类
/**
* API鉴权工具类
* 1、校验时间戳
* 2、校验签名
*/
public class AppAuthenticationUtils {
//log日志
private static final Logger logger = LoggerFactory.getLogger(AppAuthenticationUtils.class);
//鉴权时间戳范围,单位毫秒
public static final long scopeTime = 300000L;
/**
* API鉴权
* @param appId 应用ID
* @param accessKey 访问密钥
* @param secretKey 签名密钥
* @param requestTimestamp 时间戳
* @param signature 签名
*/
public static ResponseStatus checkAuthentication(String appId , String accessKey , String secretKey,
String requestTimestamp, String signature) {
long requestUnixTimestamp = EncryptedUtil.getUnixTimestamp(requestTimestamp);
// 校验请求时间戳
if (!checkRequestTimestamp(requestUnixTimestamp , scopeTime)){
return new ResponseStatus(false, "Invalid requestTimestamp ...");
}
// 校验签名
if (!checkSignature(appId, accessKey, secretKey, requestTimestamp,signature)){
return new ResponseStatus(false, "Invalid signature...");
}
return new ResponseStatus(true, "鉴权通过");
}
/**
* 校验签名
* @param appId 应用ID
* @param accessKey 接口密钥
* @param requestTimestamp 时间戳
* @param signature 签名
*/
private static boolean checkSignature(String appId, String accessKey, String secretKey, String requestTimestamp,
String signature) {
boolean isAuthPass = false;
try {
long requestUnixTimestamp = EncryptedUtil.getUnixTimestamp(requestTimestamp);
String requestMd5Str = EncryptedUtil.MD5(requestUnixTimestamp + appId + accessKey);
String sign = EncryptedUtil.getSign(requestMd5Str ,secretKey);
if (StringUtils.isNotEmpty(sign) && sign.equals(signature)){
isAuthPass = true;
}
} catch (Exception e) {
logger.error("Interface authentication failed ", e);
}
return isAuthPass;
}
/**
* 判断失效时间
* @param requestUnixTimestamp unix时间戳
*/
private static boolean checkRequestTimestamp(long requestUnixTimestamp ,long scopeTime){
boolean isVaildTimestamp = false;
long currentTime = System.currentTimeMillis();
long minVaildTime = currentTime - scopeTime;
long maxVaildTime = currentTime + scopeTime;
if (requestUnixTimestamp >= minVaildTime && requestUnixTimestamp <= maxVaildTime){
isVaildTimestamp = true;
}
return isVaildTimestamp;
}
}