奇怪的;jsessionid

背景

由于最近在整理session&cookie的一些原理https://my.oschina.net/qixiaobo025/blog?search=session

我们可以看到在cookie没有开启的情况下 servlet会通过传输sessionid来完成用户的标识~

我们来考虑这个问题,服务端如何来判断客户端是否开启cookie了呢???

问题

对于cookie是否禁用 在客户端的判断比较容易 但是在服务端来说基本不太现实。

我们可以判断cookie被启用 但是比较难以判断cookie是被禁用的。毕竟当客户端没有传入cookie

也有可能是客户端第一次访问而已。那么如何判断是否是cookie被禁用呢?

一个简单能思考到的方案如下:

  1. 客户端访问指定url
  2. 服务端设置cookie 设置重定向
  3. 客户端重定向到服务端
  4. 服务端检查客户端有没有携带cookie 携带cookie则认为cookie启用否则认为cookie被禁用

考虑下列场景

请求url上增加了;jsessionid的【注意不是QueryString而是url进行了重写】

可是carOwner项目都是移动端客户 莫非移动端客户都禁止了cookie???

分析

当cookie被禁用了之后 那么为了记住用户的身份 必然需要在客户端向服务端进行请求的时候传递一些必要的信息

在cookie启用的场景下需要传递sessionId那么在cookie禁用的场景下呢?自然是通过传输sessionId

servlet在做一些response的时候帮我们做了一些事情 当我们调用response

response.encodeRedirectURL(targetUrl)
/**
 * Encode the session identifier associated with this response
 * into the specified redirect URL, if necessary.
 *
 * @param url URL to be encoded
 */
@Override
public String encodeRedirectURL(String url) {
 
    if (isEncodeable(toAbsolute(url))) {
        return (toEncoded(url, request.getSessionInternal().getIdInternal()));
    } else {
        return (url);
    }
 
}

从这段注释可以看出 当使用上述api时会自动“判断cookie是否被禁用” 

似乎很神奇 按照我们之前的描述 想要判断cookie是否被禁用至少要进行一次重定向 那servlet是怎么判断的呢???

/**
 * Return <code>true</code> if the specified URL should be encoded with
 * a session identifier.  This will be true if all of the following
 * conditions are met:
 * <ul>
 * <li>The request we are responding to asked for a valid session
 * <li>The requested session ID was not received via a cookie
 * <li>The specified URL points back to somewhere within the web
 *     application that is responding to this request
 * </ul>
 *
 * @param location Absolute URL to be validated
 */
protected boolean isEncodeable(final String location) {
 
    if (location == null) {
        return (false);
    }
 
    // Is this an intra-document reference?
    if (location.startsWith("#")) {
        return (false);
    }
 
    // Are we in a valid session that is not using cookies?
    final Request hreq = request;
    final Session session = hreq.getSessionInternal(false);
    if (session == null) {
        return (false);
    }
    if (hreq.isRequestedSessionIdFromCookie()) {
        return (false);
    }
 
    // Is URL encoding permitted
    if (!hreq.getServletContext().getEffectiveSessionTrackingModes().
            contains(SessionTrackingMode.URL)) {
        return false;
    }
 
    if (SecurityUtil.isPackageProtectionEnabled()) {
        return (
            AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
 
            @Override
            public Boolean run(){
                return Boolean.valueOf(doIsEncodeable(hreq, session, location));
            }
        })).booleanValue();
    } else {
        return doIsEncodeable(hreq, session, location);
    }
}
 
private boolean doIsEncodeable(Request hreq, Session session,
                               String location) {
    // Is this a valid absolute URL?
    URL url = null;
    try {
        url = new URL(location);
    } catch (MalformedURLException e) {
        return (false);
    }
 
    // Does this URL match down to (and including) the context path?
    if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol())) {
        return (false);
    }
    if (!hreq.getServerName().equalsIgnoreCase(url.getHost())) {
        return (false);
    }
    int serverPort = hreq.getServerPort();
    if (serverPort == -1) {
        if ("https".equals(hreq.getScheme())) {
            serverPort = 443;
        } else {
            serverPort = 80;
        }
    }
    int urlPort = url.getPort();
    if (urlPort == -1) {
        if ("https".equals(url.getProtocol())) {
            urlPort = 443;
        } else {
            urlPort = 80;
        }
    }
    if (serverPort != urlPort) {
        return (false);
    }
 
    String contextPath = getContext().getPath();
    if (contextPath != null) {
        String file = url.getFile();
        if (!file.startsWith(contextPath)) {
            return (false);
        }
        String tok = ";" +
                SessionConfig.getSessionUriParamName(request.getContext()) +
                "=" + session.getIdInternal();
        if( file.indexOf(tok, contextPath.length()) >= 0 ) {
            return (false);
        }
    }
 
    // This URL belongs to our web application, so it is encodeable
    return (true);
 
}

从这段代码来看当且仅当 当前请求中并没有从cookie中获取到session 【另外就是允许在url中跟踪用户===》目前tomcat均是默认开启】

