Redis+Cookie+Jackson+Filter实现单点登录

1.Redis连接池构建

首先maven导入依赖包

    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>2.6.0</version>
    </dependency>

下面正式写一个连接池

public class RedisPool {
    private static JedisPool pool;//jedis连接池
    private static Integer maxTotal =  Integer.valueOf(PropertiesUtil.getProperty("redis.max.total","20"));   //最大连接数
    private static Integer maxIdle = Integer.valueOf(PropertiesUtil.getProperty("redis.max.idle","10"));//在jedispool中最大的idle状态(空闲的)的jedis实例的个数
    private static Integer minIdle = Integer.valueOf(PropertiesUtil.getProperty("redis.min.idle","2"));//在jedispool中最小的idle状态(空闲的)的jedis实例的个数
    private static Boolean testOnBorrow =  Boolean.valueOf(PropertiesUtil.getProperty("redis.test.borrow","true"));//在borrow一个Jedis实例的时候,是否要进行验证操作,如果赋值true,那么得到的jedis实例肯定是可用的
    private static Boolean testOnReturn =  Boolean.valueOf(PropertiesUtil.getProperty("redis.test.return","true"));//在borrow一个Jedis实例的时候,是否要进行验证操作,如果赋值true,则放回jedispool的jedis实例肯定是可用的

    private static String redisIp = PropertiesUtil.getProperty("redis.ip");//在jedispool中最大的idle状态(空闲的)的jedis实例的个数
    private static Integer redisPort = Integer.valueOf(PropertiesUtil.getProperty("redis.port"));//在jedispool中最小的idle状态(空闲的)的jedis实例的个数

    private static void initPool(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setTestOnBorrow(testOnBorrow);
        config.setTestOnReturn(testOnReturn);

        config.setBlockWhenExhausted(true);//连接耗尽的时候是否阻塞,true:阻塞,false:抛出异常

        pool = new JedisPool(config,redisIp,redisPort,1000*2);
    }

    static {
        initPool();
    }

    public static Jedis getJedis(){
        return pool.getResource();
    }

    public static void returnBrokenResource(Jedis jedis){
            pool.returnBrokenResource(jedis);
    }

    public static void returnResource(Jedis jedis){
            pool.returnResource(jedis);
    }

    public static void main (String[] args){
        //Jedis jedis = pool.getResource();
        Jedis jedis = RedisPool.getJedis();
        jedis.set("key","value");
        RedisPool.returnResource(jedis);
        pool.destroy();//临时调用
        System.out.println("program is end");
    }

}

代码内的PropertiesUtil是一个读取properties的工具类

public class PropertiesUtil {

    private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);

    private static Properties props;

    static {
        String fileName = "zfb.properties";
        props = new Properties();
        try {
            props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));
        } catch (IOException e) {
            logger.error("配置文件读取异常",e);
        }
    }

    public static String getProperty(String key){
        String value = props.getProperty(key.trim());
        if(StringUtils.isBlank(value)){
            return null;
        }
        return value.trim();
    }

    public static String getProperty(String key,String defaultValue){

        String value = props.getProperty(key.trim());
        if(StringUtils.isBlank(value)){
            value = defaultValue;
        }
        return value.trim();
    }



}

与redis的连接搞定了,那接下来就要封装一下操作redis的方法了

2.Jedis API封装

@Slf4j
public class RedisPoolUtil {

