一、会话控制简述
HTTP协议是一个无状态协议,我们的服务器不能区分多次请求是否发送自同一个浏览器。
我们可以使用Cookie来解决这个问题。Cookie的工作机制:Cookie实际上就是一个头(请求头或者响应头),服务器以响应头的形式将Cookie发送给浏览器, 浏览器接收到该头以后,会将Cookie的信息保存,然后在每次访问服务器时,会以请求头的形式再将该Cookie发送给服务器。
Cookie的局限性:
1️⃣Cookie是纯文本,非常容易被截获,不太安全
2️⃣各个浏览器对Cookie的数量和大小都有限制,也就是说我们不能在Cookie中保存过多的信息
3️⃣即使对Cookie大小没有限制,我们也不会在Cookie中保存大量信息,因为浏览器每次访问时都会发回Cookie,这样会占用大量带宽
我们还可以使用Session来进行会话控制,Cookie和Session同属于会话控制的范畴。
二、HttpSession
HttpSession运行时依赖于Cookie,因为JSESSIONID是通过Cookie传递的,session是JavaWeb中的四个域对象之一。由于Cookie中的信息是保存在浏览器端的,浏览器每次访问时都需要发回Cookie,所以我们不能在Cookie中保存大量的信息。既然客户端不能保存大量的信息,那可不可以将信息保存到服务器端呢?我们可以为每次会话都在服务器中创建一个对象,然后在该对象中保存相关的信息。这个对象就是HttpSession,那怎么给会话和对象建立一个对应关系呢?给每一个session对象都赋有一个id,这个id是不可重复的,也就是每一个Session对象都有一个唯一的标识。这样我们就可以将这个唯一的标识交个浏览器保存,浏览器每次访问服务器时都会带着这个唯一标识(即JSESSIONID),这样服务器就可以根据这个唯一标识找到每个会话对应的session对象了。
SESSION工作机制:HttpSession对象就相当于浏览器在服务器的账户,而session的id(JSESSIONID),相当于这个账户的账号。实际上HttpSession对象就是服务器中用来保存会话信息的对象,每个session对象都有一个唯一的id,这个id通过cookie的形式先由服务器发送给浏览器,浏览器收到cookie以后会自动保存,然后在每次访问服务器时,都会带着这个Cookie,服务器就可以根据Cookie中保存的session的id找到浏览器对应的session对象。
Cookie的具体形式如下:
Set-Cookie JSESSIONID=A1C1D6E213BDF1F147E673963ED503B2
一次会话对应一个session对象,那session对象是什么时候创建的呢?HttpSession对象是在调用getSession()方法时创建的,但并不是第一次调用setSession()时都会创建session,会根据是否有JSESSIONID这个Cookie以及这个Cookie的状态来判断是否创建session(JSESSIONID存在且有效时不新创建,否则新建)。HttpSession创建时会有一个JSESSIONID,服务器会将session对象保存到一个SessionMap中,这个map的key就是JSESSIONID,而value就是session对象。如果浏览器在访问服务器时携带JSESSIONID这个Cookie,服务器就不会再去创建新的Session对象,而是根据浏览器的JSESSIONID的值,去SessionMap中查找已有的Session对象。在JSP对应的Java文件中已经自动调用getSession()方法,所以我们在访问JSP时也会创建session对象,这也是session是jsp页面中的隐藏对象的原因。
session的默认有效时间为一次会话,session的默认时间之所以是一次会话,并不是因为HttpSession对象被销毁,而是因为浏览器中保存JSESSIONID的那个Cookie的默认有效时间为一次会话,Cookie的销毁导致JSESSIONID的丢失。
HttpSession对象都是保存到服务器的SessionMap中的,一旦Session对象闲置超过一定时间以后,服务器会自动将该对象销毁,那这样以后即使浏览器还保存着JSESSIONID,但是由于对象已经销毁了,所以这时服务器要重新创建一个新的HttpSession对象。判断sesseion是否是新建的:
session.isNew();
如何设置Session的最大闲置时间?
1、我们可以通过修改tomcat的web.xml配置文件的方式:
在tomcat的web.xml文件中有如下配置信息:将session的默认闲置时间设置为30分钟
<session-config>
<session-timeout>30</session-timeout>
</session-config>
我们可以通过修改该值,来修改session的有效时间,这会导致当前服务器下的所有项目的session的闲置时间都会被修改。
2、在当前项目的web.xml文件中设置,设置方法和上边一样:
<session-config>
<session-timeout>30</session-timeout>
</session-config>
3、可以通过调用session对象方法的形式来设置:
session.setMaxInactiveInterval(秒数);
session.invalidate();//使session立即失效
大于0的时间:会设置闲置时间为相应的秒数
等于0的时间:会使session对象立即失效
小于0的时间:如果给setMaxInactiveInterval方法设置一个负的时间,则session对象永远不会被销毁,除非手动调用session.invalidate()方法
我们可以通过session.getMaxInactiveInterval()来获取session的最大闲置时间。
Tip
:较为安全的Cookie和Session的校验方式是,在对Cookie或Session进行校验时同时校验请求的IP。例如,我们可能会在Session中存放当前登录用户的对象信息user,那可以在封装user对象时同时添加一个登录IP的属性,并在用户登录成功时设置该IP属性的值,这样在通过JSESSIONID获取到session对象以后,先判断该session是否为新建session(session.isNew()),如果不是新session再比对请求的IP和session中存放的user对象的IP是否一致,一致则通过校验,否则校验失败,获取IP的方式参考如下:
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
三、URL重写
HttpSession运行时依赖于Cookie,如果我们将浏览器的Cookie禁用,那么HttpSession将不能正常使用,因为浏览器如果禁用了Cookie则无法保存携带着JSESSIONID的那个Cookie,也就是说浏览器每次访问服务器时候都无法携带JSESSIONID,而每次都会新建一个Session对象,所以Session对象将不能正常使用。
之前我们的Cookie都是通过请求报文的请求头来传送,我们可不可以通过URL地址来传递JSESSIONID呢?比如:
http://localhost:8080/12_WEB_Session/hello.jsp?jsessionid=29BAA3DDCCD6563ECE852D78E76EDC54
这种方式就叫做URL重写,但是我们手动拼地址很不靠谱,可以通过调用response的方法,以下两种方式得到的url就是url重写之后的结果:
String url = response.encodeURL(request.getContextPath()+"/hello.jsp");
String url2 = response.encodeRedirectURL(request.getContextPath()+"/hello.jsp");
还可以通过JSTL中的url标签,这种方式更简便:
<c:url value="/hello.jsp"></c:url>
整体来说URL重写比较简单,但是我们在实际开发中对URL重写做的并不是很多,一般情况下如果浏览器将Cookie禁用,那么一些依赖于Session的功能也将失效:因为Cookie和Session中都携带了一些个人信息,URL重写以后的地址,容易泄露信息,不太安全。
四、Session的活化和钝化
钝化是指将HttpSession对象序列化到硬盘中,一般钝化发生在服务器停止时。服务器停止时会自动将HttpSession对象序列化到硬盘,这个过程我们称为钝化。
活化是将硬盘中HttpSession对象加载进内存中,一般在服务器启动时,会自动将硬盘中HttpSession对象重新加载进内存。
一个类要想可以序列化到硬盘中必须要实现java.io.Serializable接口,这个类中的所有属性也需要实现java.io.Serializable接口,在开发时大部分类都要实现Serializable接口以实现序列化。
如果服务器的访问量较大,那么服务器会有大量的HttpSession对象存在,但是这些对象并不是都处于一个活跃的状态,这些不活跃的session对象也会存在于内存中,这样会大量占用内存,这时我们就希望将这些闲置的对象写入硬盘中,在用户需要使用时再加载进内存。
在context.xml文件中可以加入如下内容:
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="mySession" />
</Manager>
maxIdleSwap:指session的最大闲置时间,超过该时间以后,session将会自动钝化
directory:钝化到硬盘中的目录
session会钝化到tomcat服务器的work目录中
五、表单的重复提交
表单重复提交指的是同一个表单相同的内容多次提交。
表单重复提交的危害:
1️⃣增加数据库中垃圾数据
2️⃣无形中增加了服务器压力
表单重复提交的几种情况:
1️⃣表单提交成功以后,反复刷新成功页面
产生原因:使用的是转发。我们在Servlet中使用转发的形式跳转到成功页面,整个过程中浏览器只发送了一次请求,当我们在成功页面刷新时,实际是将上次的请求又发送了一遍,所以造成了重复提交。
解决:不使用转发,而是使用重定向。重定向发了两次请求,我们再次刷新,刷新的是第二个请求,而不是第一个。
2️⃣网速较慢时,用户多次点击提交按钮。
产生原因:就是表单的提交按钮可以点击多次
解决:使表单的提交按钮只能点一次,点完一次以后按钮将变为不可用的状态。
window.onload = function(){
//点击完提交按钮以后,让按钮变为一个不可用的状态
//获取按钮对象
var btn = document.getElementById("btn");
//为按钮绑定一个单击响应函数
btn.onclick = function(){
//设置按钮为不可用状态
this.disabled = true;
//如果将按钮设置为不可用状态,那么表单也将不会提交
//我们需要手动提交表单
this.parentNode.submit();
};
};
3️⃣成功提交请求后,点击回退按钮,但是不刷新页面,再次提交。
产生原因:服务器端的Servlet不能区分两次请求是不是重复提交的内容
解决:在Servlet中,在处理请求之前,先来检查表单是否是重复提交。使用token解决这个问题。
token是令牌的意思,token就是一串随机码。所谓的token就是服务器在处理用户请求之前,先检查token是否正确,如果token正确那服务器正常处理请求;如果token不正确服务器不处理请求。我们的令牌是一个一次性的令牌,只能使用一次,所以使用完毕之后一定要将token销毁
流程:使用UUID创建token,令牌一定要销毁不销毁是起不到作用的
1️⃣创建一个令牌,要求唯一,不能重复(UUID),并在服务器中保存token
2️⃣将token放入到浏览器的表单中
3️⃣浏览器提交表单时,会同时将token一起提交
4️⃣服务器在处理请求之前要检查令牌是否有效
5️⃣销毁token