并且当前重定向url必须和当前port contextPath相同的场景下将会在url上添加上sessionId【需要额外处理url中包含了sessionId的场景】

从这种情况下来看其实并没有判断服务端cookie是否被禁用【当然cookie被禁用了之后也能走到这段逻辑,自然也是OK的】

复盘

判断我们车主端使用跳转来指定页面 通常需要微信用户授权后跳转到首页

/**
 * 微信授权回调接口
 */
@RequestMapping(value = "/wxRedirect")
public String wxRedirect(HttpServletRequest request, String code, String state) throws Exception {
    String[] params = state.split("\\|");
    if (!TextUtils.isEmpty(code) && params.length == 2) {
        PappStation pappStation = wxStationService.getPappStationbyWxAppId(params[0]);
        if (pappStation != null) {
            OauthAPI oauthAPI = new OauthAPI(wxConfigService.getConfig(pappStation));
            OauthGetTokenResponse response = oauthAPI.getToken(code);
            if (response.getErrcode() == null || ResultType.SUCCESS.getCode().toString().equals(response.getErrcode())) {
                SessionUtil.saveToSession(request, SessionUtil.KEY_APP, pappStation);
                GetUserInfoResponse userinfo = oauthAPI.getUserInfo(response.getAccessToken(), response.getOpenid());
                //保存或更新用户信息
                wxUserService.createOrUpdateUser(createUserObject(userinfo, pappStation.getWxAppId()));
                Puser puser = wxUserService.getUserInfoByOpenId(response.getOpenid());
                SessionUtil.saveToSession(request, SessionUtil.KEY_USER, puser);
                //重定向到用户实际访问的页面
                return "redirect:" + mConstant.getClientUrl() + "?appid=" + params[0]
                        + "&openid=" + response.getOpenid() + "#" + params[1];
            } else {
                J2Cache.getChannel().evict("apiConfig", pappStation.getWxAppId());
                logger.warn("clear cache for {}", pappStation.getWxAppId());
            }
        }
    }
    return "redirect:" + mConstant.getClientErrorPage();
}

其实springmvc在使用redirect的时候其实走到了RedirectView

/**
 * Convert model to request parameters and redirect to the given URL.
 * @see #appendQueryProperties
 * @see #sendRedirect
 */
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
      HttpServletResponse response) throws IOException {
 
   String targetUrl = createTargetUrl(model, request);
   targetUrl = updateTargetUrl(targetUrl, model, request, response);
 
   FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
   if (!CollectionUtils.isEmpty(flashMap)) {
      UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build();
      flashMap.setTargetRequestPath(uriComponents.getPath());
      flashMap.addTargetRequestParams(uriComponents.getQueryParams());
      FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request);
      if (flashMapManager == null) {
         throw new IllegalStateException("FlashMapManager not found despite output FlashMap having been set");
      }
      flashMapManager.saveOutputFlashMap(flashMap, request, response);
   }
 
   sendRedirect(request, response, targetUrl, this.http10Compatible);
}
 
 
 
/**
 * Send a redirect back to the HTTP client
 * @param request current HTTP request (allows for reacting to request method)
 * @param response current HTTP response (for sending response headers)
 * @param targetUrl the target URL to redirect to
 * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
 * @throws IOException if thrown by response methods
 */
protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
      String targetUrl, boolean http10Compatible) throws IOException {
 
   String encodedRedirectURL = response.encodeRedirectURL(targetUrl);
   if (http10Compatible) {
      if (this.statusCode != null) {
         response.setStatus(this.statusCode.value());
         response.setHeader("Location", encodedRedirectURL);
      }
      else {
         // Send status code 302 by default.
         response.sendRedirect(encodedRedirectURL);
      }
   }
   else {
      HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
      response.setStatus(statusCode.value());
      response.setHeader("Location", encodedRedirectURL);
   }
}

很明显这边调用了

response.encodeRedirectURL(targetUrl)

而根据我们上面描述就是 当该请求并没有cookie中获取到session中时 一旦调用encodeRedirectURL 那么在默认场景下将会使得url被重写

当然除了该接口encodeRedirectUrl之外还有

/**
 * Encode the session identifier associated with this response
 * into the specified URL, if necessary.
 *
 * @param url URL to be encoded
 */
@Override
public String encodeURL(String url) {
 
    String absolute;
    try {
        absolute = toAbsolute(url);
    } catch (IllegalArgumentException iae) {
        // Relative URL
        return url;
    }
 
    if (isEncodeable(absolute)) {
        // W3c spec clearly said
        if (url.equalsIgnoreCase("")) {
            url = absolute;
        } else if (url.equals(absolute) && !hasPath(url)) {
            url += '/';
        }
        return (toEncoded(url, request.getSessionInternal().getIdInternal()));
    } else {
        return (url);
    }
 
}

猜你喜欢

转载自my.oschina.net/qixiaobo025/blog/1794325