session拿request_基于Cookie+Redis+Filter实现Tomcat集群Session共享

1. 基于Cookie+Redis+Filter解决方案

用户登录之后,将Session Id和用户信息存储到Redis中,并添加一个Cookie,将该Session Id带到客户端。当发起其他请求之后,携带该Cookie,应用服务器获取到Session Id之后去Redis中查询是否存在,如果存在则继续进行相关业务,否则提示用户未登录。那种在Cookie中存放用户信息的方式直接Pass掉了。

实现过程

105b1b34f9ab099082c882589cfd9071.png

登录过程

public ServerResponse login(String username,String password, HttpSession session, HttpServletResponse response){

    //获取数据库中的用户信息

    ServerResponseserverResponse = iUserService.login(username,password);

    if (serverResponse.isSuccess()){

        StringsessionId = session.getId();

        //添加Cookie

        CookieUtil.writeCookie(response,sessionId);

        //将sessionId和Json化的用户信息保存到分片的Redis中

        RedisShardedUtil.setEx(sessionId,JsonUtil.obj2String(serverResponse.getData()),Const.RedisCache.SESSION_EXTIME);

    }

    return serverResponse;

}

刷新“Session”有效时间

假设“Session”的默认时间设置为半个小时,当我们登录之后,每次请求都应该将Session的有效期设置为半个小时。否则的话,一到半个小时“Session”失效了用户还得重新登录。为了解决这个问题,我们设置一个过滤器:

public class SessionExpireFilter implements Filter {

    @Override

    public void init(FilterConfigfilterConfig) throws ServletException {

    }

    @Override

    public voiddoFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException {

        //将ServletRequest转换为HttpServletRequest

        HttpServletRequestrequest = (HttpServletRequest)servletRequest;

        Stringtoken = CookieUtil.readCookieValue(request);

        //如果token不为空的话,符合条件,则获取user信息,user不为空,则将redis缓存中的session时间重置为指定时时长

        if(StringUtils.isNotBlank(token)){

            StringuserJsonStr = RedisShardedUtil.get(token);

            Useruser = JsonUtil.string2Obj(userJsonStr,User.class);

            if(user!= null){

                //如果user不为空,则重置session的时间,即调用expire命令

                RedisShardedUtil.expire(token,Const.RedisCache.SESSION_EXTIME);

            }

        }

        filterChain.doFilter(servletRequest,servletResponse);

    }

    @Override

    public void destroy() {

    }

}

在web.xml中配置该过滤器:

    sessionExpireFilter

    com.lcmall.common.filter.SessionExpireFilter

    sessionExpireFilter

    *.do

123456789

其中CookieUtil封装的代码如下,注释很详细,就不再解释了:

/**

* cookie操作工具类

* @author wlc

*/

@Slf4j

public class CookieUtil {

    /**设置为一级域名,子域名就可以共享该一级域名下的cookie*/

    private static final StringCOOKIE_DOMAIN = ".lcmall.com";

    /**设置cookie的目录为根目录"/",子级目录可以共享*/

    private static final StringCOOKIE_PATH = "/";

    /**设置cookie的name,读写均使用它*/

    private static final StringCOOKIE_NAME = "login_token";

    /**

     * 读取cookie中的value

     * @param request

     * @return

     */

    public static StringreadCookieValue(HttpServletRequest request){

        MapcookieMap = getCookieMap(request);

        if(cookieMap.containsKey(COOKIE_NAME)){

            Cookiecookie = cookieMap.get(COOKIE_NAME);

            log.info("returncookieName:{},cookieValue:{}",cookie.getName(),cookie.getValue());

            returncookie.getValue();

        }

        returnnull;

    }