    /*设置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("expire key:{} exTime:{}  error",key,exTime,e);
            RedisPool.returnBrokenResource(jedis);
            return result;
        }
        RedisPool.returnResource(jedis);
        return  result;

    }


    //exTime的单位是秒
    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:{} exTime:{} value:{} error",key,exTime,value,e);
            RedisPool.returnBrokenResource(jedis);
            return result;
        }
        RedisPool.returnResource(jedis);
        return  result;

    }

    public static String set(String key,String value){
        Jedis jedis = null;
        String result = null;

        try {
            jedis = RedisPool.getJedis();
            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();
            result = jedis.get(key);
        } catch (Exception e) {
            log.error("get 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();
            result = jedis.del(key);
        } catch (Exception e) {
            log.error("del key:{} error",key,e);
            RedisPool.returnBrokenResource(jedis);
            return result;
        }
        RedisPool.returnResource(jedis);
        return  result;

    }

    public  static  void main (String[] args){
        Jedis jedis = RedisPool.getJedis();
        RedisShardedPoolUtil.set("keyTest","value");
        String value = RedisShardedPoolUtil.get("keyTest");
        RedisShardedPoolUtil.setEx("kevex","valueex",60*10);
        RedisShardedPoolUtil.expire("keyTest",60*20);
        RedisShardedPoolUtil.del("keyTest");
        System.out.println("end");
    }




}

类里面封装了一些常用的操作redis的方法,set(),设置键值;del(),删除键值;setEx(),set的时候就把生存时间设置了等等

3.JSONUtil封装

RedisPoolUtil 工具类是对String操作的,但是我们在登录的时候,用户User是一个对象,里面有具体的参数,那么我们就需要把这个对象User序列化,然后存储到redis里面

    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-mapper-asl</artifactId>
      <version>1.9.12</version>
    </dependency>
public class JsonUtil {
    private static ObjectMapper objectMapper = new ObjectMapper();
    static {
        //对象的所有字段全部列入
        objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
        //取消默认转换timestamps形式
        objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_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);

    }

    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;
            }
    }

    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;
        }
    }


    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 (IOException e) {
            log.warn("Parse String to Object error",e);
            return  null;
        }
    }

    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 (IOException e) {
            log.warn("Parse String to Object error",e);
            return  null;
        }
    }

    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 (IOException 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("[email protected]");

        User u2 = new User();
        u2.setId(2);
        u2.setEmail("[email protected]");

        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> userListObj1 = JsonUtil.string2Obj(userListStr, new TypeReference<List<User>>() {
        });

        List<User> userListObj2 = JsonUtil.string2Obj(userListStr,List.class,User.class);

        System.out.println("end");
    }


}

准备工作做完以后,就是使用redis来存储session了

4.单点登录Redis存储Session

以下代码是以单机多部署两个tomcat集群为基础的,如果只是一个tomcat,那么执行代码

  RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(serverResponse.getData()),Const.RedisCacheExtime.REDIS_SESSION_EXTIME);

浏览器端cookie的value值和redis存储的key值肯定是一样的。

如果是两个tomcat,使用tomcat1登录,那么Cookie有了值,且保存到redis上了,再次发起请求,负载均衡到tomcat2上了,那么这个时候session.getId()得到的值在redis上是没有这个key,就会认为用户没有登录

    @RequestMapping(value = "login.do",method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse){
        ServerResponse<User> serverResponse = iUserService.login(username,password);
        if (serverResponse.isSuccess()){
           // session.setAttribute(Const.CURRENT_USER,serverResponse.getData());
            RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(serverResponse.getData()),Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
        }
        return serverResponse;
    }

那么我们就写一个Cookie的工具类

@Slf4j
public class CookieUtil {

    private final static String COOKIE_DOMAIN = ".XXX.com";
    private final static String COOKIE_NAME = "study_login_token";


    public static String readLoginToken(HttpServletRequest request){
        Cookie[] cks = request.getCookies();
        if (cks != null){
            for (Cookie ck : cks){
                log.info("read cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
                if (StringUtils.equals(ck.getName(),COOKIE_NAME)){
                    log.info("read cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
                    return ck.getValue();
                }
            }
        }
        return null;
    }





    public static void writeLoginToken(HttpServletResponse response,String token){
        Cookie ck = new Cookie(COOKIE_NAME,token);
        ck.setDomain(COOKIE_DOMAIN);
        ck.setPath("/");//代表设置在根目录下
        ck.setHttpOnly(true);
        //单位是秒。
        //如果这个maxage不设置的话,cookie就不会写入硬盘,而是写在内存。只在当前页面有效
        ck.setMaxAge(60*60*24*365);//如果是-1,代表永久
        log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
        response.addCookie(ck);
    }

    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("/");
                    ck.setMaxAge(0);//设置成0,代表删除此cookie
                    log.info("del cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
                    response.addCookie(ck);
                    return;
                }
            }
        }
    }

}

通过这个类,我们登录的时候就从服务端给客户端种上了一个cookie,只要我们是在.XXX.com这个一级域名下进行操作,就能拿到这个cookie

    @RequestMapping(value = "login.do",method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse){
        ServerResponse<User> serverResponse = iUserService.login(username,password);
        if (serverResponse.isSuccess()){
           // session.setAttribute(Const.CURRENT_USER,serverResponse.getData());

       //登录的时候我们就把cookie种上返回给客户端,那么我们校验是否登录的时候就readLoginToken
       //就是说我们现在不管你走哪个tomcat,cookie是一样的 CookieUtil.writeLoginToken(httpServletResponse,session.getId()); RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(serverResponse.getData()),Const.RedisCacheExtime.REDIS_SESSION_EXTIME); } return serverResponse; }

校验是否登录

    @RequestMapping("cancel.do")
    @ResponseBody
    public ServerResponse cancel(HttpServletRequest request, Long orderNo){
        //User user = (User) session.getAttribute(Const.CURRENT_USER);
        String loginToken = CookieUtil.readLoginToken(request);
        if(StringUtils.isEmpty(loginToken)){
            return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");
        }
        String userJsonStr = RedisShardedPoolUtil.get(loginToken);
        User user = JsonUtil.string2Obj(userJsonStr,User.class);
        if (user == null){
            return ServerResponse.createByErrorCode(ResponseCode.NEED_LOGIN.getCode(),ResponseCode.NEED_LOGIN.getDesc());
        }
        return iOrderService.cancle(user.getId(),orderNo);
    }

tomcat1和tomcat2还是会产生两个不同的Cookie,但同时我们又自己增加了name为study_login_token的cookie,所以我们只需要读取这个name,就可以判断用户登录状态

但现在又有了一个问题,我们存储在redis里面的“session”,一次登录以后有效期是30分钟,我们后面再执行其他操作,这个session的有效期是不会更新的,就是说一次登录只能玩30分钟,所以我们就用过滤器来解决这个问题

5.过滤器Filter重置session有效期

首先写好类

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>sesssionExpireFilter</filter-name>
        <filter-class>com.study.controller.commom.SessionExpireFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>sesssionExpireFilter</filter-name>
        <url-pattern>*.do</url-pattern>
    </filter-mapping>

现在单点登录就到这里了

猜你喜欢

转载自www.cnblogs.com/zhangliang1726/p/10205420.html