最近跟着视频学习,学习用到了Guava Cache和token机制,就去百度了下找到一个原文:https://blog.csdn.net/qq_28021299/article/details/80541755 。加了一点点本身的理解。java
功能需求是这样的,在客户端用户登陆时忘记密码,须要找回密码,经过正确输入找回密码答案来访问服务端的修改密码接口。为了防止恶意用户来直接访问修改密码接口,假如没有taken那么就能够修改任意用户密码。(经典的横向越权)算法
,在调用验证答案接口后采用token机制来验证身份,并用Guava Cache作一个定时的token来保证安全性。数据库
理解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:
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;
public class TokenCache {
// 建立logback的logger
private static Logger logger = LoggerFactory.getLogger(TokenCache.class);
// 声明一个静态的内存块,guava里面的本地缓存
private static LoadingCache<String, String> localcache =
//构建本地缓存,调用链的方式 ,1000是设置缓存的初始化容量,maximumSize是设置缓存最大容量,当超过了最大容量,guava将使用LRU算法(最少使用算法),来移除缓存项
//expireAfterAccess(12,TimeUnit.HOURS)设置缓存有效期为12个小时
CacheBuilder.newBuilder().initialCapacity(1000).maximumSize(10000).expireAfterAccess(12,TimeUnit.HOURS)
//build里面要实现一个匿名抽象类
.build(new CacheLoader<String, String>() {
// 这个方法是默认的数据加载实现,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;
}
}cookie
Guava Cache的回收策略
有两种回收策略:session
一种是基于容量的回收CacheBuilder.maximumSize(Long)。设置缓存最大容量,当超过最大容量,缓存将尝试回收最近没有使用或整体上不多使用的缓存项。dom
第二种定时回收ide
expireAfterAccess(long,TimeUnit):缓存项在给定时间内没有被读写访问,则回收。请注意这种缓存的回收顺序和基于大小回收同样。
expireAfterWrite(long,TimeUnit):缓存项在给定时间内没有被写访问(建立或覆盖),则回收。若是认为缓存数据老是在固定时候后变的陈旧不可用,这种回收是可取的。
实例中用的是第一种策略,经过设置缓存最大容量,当超过了最大容量,guava将使用LRU算法来减小缓存项
LRU算法原理
LRU(Least recently used,最近最少使用的)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“若是数据最近被访问过,那么未来被访问的概率也更高。
最多见的实现是使用一个链表保存缓存数据:1 新数据插入到链表头部2 每当缓存命中(即缓存数据被访问),则将数据移到链表头部3 当链表满的时候,将链表尾部的数据丢弃