Session
Session是另一种记录浏览器状态的机制。不同的是Cookie保存在浏览器中,Session保存在服务器中。用户使用浏览器访问服务器的时候,服务器把用户的信息以某种的形式记录在服务器。
Session比Cookie使用方便,Session可以解决Cookie解决不了的事情【Session可以存储对象,Cookie只能存储字符串】。
Session API
- long getCreationTime();【获取Session被创建时间】
- String getId();【获取Session的id】
- long getLastAccessedTime();【返回Session最后活跃的时间】
- ServletContext getServletContext();【获取ServletContext对象】
- void setMaxInactiveInterval(int var1);【设置Session超时时间】
- int getMaxInactiveInterval();【获取Session超时时间】
- Object getAttribute(String var1);【获取Session属性】
- Enumeration<String> getAttributeNames();【获取Session所有的属性名】
- void setAttribute(String var1, Object var2);【设置Session属性】
- void removeAttribute(String var1);【移除Session属性】
- void invalidate();【销毁该Session】
- boolean isNew();【该Session是否为新的】
Session的生命周期和有效期
- Session在用户第一次访问服务器Servlet,jsp等动态资源就会被自动创建,Session对象保存在内存里。
- 如果访问HTML,IMAGE等静态资源Session不会被创建。
- Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,无论是否对Session进行读写,服务器都会认为Session活跃了一次。
- 由于会有越来越多的用户访问服务器,因此Session也会越来越多。为了防止内存溢出,服务器会把长时间没有活跃的Session从内存中删除,这个时间也就是Session的超时间。
Session的超时间默认是30分钟,有三种方式可以对Session的超时间进行修改。
方法一:在Tomcat/conf/web.xml文件中设置,时间值为20分钟,所有的WEB应用都有效。
<session-config> <session-timeout>30</session-timeout> </session-config>
方法二:在单个的web.xml文件中设置,对单个web应用有效,如果有冲突,以自己的web应用为准。
<session-config> <session-timeout>30</session-timeout> </session-config>
方法三:通过setMaxInactiveInterval()方法设置
session.setMaxInactiveInterval(20);
System.out.println(session.getMaxInactiveInterval());
Session的有效期与Cookie的是不同的

使用Session完成简单的购物功能
Servlet1显示“网页上所有的书籍”
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("网页上所有的书籍:" + "<br/>");
LinkedHashMap<String, Book> all = DBUtils.getAll();
Set<Map.Entry<String, Book>> entrySet = all.entrySet();
for (Map.Entry<String, Book> entry : entrySet) {
Book book = entry.getValue();
String url = "/Servlet2?id=" + book.getId();
response.getWriter().write(book.getName());
response.getWriter().write("<a href='" + url + "'>购买</a>");
response.getWriter().write("<br/>");
}
Servlet2将购买过的书籍放入到Session中
String id = request.getParameter("id");
Book book = (Book) DBUtils.getAll().get(id);
HttpSession session = request.getSession();
List<Book> listSession = (List) session.getAttribute("list");
if (listSession == null) {
List list = new ArrayList();
list.add(book);
session.setAttribute("list", list);
}else {
listSession.add(book);
session.setAttribute("list", listSession);
}
String url="/Servlet3";
response.sendRedirect(url);
Servlet3显示购买过的书籍
response.setContentType("text/html;charset=UTF-8");
HttpSession session = request.getSession();
List<Book> list = (List)session.getAttribute("list");
if(list==null||list.size()==0){
response.getWriter().write("你没有购买过任何商品");
}else{
response.getWriter().write("你购买过以下商品:");
response.getWriter().write("<br/>");
for(Book book:list){
response.getWriter().write(book.getName()+"<br/>");
}
}