    /**

     * 写入cookie,下面解释一下domain与path

     *     //X:domain=".lcmall.com"

     *     //a:A.lcmall.com            cookie:domain=A.lcmall.com;path="/"

     *     //b:B.lcmall.com            cookie:domain=B.lcmall.com;path="/"

     *     //c:A.lcmall.com/test/cc    cookie:domain=A.lcmall.com;path="/test/cc"

     *     //d:A.lcmall.com/test/dd    cookie:domain=A.lcmall.com;path="/test/dd"

     *     //e:A.lcmall.com/test       cookie:domain=A.lcmall.com;path="/test"

     *

     *     //由于domain和path的设置以上的结果如下:

     *     //a,b,c,d,e都能拿到X这个domain下的cookie

     *     //a与b相互之间是拿不到之间的cookie的

     *     //c与d均能够共享a与e产生的cookie

     *     //a与b相互之间是拿不到之间的cookie的,c、d均拿不到b的

     * @param response

     * @param token

     * @return

     */

    public static voidwriteCookie(HttpServletResponse response, String token){

        Cookiecookie = new Cookie(COOKIE_NAME,token);

        cookie.setDomain(COOKIE_DOMAIN);

        //设置cookie的访问仅通过http方式,可一定程度防止脚本攻击

        cookie.setHttpOnly(true);

        //如果不设置该值,则cookie不会保存到硬盘中,只存在于内存中,只在当前页面有效。

        //单位为s,这里设置为一年,如果设置为-1,则代表永久

        cookie.setMaxAge(60*60*24*365);

        cookie.setPath(COOKIE_PATH);

        log.info("wirtecookie name:{},value:{}",cookie.getName(),cookie.getValue());

        response.addCookie(cookie);

    }

    /**

     * 删除cookie

     * @param response

     * @return

     */

    public static voiddelCookie(HttpServletRequest request,HttpServletResponse response){

        MapcookieMap = getCookieMap(request);

        if(cookieMap.containsKey(COOKIE_NAME)){

            Cookiecookie = cookieMap.get(COOKIE_NAME);

            cookie.setDomain(COOKIE_DOMAIN);

            cookie.setPath(COOKIE_PATH);

            //设置成0,代表删除此cookie

            cookie.setMaxAge(0);

            log.info("delcookieName:{},cookieValue:{}",cookie.getName(),cookie.getValue());

            response.addCookie(cookie);

        }

    }

    /**

     * 将request中的cookie包装成一个map,实现代码复用

     * @param request

     * @return

     */

    private staticMap getCookieMap(HttpServletRequest request){

        Cookie[]cookies = request.getCookies();

        MapcookieMap = new HashMap<>();

        if(cookies != null){

            for(Cookie cookie: cookies) {

                cookieMap.put(cookie.getName(),cookie);

            }

        }

        returncookieMap;

    }

}

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394

RedisShardedUtil的封装过程可以参见博客:分布式redis连接池工具类的封装

JsonUtil可以自己写,也可以使用现成的Json工具类,如FastJson等,这里是对Jackson进行的二次封装。

上面可以看出,这个方案其实并不是使用真正的Session。而且不论是明文还是加密之后的用户信息并没有放到Cookie中,所以一般情况下也不存在用户数据泄露的问题。测试方法可参见:Tomcat集群的Debug方法

优缺点

优点

代码灵活,基于分布式Redis,可以实现对高并发请求的支持。

缺点

需要修改的代码较多,涉及到Session的地方都需要更改。不太适合对老系统的改造,比较适合于新开发的系统。但是如果我们提前将用户接口抽离成了一个单独的服务,那么改造起来还是比较好处理的。

踩坑

An invalid domain [.lcmall.com]  was specified for thiscookie

原因是Tomcat8.5以后,Cookie的校验规则更改了,只允许以数字和字母开头。解决方法如下:

如果项目使用的外置Tomcat,需要更改Tomcat的配置文件,步骤:

1. Edit the Tomcat/conf/context.xml

2. Add the statement in betweeen the and tags:

3. Restart Tomcat.

1234

当项目为SpringBoot的时候,由于使用的内嵌Tomcat,需要更改代码:

@Configuration

public class CookieConfig {

    /**

     * 解决问题:

     * An invalid domain [.localhost.com]was specified for this cookie

     */

    @Bean

    public WebServerFactoryCustomizercookieProcessorCustomizer() {

        return(factory) -> factory.addContextCustomizers(

                (context)-> context.setCookieProcessor(new LegacyCookieProcessor()));

    }

}

1234567891011121314

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值