redis+cookie+json+filter实现单点登录
目录:
1.项目集成redis客户端jedis
引入Jedis pom
2.redis连接池构建及调试
1)JedisPoolConfig源码解析
2)JedisPool源码解析
3)JedisPool回收资源
4)封装redisPool
public class RedisPool {
//声明成static的原因:保证jedis连接池在tomcat启动时就加载出来
//jedis连接池
private static JedisPool pool;
//与redis连接池连接的最大连接数
private static Integer maxTotal = Integer.parseInt(PropertiesUtil.getProperty("redis.max.total", "20"));
//在这个连接池中最多有多少个状态为idle的jedis实例,jedis连接池里就是jedis的实例,idle就是空闲的jedis实例
//在jedis连接池中最大的idle状态(空闲的)的jedis实例的个数
private static Integer maxIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle", "10"));
//在jedis连接池中最小的idle状态(空闲的)的jedis实例的个数
private static Integer minIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle", "2"));
//在borrow一个jedis实例的时候,是否要进行验证操作,如果赋值为true,则得到的jedis实例肯定是可用的
private static Boolean testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow", "true"));
//在return一个jedis实例的时候,是否要进行验证操作,如果赋值为true,则返回jedis连接池的jedis实例肯定是可用的
private static Boolean testOnReturn = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return", "true"));
private static String redisIp = PropertiesUtil.getProperty("redis.ip");
private static Integer redisPort = Integer.parseInt(PropertiesUtil.getProperty("redis.port"));
//初始化连接池,只会调用一次
private static void initPool() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnReturn(testOnReturn);
//连接池耗尽的时候,是否阻塞,false会抛出异常,true阻塞直到超时,会抛出超时异常,默认为true
config.setBlockWhenExhausted(true);
//这里超时时间是2s
pool = new JedisPool(config, redisIp, redisPort, 1000*2);
}
static {
initPool();
}
//从连接池中拿取一个实例
public static Jedis getJedis() {
return pool.getResource();
}
//将正常实例放回jedis连接池
public static void returnResource(Jedis jedis) {
pool.returnResource(jedis);
}
//将破损实例放回jedis连接池
public static void returnBrokenResource(Jedis jedis) {
pool.returnResource(jedis);
}
//测试是否与redis-server正常连接上
// public static void main(String[] args) {
// Jedis jedis = pool.getResource();
// jedis.set("kkk", "ddd");
// returnResource(jedis);
// pool.destroy();
// System.out.println("end");
// }
}
3.jedis API封装及调试
封装RedisPoolUtil
@Slf4j
public class RedisPoolUtil { //重新设置有效期
//参数只有key和有效期,因为只需要根据key设置有效期即可
public static Long expire(String key, int exTime) {
Jedis jedis = null;
Long result = null;
try {
jedis = RedisPool.getJedis();
//设置有效期
result = jedis.expire(key, exTime);
} catch (Exception e) {
log.error("setex key:{} error", key, e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
} //exTime单位是s,设置session有效时间
//当用户初次登录的时候,需要设置有限期,存在redis session中
//后续如果用户再次请求登录,则只需要调用expire,重新设置有效期即可
public static String setEx(String key, String value, int exTime) {
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.setex(key, exTime, value);
} catch (Exception e) {
log.error("setex key:{} value:{} error", key, value, e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
} public static String set(String key, String value) {
Jedis jedis = null;
//jedis返回的结果
String result = null;
try {
jedis = RedisPool.getJedis();
//设置key-value
result = jedis.set(key, value);
} catch (Exception e) {
log.error("set key:{} value:{} error", key, value, e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
} public static String get(String key) {
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
//根据key获取value值
result = jedis.get(key);
} catch (Exception e) {
log.error("set key:{} error", key, e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
} public static Long del(String key) {
Jedis jedis = null;
Long result = null;
try {
jedis = RedisPool.getJedis();
//根据key删除key-value
result = jedis.del(key);
} catch (Exception e) {
log.error("set key:{} error", key, e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
} //单步调试上面的各个方法
// public static void main(String[] args) {
// Jedis jedis = RedisPool.getJedis();
// RedisPoolUtil.set("keyTest", "value");
// String value = RedisPoolUtil.get("keyTest");
// RedisPoolUtil.setEx("keyex", "valueex", 60*10);
// RedisPoolUtil.expire("keyTest", 60*20);
// RedisPoolUtil.del("keyTest");
// System.out.println("end");
// }
}
4.json封装jsonUtil及调试
1)封装及调试
2)多泛型(一个对象里面有多个泛型,例如map里面的key-value,value里面是一个set或list,里面又是一个泛型)序列化和反序列化
public class JsonUtil {
private static ObjectMapper objectMapper = new ObjectMapper();
static {
//序列化
//对象的所有字段全部列入
objectMapper.setSerializationInclusion(Inclusion.ALWAYS);
//取消默认转换timestamps形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
//忽略空Bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
//所有的日期格式都统一为以下的样式,即yyyy-MM-dd HH:mm:ss
objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
//反序列化
//忽略在json字符串中存在,但在java对象中不存在对应属性的情况,防止错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
//对象转json字符串
public static <T> String obj2String(T obj) {
if(obj == null) {
return null;
}
try {
return obj instanceof String ? (String)obj : objectMapper.writeValueAsString(obj);
} catch (Exception e) {
log.warn("Parse object to String error", e);
return null;
}
}
//返回格式化好的json字符串
public static <T> String obj2StringPretty(T obj) {
if(obj == null) {
return null;
}
try {
return obj instanceof String ? (String)obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (Exception e) {
log.warn("Parse Object to String error", e);
return null;
}
}
//json字符串转对象
//只能针对一个对象的情况,如果是list,就不适用
public static <T> T string2Obj(String str, Class<T> clazz) {
if(StringUtils.isEmpty(str) || clazz == null) {
return null;
}
try {
return clazz.equals(String.class) ? (T)str : objectMapper.readValue(str, clazz);
} catch (Exception e) {
log.warn("Parse String to Object error", e);
return null;
}
}
//可以针对list的情况,只要第二个参数传入new TypeReference<List<User>>就可以
public static <T> T string2Obj(String str, TypeReference<T> typeReference) {
if(StringUtils.isEmpty(str) || typeReference == null) {
return null;
}
try {
return (T)(typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
} catch (Exception e) {
log.warn("Parse String to Object error", e);
return null;
}
}
//可以针对List的情况,只要传入List.class和User.class就可以了
public static <T> T string2Obj(String str,Class<?> collectionClass,Class<?>... elementClasses){
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses);
try {
return objectMapper.readValue(str,javaType);
} catch (Exception e) {
log.warn("Parse String to Object error",e);
return null;
}
}
/* public static void main(String[] args) {
User u1 = new User();
u1.setId(1);
u1.setEmail("kkk@163.com");
User u2 = new User();
u2.setId(2);
u2.setEmail("iii@163.com");
String user1Json = JsonUtil.obj2String(u1);
String user1JsonPretty = JsonUtil.obj2StringPretty(u1);
log.info("user1Json:{}", user1Json);
log.info("user1JsonPretty:{}", user1JsonPretty);
User user = JsonUtil.string2Obj(user1Json, User.class);
List<User> userList = Lists.newArrayList();
userList.add(u1);
userList.add(u2);
String userListStr = JsonUtil.obj2StringPretty(userList);
log.info("------------------");
log.info(userListStr);
// List<User> userListObj = JsonUtil.string2Obj(userListStr, List.class);
// List<User> userListObj = JsonUtil.string2Obj(userListStr, new TypeReference<List<User>>() {
// });
List<User> userListObj = JsonUtil.string2Obj(userListStr, List.class, User.class);
System.out.println("end");
}*/
}
5.json ObjectMapper源码解析
1)Inclusion.ALWAYS
2)Inclusion.NON_NULL
3)Inclusion.NON_DEFAULT
4)Inclusion.NON_EMPTY
5)SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPSInclusion.NON_NULL
6)SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS
7)DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES
8)ObjectMapper DateFormat
6.cookie封装及使用
1)写cookie
2)读cookie
3)删cookie
4)domain
5)path
6)maxAge
7)httponly
public class CookieUtil {
//将这个cookie放在一级域名下,则二级域名www.mall.com、user.mall.com等都可以访问到这个cookie,而同级域名是访问不到的
private final static String COOKIE_DOMAIN = ".mall.com";
//这个名字会由服务端种到客户端的浏览器上,
private final static String COOKIE_NAME = "mall_login_token";
//获取cookie
public static String readLoginToken(HttpServletRequest request) {
Cookie[] cks = request.getCookies();
if(cks != null) {
for(Cookie ck : cks) {
log.info("cookieName:{}, cookieValue:{}", ck.getName(), ck.getValue());
if(StringUtils.equals(ck.getName(), COOKIE_NAME)) {
log.info("return cookie_name:{}, cookie_value:{}", ck.getName(), ck.getValue());
return ck.getValue();
}
}
}
return null;
}
//X:domain=".mall.com",a,b,c,d,e都能拿到这个cookie
//a,b不能看到彼此的cookie
//a:A.mall.com cookie:domain = A.mall.com;path = "/"
//b:B.mall.com cookie:domain = B.mall.com;path = "/"
//c,d能共享a的cookie,因为domain相同;c,d也能共享e的cookie,因为domain和path
//c,d不能看到彼此的cookie,也不能看到b的cookie
//c:A.mall.com/test/cc cookie:domain = A.mall.com;path = "/test/cc"
//d:A.mall.com/test/dd cookie:domain = A.mall.com;path = "/test/dd"
//e:A.mall.com/test cookie:domain = A.mall.com;path = "/test"
//登录时,写入cookie,这个token就是sessionId
public static void writeLoginToken(HttpServletResponse response, String token) {
Cookie ck = new Cookie(COOKIE_NAME, token);
ck.setDomain(COOKIE_DOMAIN);
//"/"代表设置在根目录,
ck.setPath("/");
//禁止通过脚本访问cookie,可以防止脚本攻击泄露信息
ck.setHttpOnly(true);
//如果是-1,代表永久,单位是s;如果不设置这个变量,则cookie不会写入硬盘,而只是写在内存,值在当前页面有效
ck.setMaxAge(60 * 60 * 24 * 365);
log.info("write cookie_name:{}, cookie_value:{}", ck.getName(), ck.getValue());
response.addCookie(ck);
}
//注销时删除cookie
public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cks = request.getCookies();
if(cks != null) {
for(Cookie ck : cks) {
if(StringUtils.equals(ck.getName(), COOKIE_NAME)) {
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");
//设置成0,代表删除此cookie
ck.setMaxAge(0);
log.info("del cookie_name:{}, cookie_value:{}", ck.getName(), ck.getValue());
response.addCookie(ck);
return;
}
}
}
}
}
7.SessionExpireFilter重置session有效期
由于用户在第一次登录的时候,会设置session有效期,为了session不过期,在用户每一次请求页面数据的时候,就重置一下session有效期。使用过滤器实现。
Filter类:
public class SessionExpireFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isNotEmpty(loginToken)) {
//判断loginToken是否为空或者""
//如果不为空的话,符合条件,继续拿user信息
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User user = JsonUtil.string2Obj(userJsonStr, User.class);
if(user != null) {
//如果user不为空,则重置session的时间,即调用expire命令
RedisShardedPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
web.xml配置过滤器:
<filter>
<filter-name>sessionExpireFilter</filter-name>
<filter-class>com.mall.controller.common.SessionExpireFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sessionExpireFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
8.用户session相关模块重构
9.Guava cache迁移redis缓存
public class JsonUtil {
private static ObjectMapper objectMapper = new ObjectMapper();
static {
//序列化
//对象的所有字段全部列入
objectMapper.setSerializationInclusion(Inclusion.ALWAYS);
//取消默认转换timestamps形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
//忽略空Bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
//所有的日期格式都统一为以下的样式,即yyyy-MM-dd HH:mm:ss
objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
//反序列化
//忽略在json字符串中存在,但在java对象中不存在对应属性的情况,防止错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
//对象转json字符串
public static <T> String obj2String(T obj) {
if(obj == null) {
return null;
}
try {
return obj instanceof String ? (String)obj : objectMapper.writeValueAsString(obj);
} catch (Exception e) {
log.warn("Parse object to String error", e);
return null;
}
}
//返回格式化好的json字符串
public static <T> String obj2StringPretty(T obj) {
if(obj == null) {
return null;
}
try {
return obj instanceof String ? (String)obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (Exception e) {
log.warn("Parse Object to String error", e);
return null;
}
}
//json字符串转对象
//只能针对一个对象的情况,如果是list,就不适用
public static <T> T string2Obj(String str, Class<T> clazz) {
if(StringUtils.isEmpty(str) || clazz == null) {
return null;
}
try {
return clazz.equals(String.class) ? (T)str : objectMapper.readValue(str, clazz);
} catch (Exception e) {
log.warn("Parse String to Object error", e);
return null;
}
}
//可以针对list的情况,只要第二个参数传入new TypeReference<List<User>>就可以
public static <T> T string2Obj(String str, TypeReference<T> typeReference) {
if(StringUtils.isEmpty(str) || typeReference == null) {
return null;
}
try {
return (T)(typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference));
} catch (Exception e) {
log.warn("Parse String to Object error", e);
return null;
}
}
//可以针对List的情况,只要传入List.class和User.class就可以了
public static <T> T string2Obj(String str,Class<?> collectionClass,Class<?>... elementClasses){
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses);
try {
return objectMapper.readValue(str,javaType);
} catch (Exception e) {
log.warn("Parse String to Object error",e);
return null;
}
}
/* public static void main(String[] args) {
User u1 = new User();
u1.setId(1);
u1.setEmail("kkk@163.com");
User u2 = new User();
u2.setId(2);
u2.setEmail("iii@163.com");
String user1Json = JsonUtil.obj2String(u1);
String user1JsonPretty = JsonUtil.obj2StringPretty(u1);
log.info("user1Json:{}", user1Json);
log.info("user1JsonPretty:{}", user1JsonPretty);
User user = JsonUtil.string2Obj(user1Json, User.class);
List<User> userList = Lists.newArrayList();
userList.add(u1);
userList.add(u2);
String userListStr = JsonUtil.obj2StringPretty(userList);
log.info("------------------");
log.info(userListStr);
// List<User> userListObj = JsonUtil.string2Obj(userListStr, List.class);
// List<User> userListObj = JsonUtil.string2Obj(userListStr, new TypeReference<List<User>>() {
// });
List<User> userListObj = JsonUtil.string2Obj(userListStr, List.class, User.class);
System.out.println("end");
}*/
}
redis+cookie+json+filter实现单点登录的更多相关文章
- .NET Core2.0+MVC 用Redis/Memory+cookie实现的sso单点登录
之前发布过使用session+cookie实现的单点登录,博主个人用的很不舒服,为什么呢,博主自己测试的时候,通过修改host的方法,在本机发布了三个站点,但是,经过测试,发现,三个站点使用的sess ...
- Asp.Net Core基于Cookie实现同域单点登录(SSO)
在同一个域名下有很多子系统 如:a.giant.com b.giant.com c.giant.com等 但是这些系统都是giant.com这个子域. 这样的情况就可以在不引用其它框架的情况下, ...
- 基于SpringBoot+Redis的Session共享与单点登录
title: 基于SpringBoot+Redis的Session共享与单点登录 date: 2019-07-23 02:55:52 categories: 架构 author: mrzhou tag ...
- NET Core 2.0使用Cookie认证实现SSO单点登录
NET Core 2.0使用Cookie认证实现SSO单点登录 之前写了一个使用ASP.NET MVC实现SSO登录的Demo,https://github.com/bidianqing/SSO.Sa ...
- 前端分享之cookie的使用及单点登录
cookie是什么 cookie的英文意思是饼干.在计算机术语中指服务端存放在客户端的一段数据.这段数据在客户端每次进行http请求时会自动加在http请求报文中的header上:服务端在响应时,可以 ...
- 基于Cookie跨域的单点登录问题
由于项目中,需要用的单点登录,主要的思路是:系统1:用户名密码-->写入Cookie-->其他系统读取Cookie. 1.在同一个服务器下的Cookie共享 @Component(&quo ...
- .NET Core2.0+MVC 用session,cookie实现的sso单点登录
博主刚接触.NET Core2.0,想做一个单点登录的demo,所以参考了一些资料,这里给上链接: 1.http://www.cnblogs.com/baibaomen/p/sso-sequence- ...
- CORS跨域、Cookie传递SessionID实现单点登录后的权限认证的移动端兼容性测试报告
简述 本文仅记录如标题所述场景的测试所得,由于场景有些特殊,且并不需兼容所有浏览器,所以本文的内容对读者也许并无作用,仅为记录. 场景.与实现 需在移动端单点登录 需在移动端跨域访问我们的服务 基于历 ...
- 使用Cookie实现跨域单点登录的原理
对于构建分布式系统来说业务功能的物理部署会随着新业务模块的增加而增加或改变物理部署的位置.而每个用户都有统一的帐号作为我们登录系统时的一个认证.当新业务或子系统部署在不同的物理机上,我们去访问不同的业 ...
随机推荐
- HUST1017-Exact Cover
给出一个\(n\times m\)的01矩阵,每行最多有\(c\)个1,求一个精确覆盖,即选出一些行使得每列有有且仅有一个1.输出方案. 分析 被这个题坑到了啊!!第一次上HUSTOJ做题,不知道没有 ...
- bzoj2383[CEOI2011] ballons
题意 在一条数轴上从左向右有一些气球,每个气球一开始位于横坐标xi的位置,是半径为0的圆.现在开始从左向右给每个气球充气.被充气的气球的半径会不断变大,直到达到这个气球的半径上限Ri或者这个气球和之前 ...
- 详解SQL Server数据修复命令DBCC的使用
严重级别为 21 表示可能存在数据损坏. 可能的原因包括损坏的页链.损坏的 IAM 或该对象的 sys.objects目录视图中存在无效条目. 这些错误通常由硬件或磁盘设备驱动程序故障而引起. MS ...
- Contest 3
A:非常裸的dp. #include<iostream> #include<cstdio> #include<cstdlib> #include<cstrin ...
- 转:python的nltk中文使用和学习资料汇总帮你入门提高
python的nltk中文使用和学习资料汇总帮你入门提高 转:http://blog.csdn.net/huyoo/article/details/12188573 nltk的安装 nltk初步使用入 ...
- py2exe使用总结
假如你用python写了个小程序,想给别人用或者给别人演示,但他电脑里没装python.wxpython等,这时候你可以试试py2exe,它是一个将python脚本转换成windows上的可执行程序( ...
- list的4种遍历方式
import java.util.ArrayList;import java.util.Iterator;import java.util.List; import com.hbut.domain.P ...
- 制作VR视频播放器
最近VR火的不要不要的,但是综合起来,VR资源最多的还是全景图片和全景视频,今天在这里给大家简单介绍一下如何用Unity制作简单的VR视频播放器. 首先找到EasyMovieTexture这个插件,A ...
- listen() 函数
声明:本文来自网络博文的合并,文后有链接. 一.listen函数仅由TCP服务器调用 它做两件事: 1.当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用conne ...
- Linux之选取信息命令介绍与应用20170331
在介绍选取信息命令之前,说一下管道符“|”与tr命令,因为在使用多个命令一起的时候经常用到它. 一.利用Linux所提供的管道符“|”将两个命令隔开,管道符左边命令的输出就会作为管道符右边命令的输入. ...