无状态的请求响应式行为,无法关联来自同一客户端的多个请求,当然也无法确定这些请求之间的共享数据。从服务端的角度来说,当用户的Web浏览器打开第一个链接到服务器的套接字时请求就开始了,直到服务器返回最后一个数据包关闭连接时,请求结束。ip地址或许是一个好的想法,但是NAT并不可靠,以大学校园来说,学生们使用着相同的ip,而真实的ip隐藏在NAT路由之后,因此如何辨别请求是否来自同一客户端便有了难题。
从HTTP1.1开始就出现了cookie技术。
下面为服务端的代码
Cookie cookie = new Cookie("lastAccess",currenttime);
response.addCookie(cookie);
在响应之中添加了Cookie(设置sert-cookie字段),之后会返回给客户端,客户端会把这个Cookie保存在客户端内存之中,以后发送请求就会在请求头中加入这个Cookie来起到识别客户端的作用。
下面简单展示一下。
package com.test;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(urlPatterns = "/CookieServlet", asyncSupported = true)
public class CookieServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie[] cookies = req.getCookies();
Boolean existence = valueInCookie(cookies, "mycookie");
if(!existence){
Cookie cookie = new Cookie("mycookie","123");
resp.addCookie(cookie);
}
}
private Boolean valueInCookie(Cookie[] cookies,String key){
if(cookies==null){
return false;
}
for(Cookie cookie : cookies){
String name = cookie.getName();
String value = cookie.getValue();
System.out.println("cookie is "+name+" "+value);
if(key.equals(name)){
return true;
}
}
return false;
}
}
我们看到了请求之中返回了Set-Cookie
再次发送请求我们发现请求头中已经有了Cookie.
以下为Cookie的部分源代码。
public class Cookie implements Cloneable, Serializable {
private static final long serialVersionUID = -6454587001725327448L;
private static final String TSPECIALS;
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
private String name;
private String value;
private String comment;
private String domain;
private int maxAge = -1;
private String path;
private boolean secure;
private int version = 0;
private boolean isHttpOnly = false;
public Cookie(String name, String value) {
if (name != null && name.length() != 0) {
if (this.isToken(name) && !name.equalsIgnoreCase("Comment") && !name.equalsIgnoreCase("Discard") && !name.equalsIgnoreCase("Domain") && !name.equalsIgnoreCase("Expires") && !name.equalsIgnoreCase("Max-Age") && !name.equalsIgnoreCase("Path") && !name.equalsIgnoreCase("Secure") && !name.equalsIgnoreCase("Version") && !name.startsWith("$")) {
this.name = name;
this.value = value;
} else {
String errMsg = lStrings.getString("err.cookie_name_is_token");
Object[] errArgs = new Object[]{name};
errMsg = MessageFormat.format(errMsg, errArgs);
throw new IllegalArgumentException(errMsg);
}
} else {
throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank"));
}
}
domain确定了浏览器该将Cookie发送的域名。
path就是进一步的将cookie限制在相对于域的某个URL中。
Expires是过期时间。
Secure保证了cookie只会通过https来发送
httponly就会把cookie限制在直接的浏览器之中,这样js和flash将无法访问到cookie.
domain和path是为了不同的请求发送不同的cookie,不可能一个请求会把全部的cookie都扔过去。
而Secure和httponly是为了防止一些漏洞以免造成不安全的事件。
有一种形式的会话劫持,可以利用js读取会话cookie内容,攻击者可以利用这点来跨站脚本攻击,先把js注入到某个页面,然后document.cookie就能读取到cookie,然后模拟会话,来达到冒充的效果,因此httponly很有必要,它会禁止js,flash或其他插件来获取到cookie,并且cookie只可被用在浏览器创建的http/https请求中。
http请求是会发生中间人攻击的,我们往往使用ssl/tls技术,secure就限制了cookie只能通过https来发送,防止这种情况。
集群中遇到的问题和解决方法。
会话以对象的方式存在于内存中,并且只存在web容器的单个实例中,在具有负载平衡的环境下,同一个客户端的多次不同请求会被发送到不同的服务器,第一个web服务器一切和以前一样,第二个请求中会有cookie但是服务器没有,根据上面的例子就是
Boolean existence = valueInCookie(cookies, "mycookie");
这一句就不对了。
通常的解决办法是粘滞会话,就是负载平衡累一点,把来自一个客户端的请求都扔到同一个服务器去。然而这样便不能使用https了,这会妨碍到负载平衡器的检测,这不是严重问题,因为现在很多的负载平衡器是支持https的,关键是把加密机制从服务器放到了负载平衡器上,那么负载平衡器要么获取到cookie进行记录,每次查记录去发送请求,要么添加自己的cookie,在后续请求中去识别,能用负载平衡的项目请求数量必定很大,如果在平衡器上就花费这些时间,集群的优势就被减弱了。同步每个tomcat的cookie自然也是很麻烦的,还要考虑请求阻塞不阻塞的问题。什么时候同步的问题,同步时处理不处理请求的问题,有些麻烦
tomcat已经给出了解决方法,还是粘滞会话,但是是有专门的服务器来做了,这个服务器卡在负载平衡器和web服务器之间,叫的是连接器(Apache HTTPD/mod_jk IIS/isapi_redirect)。连接器用了一个tomcat中被称为会话ID jvmroute的概念来完成粘滞会话。基本的套路就是我配置一个tomcat服务器的标识,在conf/server/xml配置,<Connector>下添加一个jvmroute,然后jvmroute的值会被添加到会话ID的末端,既然cookie和服务器之间的关系比较难维护,我们就直接在cookie标志出所属服务器,服务器数量不会太多,这样就比较好的完成了粘滞会话。