1.Token生成逻辑
生成时间戳timestamp和时间戳nonce(保证每次唯一).
signOriginStr= v_name + timestamp + nonce; //待签名字符串
salt=func(); //例如:通过一些特定算法,计算出新的salt,例如进行128个字节,散列后 组合在散列,在加上timestamp
token = HMAC(signOriginStr, salt);//最后签名
2.具体实现(加密时字段顺序不能变):
salt="HSH_APP_SIGN"
nonce为随机字符串,保证不重复,建议生成算法:nonce=MD5(UUID.randomUUID() + uuid)或直接用 UUID.randomUUID()
signOriginStr= v_name + timestamp(毫秒) + nonce
TOKEN= MD5(signOriginStr + SHA(SHA(salt + timestamp) + timestamp));
3.接口认证规则
a.生成Token的参数全都不为空
b.取参数使用同样的加密规则与参数Token一致
c.判断时间戳是否在60s之内,如果不在抛弃记录错误。
d:判断nonce是否已经存在,如果存在则抛弃记录错误。(每一个nonce直接放入redis设置60s的过期时间,1秒1000个请求, redis只需要存储60万个keys)
4.nonce生成
function nonce() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
var nonce = s.join("");
return nonce;
}
5.uuid生成
[[[UIDevice currentDevice] identifierForVendor] UUIDString]; (iOS)
android:
public static String getUniqueID(Context context) {
long id = ((long) getDeviceImei(context).hashCode() << 32) | getMac(context).hashCode();
String m_szLongID = getPesudoUniqueID() + id;
// compute md5
MessageDigest m = null;
try {
m = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
m.update(m_szLongID.getBytes(), 0, m_szLongID.length());
// get md5 bytes
byte p_md5Data[] = m.digest();
// create a hex string
String m_szUniqueID = new String();
for (int i = 0; i < p_md5Data.length; i++) {
int b = (0xFF & p_md5Data[i]);
// if it is a single digit, make sure it have 0 in front (proper padding)
if (b <= 0xF)
m_szUniqueID += "0";
// add number to string
m_szUniqueID += Integer.toHexString(b);
} // hex string to uppercase
m_szUniqueID = m_szUniqueID.toUpperCase();
return m_szUniqueID;
}
6.示例:
long timestamp = 1555324734L;
String nonce = "324734";
String v_name = "3.2.1.0";
String mac = "48-5A-B6-CE-57-91";
加密后:
sha1 = 39d05da6ab1d9364a047515b6f0212237770069d
sha2 = 84c5e99c2d520469bbd254edb53247739b528bef
TOKEN = f916281ccd0d93fa909658b030be50a6
7.Java实现
代码设计基于springboot自动装配策略:
SecurePolicy.java 接口安全校验的核心类
package com.common.secure;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import com.common.utils.EncryptUtil;
/**
* 接口安全Token校验策略
*
* @author dwj
* @Date 2019年4月17日
* @version 1.0
* @Description TOKEN= MD5(signOriginStr + SHA(SHA(salt + timestamp) +
* timestamp));
*/
public class SecurePolicy {
private static Logger LOGGER = LoggerFactory.getLogger(SecurePolicy.class);
private long tokenActiveTime;// token有效时间
private String salt;
private RedisTemplate<String, String> redisTemplate;
public SecurePolicy(SecurePolicyProperties config,
RedisTemplate<String, String> redisTemplate) {
this.tokenActiveTime = config.getTokenActiveTime();
this.salt = config.getSalt();
this.redisTemplate = redisTemplate;
}
/**
* 接口安全校验
*
* @param verifyToken
* 待校验Token
* @param signOriginStr
* 签名原始字符串
* @param nonce
* @param timestamp
* @return
*/
public boolean verify(String verifyToken, String signOriginStr,
String nonce, long timestamp) {
if (timestampTimeout(timestamp) && nonceRetry(nonce)
&& tokenVerity(verifyToken, signOriginStr, timestamp)) {
return true;
}
return false;
}
// 时间戳过期
private boolean timestampTimeout(long timestamp) {
long now = System.currentTimeMillis();
// 时间戳过期
if (Math.abs(now - timestamp) > tokenActiveTime) {
LOGGER.warn("异常请求,时间戳过期!");
return false;
}
return true;
}
// nonce是否被重放
private boolean nonceRetry(String nonce) {
// nonce是否被重放
if (!redisTemplate.opsForValue().setIfAbsent(nonce.toString(),
String.valueOf(System.currentTimeMillis()))) {
LOGGER.warn("异常请求,NONCE重放!");
return false;
}
// 120s过期
redisTemplate.expire(nonce.toString(), tokenActiveTime,
TimeUnit.MILLISECONDS);
return true;
}
/**
* TOKEN校验
*
* @param verifyToken
* @param signOriginStr
* @param timestamp
* @return
*/
private boolean tokenVerity(String verifyToken, String signOriginStr,
long timestamp) {
// token校验
String token = EncryptUtil.signEncrypt(signOriginStr, salt, timestamp);
if (!verifyToken.equals(token)) {
LOGGER.warn("异常请求,TOKEN不一致!");
return false;
}
return true;
}
}
SecurePolicyAutoConfiguration.java 自动装配类
package com.common.secure;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
/**
* 自动配置
*
* @author dwj
* @Date 2019年6月24日
* @version 1.0
*/
@Configuration
@EnableConfigurationProperties({ SecurePolicyProperties.class })
public class SecurePolicyAutoConfiguration {
@Bean
public SecurePolicy securePolicyInit(
SecurePolicyProperties securePolicyProperties,
RedisTemplate<String, String> redisTemplate) {
return new SecurePolicy(securePolicyProperties, redisTemplate);
}
}
SecurePolicyProperties.java 配置类
package com.common.secure;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 安全策略配置
*
* @author dwj
* @Date 2019年6月24日
* @version 1.0
*/
@ConfigurationProperties(prefix = "secure.policy")
public class SecurePolicyProperties {
private long tokenActiveTime = 2 * 60 * 1000;
private String salt = "HSH_APP_SIGN";
public SecurePolicyProperties() {
}
public SecurePolicyProperties(long tokenActiveTime, String salt) {
this.tokenActiveTime = tokenActiveTime;
this.salt = salt;
}
public long getTokenActiveTime() {
return tokenActiveTime;
}
public void setTokenActiveTime(long tokenActiveTime) {
this.tokenActiveTime = tokenActiveTime;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
}
使用方法:
1.通过springboot扫描secure初始化核心类实例,
2.new SecurePolicy()