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 2019-05-19 23:08  会飞的金鱼  阅读(157)  评论(0)    收藏  举报