Session实现原理
服务器时如何实现一个Session为一个用户浏览器服务的?或者说:为什么服务器能够为不同的用户浏览器提供不同的Session的?
- HTTP协议时无状态的,Session不能依据HTTP连接来判断是否为同一个用户。于是乎:服务器向用户浏览器发送一个名为JSESSIONID的Cookie,它的值就是Session的id值。其实Session依据Cookie来识别是否是同一个用户。
- 简单来说:Session之所以可以识别不同的用户,依靠的就是Cookie。
- 该Cookie是服务器自动颁发给浏览器的,不用手工创建。该Cookie的maxAge值默认是-1,也就是说金当前浏览器使用,不将该Cookie存在硬盘中。
浏览器禁用了Cookie,Session还能用吗?
URL地址重写,HttpServletResponse类提供了两个URL地址重写的方法:
- encodeURL(String url)
- encodeRedirectURL(String url)
这两个方法会自动判断该浏览器是否支持Cookie,如果支持Cookie,重写后的URL地址就不会带有JSESSIONID。但是即使浏览器支持Cookie,第一次输出URL地址的时候还是会出现JSESSIONID(因为没有任何Cookie可带)。
URL地址重写的原理:将Session的id信息重写到URL地址中,服务器解析重写后URL,获取Session的id。即使浏览器禁用掉了Cookie,但Session的id通过服务器端传递,还是可以使用Session来记录用户的状态。
String url="/Servlet3";
response.sendRedirect(response.encodeURL(url));
Session禁用Cookie
Java Web规范支持通过配置禁用Cookie
1)禁用自己项目的Cookie,在META-INF文件夹下的Context.xml文件中修改(没有则创建)
<?xml version='1.0' encoding='utf-8'?>
<Context path="/Servlet1" cookies="false">
</Context>
2)禁用全部web应用的Cookie,在Conf/context.xml中修改
注意:该配置只是让服务器不能自动维护名为jessionid的Cookie,并不能阻止Cookie的读写。
利用Session防止表单重复提交
重复提交的危害
- 在投票的网页上不停地提交,实现刷票的效果。
- 注册多个用户,不断发帖子,扰乱正常发帖秩序。
重复提交的情况
- 在处理表单的Servlet中刷新
- 后退再提交
- 网络延迟,多次点击提交按钮
网络延迟可以其实是客户端的问题,可以用javascript来防止。
<script type="text/javascript"> var isSubmited=false; function doSubmit(){ if(!isSubmited){ isSubmited=true; return true; }else{ return false; } } </script>
<form id="form" onsubmit="return doSubmit();" action="/servlet"> 用户名:<input type="text" name="username"> <input type="submit" value="提交"> </form>
还可以这样处理:第一次点击提交按钮后,就把按钮隐藏起来,不让用户点击。
<script type="text/javascript"> function doSubmit(){ var button=document.getElementById("button"); button.disabled=disabled; return true; } </script>
在处理表单的Servlet中刷新和后退再提交这两种方式不能只靠客户端来限制了。Session可以用来标识一个用户是否登陆了。不同的用户浏览器会拥有不同的Session,而request和Servlet不行,是因为request的域对象只能是一次http请求,提交表单数据的时候域对象的数据取不出来。ServletContext代表整个web应用,如果有几个用户浏览器同时访问,ServletContext域对象的数据就会被多次覆盖掉。
Session实现原理非常简单:
- 在session域中存储一个token
- 然后在前台页面的隐藏域获取得到这个token
- 在第一次访问的时候,判断session有没有值,如果有就比对,比对正确就处理请求,接着就把session存储的数据删除掉
- 等到下次访问的时候,session就没有值了,就不受理前台的请求了
向session域对象存入的数据是一个随机数【Token--令牌】
public class TokenProcessor { private TokenProcessor(){ } private final static TokenProcessor tokenProcessor=new TokenProcessor(); public static TokenProcessor getInstance(){ return tokenProcessor; } public static String makeToken(){ String token=String.valueOf(System.currentTimeMillis()+new Random().nextInt(99999999)); try { MessageDigest messageDigest=MessageDigest.getInstance("md5"); byte[] md5=messageDigest.digest(token.getBytes()); BASE64Encoder base64Encoder=new BASE64Encoder(); return base64Encoder.encode(md5); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } }
创建Token随机数,并跳转到jsp页面
HttpSession session = request.getSession(); TokenProcessor instance = TokenProcessor.getInstance(); String token=instance.makeToken(); session.setAttribute("token",token); request.getRequestDispatcher("/Login.jsp").forward(request,response);
jsp隐藏域获取到session的值
<form action="/LoginServlet" method="get"> 用户名:<input type="text" name="username"/><br/> 密码:<input type="password" name="password"/><br/> <input type="submit" value="提交"> <input type="hidden" name="token" value="${token}"> </form>
处理表单提交页面
String clientToken=request.getParameter("token");
HttpSession token = request.getSession();
String serverToken= (String)token.getAttribute("token");
System.out.println(clientToken+" "+serverToken);
if(clientToken!=null&&serverToken!=null&&clientToken.equals(serverToken)){
System.out.println("处理请求");
request.getSession().removeAttribute("token");
}else{
System.out.println("请不要重复提交");
}
一次性校验码
一次性验证码其实就是为了防止暴力猜测密码,验证的原理也非常简单:生成验证码后,把验证码的数据存进session域对象中,判断用户输入验证码是否和session域对象的数据一致。
生成验证码,并将验证码存进session中
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //在内存生成图片 BufferedImage bufferedImage=new BufferedImage(80,20,BufferedImage.TYPE_INT_RGB); //获取这张图片 Graphics2D graphics2D=(Graphics2D) bufferedImage.getGraphics(); //设置背景色为白色 graphics2D.setColor(Color.white); graphics2D.fillRect(0,0,80,20); //设置图片的字体和颜色 graphics2D.setFont(new Font(null,Font.BOLD,20)); graphics2D.setColor(Color.BLUE); //生成随机数 String randomNum=makeNum(); //往这张图片写数据,横坐标是0,纵坐标是20 graphics2D.drawString(randomNum,0,20); //将随机数存进session域中 request.getSession().setAttribute("randomNum",randomNum); //控制浏览器不缓存该图片 response.setHeader("Expires","-1"); response.setHeader("Cache-Control","no-cache"); response.setHeader("gragma","no-cache"); //通知浏览器以图片的方式打开 response.setHeader("Content-type","image/jpeg"); //把图片写给浏览器 ImageIO.write(bufferedImage,"jpg",response.getOutputStream()); }
生成随机数的方法
private static String makeNum(){ Random random=new Random(); int num=random.nextInt(999999); String randomNum=String.valueOf(num); StringBuffer stringBuffer=new StringBuffer(); for(int i=0;i<6-randomNum.length();i++){ stringBuffer.append("0"); } return stringBuffer.append(randomNum).toString(); }
jsp显示页面
<form action="/Login2Servlet"> 用户名:<input type="text" name="username"><br/> 密码:<input type="password" name="password"><br/> 验证码:<input type="text" name="randomNum"> <img src="/ImageServlet"><br/> <input type="submit" value="提交"> </form>
处理提交表单数据的Servlet
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String clientNum=request.getParameter("randomNum"); String serverNum=(String) request.getSession().getAttribute("randomNum"); if(clientNum!=null&&serverNum!=null&&clientNum.equals(serverNum)){ System.out.println("验证码正确"); return; }else{ System.out.println("验证码不正确"); } }
页面显示:

