最近做毕设,学习用到了Guava Cache和token机制,做个总结。
功能需求是这样的,在客户端用户登录时忘记密码,需要找回密码,通过正确输入找回密码答案来访问服务端的修改密码接口。为了防止恶意用户来直接访问修改密码接口,在调用验证答案接口后采用token机制来验证身份,并用Guava Cache做一个定时的token来保证安全性。
什么是token
token的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。当用户第一次登录后,服务器生成一个token并将此token返回给客户端,以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。
身份认证概述
由于HTTP是一种没有状态的协议,它并不知道是谁访问了我们的应用。这里把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下次这个客户端再发送请求时候,还得再验证一下。
通用的解决方法就是,当用户请求登录的时候,如果没有问题,在服务端生成一条记录,在这个记录里可以说明登录的用户是谁,然后把这条记录的id发送给客户端,客户端收到以后把这个id存储在cookie里,下次该用户再次向服务端发送请求的时候,可以带上这个cookie,这样服务端会验证一下cookie里的信息,看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。
以上所描述的过程就是利用session,那个id值就是sessionid。我们需要在服务端存储为用户生成的session,这些session会存储在内存,磁盘,或者数据库。
基于token机制的身份认证
使用token机制的身份验证方法,在服务器端不需要存储用户的登录记录。大概的流程:
客户端使用用户名和密码请求登录。服务端收到请求,验证用户名和密码。验证成功后,服务端会生成一个token,然后把这个token发送给客户端。客户端收到token后把它存储起来,可以放在cookie或者Local Storage(本地存储)里。客户端每次向服务端发送请求的时候都需要带上服务端发给的token。服务端收到请求,然后去验证客户端请求里面带着token,如果验证成功,就向客户端返回请求的数据。
Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或者外部服务器上。简单、强大、及轻量级。它不需要配置文件,使用起来和ConcurrentHashMap一样简单,而且能覆盖绝大多数使用cache的场景需求
校验答案正确后,在service层来生成一个唯一token,一般可以使用mac地址,或者sessionId来生成token。因为token是可以被截获的,非常容易泄露,如果不进行加密,很容易被恶意拷贝并用来登录。所以一般会对token进行加密处理。
一般可以在存储的时候把token进行对称加密存储,用到的时候再解密,或者使用请求URL、时间戳、token三者合并,通过算法进行加密处理。两个一块用更安全。这里就简单利用UUID来实现生成唯一token
String forgetToken = UUID.randomUUID().toString();
然后再把token存到本地Guava Cache内存缓存中,在响应对象中也把这个token封装起来响应给客户端。到时候用户改密,就可以传入该token实现token机制。
TokenCache.setKey("Token_"+username,forgetToken);
Guava Cache实现
package com.lsmall.common;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/*
* @Author Liang Shan
* @Description token本地缓存,使用guava缓存实现
* @Date 2018/6/1 17:28
* @Version 1.0
* */
public class TokenCache {
// 创建logback的logger
private static Logger logger = LoggerFactory.getLogger(TokenCache.class);
// 声明一个静态的内存块,guava里面的本地缓存
private static LoadingCache localcache =
//构建本地缓存,调用链的方式 ,1000是设置缓存的初始化容量,maximumSize是设置缓存最大容量,当超过了最大容量,guava将使用LRU算法(最少使用算法),来移除缓存项
//expireAfterAccess(12,TimeUnit.HOURS)设置缓存有效期为12个小时
CacheBuilder.newBuilder().initialCapacity(1000).maximumSize(10000).expireAfterAccess(12,TimeUnit.HOURS)
//build里面要实现一个匿名抽象类
.build(new CacheLoader() {
// 这个方法是默认的数据加载实现,get的时候,如果key没有对应的值,就调用这个方法进行加载
@Override
public String load(String s) throws Exception {
// 为什么要把return的null值写成字符串,因为到时候用null去.equal的时候,会报空指针异常
return "null";
}
});
/*
* 添加本地缓存
* */
public static void setKey(String key, String value) {
localcache.put(key, value);
}
/*
* 得到本地缓存
* */
public static String getKey(String key) {
String value = null;
try {
value= localcache.get(key);
if ("null".equals(value)) {
return null;
}
return value;
} catch (ExecutionException e) {
logger.error("getKey()方法错误",e);
}
return null;
}
}
Guava Cache的回收策略
有两种回收策略:
一种是基于容量的回收CacheBuilder.maximumSize(Long)。设置缓存最大容量,当超过最大容量,缓存将尝试回收最近没有使用或总体上很少使用的缓存项。
第二种定时回收
expireAfterAccess(long,TimeUnit):缓存项在给定时间内没有被读写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
expireAfterWrite(long,TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变的陈旧不可用,这种回收是可取的。
实例中用的是第一种策略,通过设置缓存最大容量,当超过了最大容量,guava将使用LRU算法来减少缓存项
LRU算法原理
LRU(Least recently used,最近最少使用的)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高。
最常见的实现是使用一个链表保存缓存数据:
1 新数据插入到链表头部
2 每当缓存命中(即缓存数据被访问),则将数据移到链表头部
3 当链表满的时候,将链表尾部的数据丢弃