背景
由于最近在整理session&cookie的一些原理https://my.oschina.net/qixiaobo025/blog?search=session
我们可以看到在cookie没有开启的情况下 servlet会通过传输sessionid来完成用户的标识~
我们来考虑这个问题,服务端如何来判断客户端是否开启cookie了呢???
问题
对于cookie是否禁用 在客户端的判断比较容易 但是在服务端来说基本不太现实。
我们可以判断cookie被启用 但是比较难以判断cookie是被禁用的。毕竟当客户端没有传入cookie
也有可能是客户端第一次访问而已。那么如何判断是否是cookie被禁用呢?
一个简单能思考到的方案如下:
- 客户端访问指定url
- 服务端设置cookie 设置重定向
- 客户端重定向到服务端
- 服务端检查客户端有没有携带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);
}
}