前提是调用String newurl = response.encodeRedirectURL("/workbench/abc/def");
,也就是用response调用encodeRedirectURL
这个方法。
最终会用到org.apache.catalina.connector.Response
这个类
581行:
public String encodeRedirectURL(String url) {
return this.isEncodeable(this.toAbsolute(url)) ? this.toEncoded(url, this.request.getSessionInternal().getIdInternal()) : url;
}
先判断是否需要编码this.isEncodeable(this.toAbsolute(url))
.
在这个方法里
protected boolean isEncodeable(final String location) {
if (location == null) {
return false;
} else if (location.startsWith("#")) {
return false;
} else {
final Request hreq = this.request;
final Session session = hreq.getSessionInternal(false);
if (session == null) {
return false;
} else if (hreq.isRequestedSessionIdFromCookie()) {
return false;
} else if (!hreq.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) {
return false;
} else {
return SecurityUtil.isPackageProtectionEnabled() ? (Boolean)AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
public Boolean run() {
return Response.this.doIsEncodeable(hreq, session, location);
}
}) : this.doIsEncodeable(hreq, session, location);
}
}
}
从方法里可以看到,
- 判断是否有session
- 在request的cookie里是否找到了session
- 以及servelt的模型是否允允许在URL上加session
以上三个条件都不满足,下边还会进行一个判断,没仔细看,主要是看最后一行this.doIsEncodeable(hreq, session, location);
private boolean doIsEncodeable(Request hreq, Session session, String location) {
URL url = null;
try {
url = new URL(location);
} catch (MalformedURLException var10) {
return false;
}
if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol())) {
return false;
} else if (!hreq.getServerName().equalsIgnoreCase(url.getHost())) {
return false;
} else {
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;
} else {
String contextPath = this.getContext().getPath();
if (contextPath != null) {
String file = url.getFile();
if (!file.startsWith(contextPath)) {
return false;
}
String tok = ";" + SessionConfig.getSessionUriParamName(this.request.getContext()) + "=" + session.getIdInternal();
if (file.indexOf(tok, contextPath.length()) >= 0) {
return false;
}
}
return true;
}
}
}
在这里我觉得最重要的一个就是file.startsWith(contextPath)
,也就是url里是否包含了servletcontext,也就是应用名,如果包含了,就会执行最下边的返回true
,返回了true
就会在URL的后边加上jsessionid
像这样http://localhost:8080/workbench/abc/def;jessionid=BB42488246F80CC8A7072FF3E35F3014
多了这个;
在一些应用里会导致问题,因为URL匹配不上造成404或者因为包含;
而被认为是注入或者攻击行为
解决方法:
这里以spring boot为例
在application.yml文件里添加如下
server:
port: 8080
servlet:
session:
tracking-modes: cookie
问题重现方法
@RequestMapping("/test/encodeURL")
public String th(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession(true);
session.setAttribute("abc", "def");
String newurl = response.encodeRedirectURL("/workbench/abc/def");
return newurl;
}
请求之前先清掉cookie里的jessionid,然后在浏览器访问,就可以得到一个带;jessionid
的返回URL