记一次Spring Cloud Session微服务间传递丢失问题定位(续) - 乱炖 - CSDN博客

版权声明: https://blog.csdn.net/weixin_36171229/article/details/80954716

 回想一下,Spring cloud微服务框架曾使用两年之久,为什么以前没有这种情况发生呢?

   仔细梳理了以前使用的场景,用户在请求业务服务之前,必须先进行系统登录,在用户登录校验请求的时候,创建系统Session而且这种登录校验过程中不涉及跨服务使用Session的情况,在用户登录校验通过以后,用户再请求业务时其实Session已经创建好了,不涉及Session创建,故没有触发上面的情况,的确实际业务使用中很少涉及这种情况,也就让人觉得使用很顺利,一切OK的错觉。

    填补这个坑,想到了如下两种方式:

  •  创建Session与使用Session不放到一次请求中

        也就是业务上规避一下,如果请求发现Session没有创建,说明用户可能没有登录过,可以创建Session后,将其请求重定向到Home页面或登录页面,这样下次业务数据请求时就可以直接使用Session了。这也是以前使用中没有注意到这个问题原因。

  • ZUUL重新设置Cookie中SESSION ID

        在此感谢HUIQQ0927提供的另外一种更便捷的方式,直接将默认的Cookie中的SESSIONID覆盖掉,重新设置新生成的SESSION ID。如下所示:

public Object run() throws ZuulException {        Double rand = Math.random() * 100;        int randInt = rand.intValue();        RequestContext ctx = RequestContext.getCurrentContext();        HttpServletRequest request = ctx.getRequest();        HttpSession session = request.getSession();        if(!request.isRequestedSessionIdValid()) {            String sessionBase64 =  Base64.getEncoder().encodeToString(session.getId().getBytes());            ctx.addZuulRequestHeader("Cookie", "SESSION=" + sessionBase64);            LOG.info("Session Base64:{}",sessionBase64);        //    ctx.addZuulRequestHeader(SESSION_ID, session.getId());        }        session.setAttribute("test", randInt);        LOG.info("Session id:{} test:{}",session.getId(),randInt);        return null;    }

       不过需要注意的是这个处理将整个Request请求中的Cookie都覆盖了,通常我们请求中Cookie中不仅仅存放一个SESSION ID,还会有其他业务或如SSO其他系统共享的Cookie,这样处理起来就会比较麻烦,需要先获取原有Request的Cookie获取到,然后修改或添加SESSION ID到原有的Cookie,最后再设置到请求的Header中。

     由于是演示,故上面这段代码没有处理原有Cookie信息,实际应用中需要特别慎重处理,否则将引起不必要的麻烦。

  •   重新实现CookieId获取策略

        Spring boot在请求进来时对Request进行了包装,而在获取Session id时目前Spring boot支持两种,一种Cookie方式,也就是通常默认使用的方式;还有一种是header模式,通过request.getHeader()获取Session ID。

       Spring boot定义了Session id获取的接口org.springframework.session.web.http.HttpSessionIdResolver,默认的两个实现类为:

  1.     org.springframework.session.web.http.CookieHttpSessionIdResolver

            该接口为默认的Spring boot获取Session方法,从Cookie中获取相应的Session id。 

     2.    org.springframework.session.web.http.HeaderHttpSessionIdResolver

           该接口通过从Request Header中获取session id,对其感兴趣的话可以查询Spring boot源码进行查看详细内容。

     既然Spring boot提供了这个接口,那我们就可以自定义实现一个HttpSessionIdResolver接口,来获取自定义的Session id,下面我们应该怎么实现这个接口呢?

     Spring boot默认使用CookieHttpSessionIdResolver进行操作,这样我们就有了参考,因为该类为Final类,不能够继承,故需要将其源码Copy到自定义类中,然后修改其id获取方式,那我们应该以什么方法获取到Session id呢?

     我们可以将Header与Cookie两种结合方式来获取,即在Session创建的服务类中,将Session id设置到request header中,而在使用Session的服务类中,定义CustomHttpSessionIdResolver类,先从request header中获取session id,如果获取不到,再从cookie中获取,这样保证了Session id任何时候都能获取到,然后通过Session id也能获取到正确的Session对象了。

      具体实现如下:

      ZUUL服务中Filter中如果发现创建了Session则将Session id设置到request header中.

@Componentpublic class LoginFilter extends ZuulFilter {     private final static Logger LOG = LoggerFactory.getLogger(LoginFilter.class);        private final static  String SESSION_ID = "SESSIONID";        @Override    public Object run() throws ZuulException {        Double rand = Math.random() * 100;        int randInt = rand.intValue();        RequestContext ctx = RequestContext.getCurrentContext();        HttpServletRequest request = ctx.getRequest();        HttpSession session = request.getSession();        if(!request.isRequestedSessionIdValid()) {            ctx.addZuulRequestHeader(SESSION_ID, session.getId());        }        session.setAttribute("test", randInt);        LOG.info("Session id:{} test:{}",session.getId(),randInt);        return null;    }     @Override    public boolean shouldFilter() {        return true;    }     @Override    public int filterOrder() {        return 0;    }     @Override    public String filterType() {        return "pre";    } }

    业务服务中,定制获取Cookie的类,如下:

package com.king.business; import java.util.Collections;import java.util.List; import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils;import org.springframework.session.web.http.CookieHttpSessionIdResolver;import org.springframework.session.web.http.CookieSerializer;import org.springframework.session.web.http.DefaultCookieSerializer;import org.springframework.session.web.http.HttpSessionIdResolver;import org.springframework.session.web.http.CookieSerializer.CookieValue;import org.springframework.stereotype.Component;/** *  * @author Administrator * */@Componentpublic class CustomHttpSessionIdResolver implements HttpSessionIdResolver {     private static final String WRITTEN_SESSION_ID_ATTR = CookieHttpSessionIdResolver.class            .getName().concat(".WRITTEN_SESSION_ID_ATTR");     private final static  String SESSION_ID = "SESSIONID";    private CookieSerializer cookieSerializer = new DefaultCookieSerializer();     @Override    public List<String> resolveSessionIds(HttpServletRequest request) {        String sessionId = request.getHeader(SESSION_ID);        if(!StringUtils.isEmpty(sessionId)) {            return Collections.singletonList(sessionId);        }        return this.cookieSerializer.readCookieValues(request);    }     @Override    public void setSessionId(HttpServletRequest request, HttpServletResponse response,            String sessionId) {        if (sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) {            return;        }        request.setAttribute(WRITTEN_SESSION_ID_ATTR, sessionId);        this.cookieSerializer                .writeCookieValue(new CookieValue(request, response, sessionId));    }     @Override    public void expireSession(HttpServletRequest request, HttpServletResponse response) {        this.cookieSerializer.writeCookieValue(new CookieValue(request, response, ""));    }     /**     * Sets the {@link CookieSerializer} to be used.     *     * @param cookieSerializer the cookieSerializer to set. Cannot be null.     */    public void setCookieSerializer(CookieSerializer cookieSerializer) {        if (cookieSerializer == null) {            throw new IllegalArgumentException("cookieSerializer cannot be null");        }        this.cookieSerializer = cookieSerializer;    } }

     这样通过配合完美的解决了Spring cloud单次服务Session传递问题。


Original url: Access
Created at: 2019-03-07 23:34:56
Category: default
Tags: none

请先后发表评论
  • 最新评论
  • 总共0条评论