Session和Cookie的区别
从存储方式上比较
- Cookie只能存储字符串,如果要存储非ASCII字符串还要对其编码
- Session可以存储任何类型的数据,可以把Session看成一个容器
从隐私安全上比较
- Cookie存储在浏览器上,对客户端是可见的。信息容易泄露出去。如果使用Cookie,最好将Cookie加密
- Session存储在服务器上,对客户端是透明的。不存在敏感信息泄露问题。
从有效期上比较
- Cookie保存在硬盘中,只需要设置MaxAge属性为比较大的正整数,即使关闭浏览器,Cookie还是存在的
- Session的保存在服务器中,设置maxInactiveInterval属性值来确定Session的有效期。并且Session依赖于名为JSESSIONID的Cookie,该Cookie默认的maxAge属性为-1。如果关闭了浏览器,该Session虽然没有从服务器中消亡,但也就失效了。
从对服务器的负担比较
- Session是保存在服务器上的,每个用户都会产生一个Session,如果是并发访问的用户非常多,是不能使用Session的,Session会消耗大量的内存。
- Cookie是保存在客户端的。不占服务器的资源。大型网站一般都是使用Cookie来进行会话跟踪
从浏览器的支持上比较
- 如果浏览器禁用了Cookie,那么Cookie是无用的
- 如果浏览器禁用了Cookie,Session可以通过URL地址重写来进行会话跟踪
从跨域名上比较
- Cookie可以设置domain属性来实现跨域名
- Session只在当前的域名内有效,不可跨域名。
Cookie和Session共同使用
现在有一个问题:在购物的时候,不小心关闭浏览器,当返回进去浏览器的时候,发现购买过的商品记录都没了。因为服务器为Session自动维护的Cookie的maxAge属性默认是-1的,当浏览器关闭掉了,该Cookie就自动消亡了。当用户再次访问的时候,已经不是原来的Cookie了。
现在就解决:即使不小心关闭了浏览器,重新进去网站,还能找到购买记录。
把Cookie保存在硬盘中,即使关闭了浏览器,浏览器再次访问页面的时候,可以带上Cookie,从而服务器识别出Session。
第一种方式:只需要在处理购买页面创建Cookie,Cookie的值是Session的id返回给浏览器即可。
Cookie cookie=new Cookie("JSESSIONID",session.getId()); cookie.setMaxAge(30*60); cookie.setPath("/Servlet1"); response.addCookie(cookie);
第二种方式:在server.xml文件中配置,将每个用户的Session在服务器关闭的时候序列化到硬盘或数据库上保存。但这种方法不常用。
posted on
浙公网安备 33010602011771号