目录
一:记住我功能的Token持久化
1.新建数据表结构
2.使用JdbcTemplate来存储生成的Token
二:记住我功能的源码解析
1.首先来看一下PersistentTokenRepository接口
2.PersistentTokenRepository的实现
3.1PersistentTokenRepository实现之一InMemoryTokenRepositoryImpl
(1)定义一个内存存储的Map
(2)定义一个根据主键查询Tokne的同步方法
(3)创建Token
(4)更新Token
(5)删除Token
3.2PersistentTokenRepository实现之二JdbcTokenRepositoryImpl
三:自定义实现记住我功能的持久化
1.为什么要自定义持久化
2.如何持久化
3.实现自定义持久化
(1)定义好RedisConfig并且完成工具类
(2)自定义MyPersistentRememberMeToken
(3)自定义myPersistentTokenRepository实现PersistentTokenRepository接口
在(一)中,我们将“记住我功能”放入数据库,进行数据持久化。主要步骤如下,我们看一下具体的:
这个接口定义的功能很明了,Token的Crud
这个接口有2个实现类,如图所示:
这个是Token的默认的存储方式,使用了内存的方式存储。我们来看一下结构:
说明一下,这里为什么要在方法加锁呢?保证同时只能有一个线程读取。
public synchronized PersistentRememberMeToken getTokenForSeries(String seriesId) {
return seriesTokens.get(seriesId);
}
public synchronized void createNewToken(PersistentRememberMeToken token) {
PersistentRememberMeToken current = seriesTokens.get(token.getSeries());
if (current != null) {
throw new DataIntegrityViolationException("Series Id '" + token.getSeries()
+ "' already exists!");
}
seriesTokens.put(token.getSeries(), token);
}
来看一下这个PersistenToken中有哪些属性:
public synchronized void updateToken(String series, String tokenValue, Date lastUsed) {
PersistentRememberMeToken token = getTokenForSeries(series);
PersistentRememberMeToken newToken = new PersistentRememberMeToken(
token.getUsername(), series, tokenValue, new Date());
// Store it, overwriting the existing one.
seriesTokens.put(series, newToken);
}
代码还是很简单的啊
public synchronized void removeUserTokens(String username) {
Iterator series = seriesTokens.keySet().iterator();
while (series.hasNext()) {
String seriesId = series.next();
PersistentRememberMeToken token = seriesTokens.get(seriesId);
if (username.equals(token.getUsername())) {
series.remove();
}
}
}
其实这个实现对于上一步5个同步的方法还是一样的思路,主要区别就是这个实现基于数据库实现了
以及CRUD的sql:
首先来说明一下,为甚要实现自定义的持久化。我们基于上面的2中存储方法来分析:
第一种:内存Map的存储
(1)这种方法只支持本地存储,一旦集群或者分布就蒙了
(2)使用内存,一旦用户量大了,内存极有可能就GG了
第二种:数据库的存储
(1)从数据库存取,太占用资源,包括但不限于内存,线程等
(2)响应时间太久,毕竟是从数据库存取,一旦数据量大,就会导致读取速度变慢
综合上面的,我们需要考虑到2点:需要支持集群以及合理的GC策略。
本节我们选用Ehcache来做持久化,主要当前只选用了这个缓存框架,实际项目中都是使用Redis的。
因为使用了缓存,实则同Map存储方式是一致的。我们参照第一种存储方式来写
详情见:https://pan.baidu.com/s/1lKRqgeRcueJoIQ9gEwUSWA
定义好MyPersistentRememberMeToken,结构同PersistentRememberMeToken,主要是为了处理PersistentRememberMeToken这个实体类无法用redisTelepate无法反序列化的问题。
package com.config.Seurity.model;
import java.io.Serializable;
import java.util.Date;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
*
* ClassName:實現PersistentRememberMeToken反序列化
* Reason: TODO ADD REASON.
* Date: 2019年9月3日 上午11:25:52
* @author Owner
* @version
* @since JDK 1.8
* @see
*/
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class MyPersistentRememberMeToken implements Serializable{
/**
* serialVersionUID:TODO(用一句话描述这个变量表示什么).
* @since JDK 1.8
*/
private static final long serialVersionUID = 1L;
private String username;
private String series;
private String tokenValue;
private Date date;
public PersistentRememberMeToken myToEntity(MyPersistentRememberMeToken myPersistentRememberMeToken) {
if(myPersistentRememberMeToken == null ) {
return null;
}
return new PersistentRememberMeToken(
myPersistentRememberMeToken.getUsername(),
myPersistentRememberMeToken.getSeries(),
myPersistentRememberMeToken.getTokenValue(),
myPersistentRememberMeToken.getDate());
}
public MyPersistentRememberMeToken(PersistentRememberMeToken persistentRememberMeToken) {
this.username=persistentRememberMeToken.getUsername();
this.series=persistentRememberMeToken.getSeries();
this.tokenValue=persistentRememberMeToken.getTokenValue();
this.date=persistentRememberMeToken.getDate();
}
}
package com.config.Seurity.repository;
import java.util.Date;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.config.Seurity.model.MyPersistentRememberMeToken;
import com.config.redis.redisService;
import commons.json.JSONObject;
/**
*
* 双token存取方案说明:
* (1)在InMemoryTokenRepositoryImpl中,实际上是以series为存储的Key,
* (2)在Remove()中,遍历所有的Map,然后找出userName的Token,在更具找出Token的series来删除的
* (3)上述方法遍历所有的Map,代价太大了。
* 双Token实现说明
* (1)以series的存储为主同步方法
* (2)以Username的存取的次异步方法
*
*
* ClassName:自定义Token存储
* Reason: 注意,这里所有的方法都用了锁,实际上入股哦使用redis做存储,是不需要的.
* Date: 2019年9月2日 上午10:56:30
* @author Owner
* @version
* @since JDK 1.8
* @see
*/
@Component
public class myPersistentTokenRepository implements PersistentTokenRepository{
@Resource
private redisService redisService;
//series作为Key的前缀
private static final String seriesKeyPre="series";
//userName作为Key的前缀
private static final String userNameKeyPre="userName";
private static final Long timeout=30L;
//=====工具方法的封装
/**
* 主同步Token的存储
* @param key
* @param t
* @author Owner
* @time:2019年9月2日下午12:13:18
* @since JDK 1.8
*/
private synchronized void putCache(PersistentRememberMeToken token) {
MyPersistentRememberMeToken myToken=new MyPersistentRememberMeToken(token);
redisService.set(seriesKeyPre+myToken.getSeries() ,myToken ,timeout);
}
/**
* 次异步Token的存储
* @param token
* @author Owner
* @time:2019年9月2日下午12:29:43
* @since JDK 1.8
*/
@Async
private void putUserToken(PersistentRememberMeToken token) {
MyPersistentRememberMeToken myToken=new MyPersistentRememberMeToken(token);
redisService.set(userNameKeyPre+myToken.getUsername() ,myToken,timeout );
}
/**
* 从缓存中获取userName
* @param userName
* @return
* @author Owner
* @time:2019年9月2日下午12:51:23
* @since JDK 1.8
*/
public MyPersistentRememberMeToken getTokenForUserName(String userName) {
return redisService.get(userNameKeyPre+userName);
}
public void removeTokenByUserName(String userName) {
MyPersistentRememberMeToken myToken=redisService.get(userNameKeyPre+userName);
redisService.remove(userNameKeyPre+userName);
redisService.remove(seriesKeyPre+myToken.getSeries());
}
//主要实现的方法
/**
*
* TODO:从缓存中获取series
* @see org.springframework.security.web.authentication.rememberme.PersistentTokenRepository#getTokenForSeries(java.lang.String)
*/
@Override
public synchronized PersistentRememberMeToken getTokenForSeries(String seriesId) {
MyPersistentRememberMeToken myToken= redisService.get(seriesKeyPre+seriesId);
return myToken == null ? null :myToken.myToEntity(myToken);
}
/**
*
* TODO 存储Token.
* @see org.springframework.security.web.authentication.rememberme.PersistentTokenRepository#createNewToken(org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken)
*/
@Override
public synchronized void createNewToken(PersistentRememberMeToken token) {
PersistentRememberMeToken current = (PersistentRememberMeToken) getTokenForSeries(token.getSeries());
if (current != null) {
throw new DataIntegrityViolationException("Series Id '" + token.getSeries()
+ "' already exists!");
}
putCache(token);
putUserToken(token);
}
@Override
public void updateToken(String series, String tokenValue, Date lastUsed) {
PersistentRememberMeToken token = getTokenForSeries(series);
PersistentRememberMeToken newToken = new PersistentRememberMeToken(
token.getUsername(), series, tokenValue, new Date());
putCache(newToken);
putUserToken(token);
}
@Override
public void removeUserTokens(String username) {
removeTokenByUserName(username);
}
}