首先选用EhCache的原因是,他的体量小,引用jar包即可使用,相比于redis来讲使用方便,不需要专门起服务。而我要实现的功能很简单,只是将生成的验证码存储于缓存,五分钟后便将缓存清理,所以对服务器内存的占用并不大。如果是大体量的缓存服务,建议使用redis等缓存技术。
1.maven引入ehcache的jar包
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
2.设置配置
在resource下面新建文件夹ehcacheconfig,并在文件夹中添加ehcache-config.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir/ehcache"/>
<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<!-- 验证码缓存 -->
<cache name="DynamicCodeCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
我的缓存服务名字叫DynamicCodeCache,最大缓存数为1000,缓存最大存活时间为300秒
在application.properties文件中,注入xml配置信息
spring.cache.ehcache.config=classpath:ehcacheconfig/ehcache-config.xml
3.编写实现类
@Service
public class EhCacheServiceImpl implements EhCacheService {
@Autowired
private CacheManager cacheManager;
/**
* 以手机号为key,将验证码存入缓存实例化对象
*/
@Override
public void cacheDynamicCode(String key,String dynamicCode) {
// TODO Auto-generated method stub
Cache cache = cacheManager.getCache("DynamicCodeCache");//获取缓存池
Element element = new Element(key,dynamicCode);//新建缓存元素
cache.put(element);//加入缓存中
Element el = cache.get(key);
System.out.println(el);
System.out.println(el.getObjectValue());
}
/**
* 验证验证码是否正确
*/
@Override
public Map<String, String> verifyDynamicCode(String key, String dynamicCode) {
// TODO Auto-generated method stub
String flag = "1";
String msg = "";
Cache cache = cacheManager.getCache("DynamicCodeCache");//获取缓存池
Element el = cache.get(key);
if(el == null) {
msg = "验证码超时";
}else if(dynamicCode.equals(el.getObjectValue())) {
msg = "验证成功";
flag = "0";
}else {
msg = "验证码错误";
}
Map<String,String> map = new HashMap();
map.put("msg", msg);
map.put("flag", flag);
return map;
}
/**
* 在客户申请重发时,将缓存中以有的老旧验证码删除
*/
@Override
public void removeDynamicCode(String key) {
// TODO Auto-generated method stub
Cache cache = cacheManager.getCache("DynamicCodeCache");//获取缓存池
Element el = cache.get(key);
if(el == null) {
return;
}else {
cache.remove(key);
}
}
}
4.附验证码的生成
@Service
public class OtpServiceImpl implements OtpService {
/**
* 共享密钥
*/
private static final String SECRET_KEY = "2345678910jqkajoker";
/**
* 时间步长 单位:毫秒 作为口令变化的时间周期
*/
private static final long STEP = 1000;
/**
* 转码位数 [1-8]
*/
private static final int CODE_DIGITS = 6;
/**
* 初始化时间
*/
private static final long INITIAL_TIME = 0;
/**
* 柔性时间回溯
*/
private static final long FLEXIBILIT_TIME = 1000;
/**
* 数子量级
*/
private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
private OtpServiceImpl() {
}
/**
* 生成一次性密码
*
* @param code 账户
* @param pass 密码
* @return String
*/
public String generateMyTOTP(String code, String pass) {
if("".equals(code)||"".equals(pass)) {
throw new RuntimeException("账户密码不许为空");
}
long now = new Date().getTime();
String time = Long.toHexString(timeFactor(now)).toUpperCase();
return generateTOTP(code + pass + SECRET_KEY, time);
}
/**
* 刚性口令验证
*
* @param code 账户
* @param pass 密码
* @param totp 待验证的口令
* @return boolean
*/
public boolean verifyTOTPRigidity(String code, String pass, String totp) {
return generateMyTOTP(code, pass).equals(totp);
}
/**
* 柔性口令验证
*
* @param code 账户
* @param pass 密码
* @param totp 待验证的口令
* @return boolean
*/
public static boolean verifyTOTPFlexibility(String code, String pass, String totp) {
long now = new Date().getTime();
String time = Long.toHexString(timeFactor(now)).toUpperCase();
System.out.println(now);
System.out.println(timeFactor(now));
String tempTotp = generateTOTP(code + pass + SECRET_KEY, time);
if (tempTotp.equals(totp)) {
return true;
}
System.out.println(now - FLEXIBILIT_TIME);
System.out.println(timeFactor(now - FLEXIBILIT_TIME));
String time2 = Long.toHexString(timeFactor(now - FLEXIBILIT_TIME)).toUpperCase();
String tempTotp2 = generateTOTP(code + pass + SECRET_KEY, time2);
return tempTotp2.equals(totp);
}
/**
* 获取动态因子
*
* @param targetTime 指定时间
* @return long
*/
private static long timeFactor(long targetTime) {
return (targetTime - INITIAL_TIME) / STEP;
}
/**
* 哈希加密
*
* @param crypto 加密算法
* @param keyBytes 密钥数组
* @param text 加密内容
* @return byte[]
*/
private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
try {
Mac hmac;
hmac = Mac.getInstance(crypto);
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "AES");
hmac.init(macKey);
return hmac.doFinal(text);
} catch (GeneralSecurityException gse) {
throw new UndeclaredThrowableException(gse);
}
}
private static byte[] hexStr2Bytes(String hex) {
byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
byte[] ret = new byte[bArray.length - 1];
System.arraycopy(bArray, 1, ret, 0, ret.length);
return ret;
}
private static String generateTOTP(String key, String time) {
return generateTOTP(key, time, "HmacSHA1");
}
private static String generateTOTP256(String key, String time) {
return generateTOTP(key, time, "HmacSHA256");
}
private static String generateTOTP512(String key, String time) {
return generateTOTP(key, time, "HmacSHA512");
}
private static String generateTOTP(String key, String time, String crypto) {
StringBuilder timeBuilder = new StringBuilder(time);
while (timeBuilder.length() < 16)
timeBuilder.insert(0, "0");
time = timeBuilder.toString();
byte[] msg = hexStr2Bytes(time);
byte[] k = key.getBytes();
byte[] hash = hmac_sha(crypto, k, msg);
return truncate(hash);
}
/**
* 截断函数
*
* @param target 20字节的字符串
* @return String
*/
private static String truncate(byte[] target) {
StringBuilder result;
int offset = target[target.length - 1] & 0xf;
int binary = ((target[offset] & 0x7f) << 24)
| ((target[offset + 1] & 0xff) << 16)
| ((target[offset + 2] & 0xff) << 8) | (target[offset + 3] & 0xff);
int otp = binary % DIGITS_POWER[CODE_DIGITS];
result = new StringBuilder(Integer.toString(otp));
while (result.length() < CODE_DIGITS) {
result.insert(0, "0");
}
return result.toString();
}
}