疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

JavaWeb基础知识Cookie,Session


 

 
   •HTTP协议是一种无状态的协议,WEB服务器本身不能识别出哪些请求是同一个浏览器发出的,浏览器的每一次请求都是完全孤立的
    •即使 HTTP1.1支持持续连接,但当用户有一段时间没有提交请求,连接也会关闭。
    •怎么才能实现网上商店中的购物车呢:某个用户从网站的登录页面登入后,再进入购物页面购物时,负责处理购物请求的服务器程序必须知道处理上一次请求的程序所得到的用户信息。
    •作为 web 服务器,必须能够采用一种机制来唯一地标识一个用户,同时记录该用户的状态
1.会话和会话状态
    •WEB应用中的会话是指一个客户端浏览器与WEB服务器之间连续发生的一系列请求和响应过程。
    •WEB应用的会话状态是指WEB服务器与浏览器在会话过程中产生的状态信息,借助会话状态,WEB服务器能够把属于同一会话中的一系列的请求和响应过程关联起来。
2.如何实现有状态的会话
    •WEB服务器端程序要能从大量的请求消息中区分出哪些请求消息属于同一个会话,即能识别出来自同一个浏览器的访问请求,这需要浏览器对其发出的每个请求消息都进行标识:属于同一个会话中的请求消息都附带同样的标识号,而属于不同会话的请求消息总是附带不同的标识号,这个标识号就称之为会话ID(SessionID)。
    •在 Servlet规范中,常用以下两种机制完成会话跟踪
    –Cookie
    –Session
一、Cookie
 
[java] view plain copy 在CODE上查看代码片派生到我的代码片
/** 
 * @author changwen on 2016/11/13. 
 */  
import java.io.Serializable;  
import java.text.MessageFormat;  
import java.util.Locale;  
import java.util.ResourceBundle;  
  
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;  //这个只有get方法  
    /* 
    下面几个属性都只有简单get,set方法 
     */  
    private String value;  
    private String comment;  
    private String domain;  //这个set方法有处理过  
    private int maxAge = -1;  
    private String path;  
    private boolean secure;  
    private int version = 0;  
    //需要了解,boolean类型的get方法是这样子public boolean isHttpOnly() {return isHttpOnly;}  
    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"));  
        }  
    }  
  
  
    private boolean isToken(String value) {  
        int len = value.length();  
  
        for(int i = 0; i < len; ++i) {  
            char c = value.charAt(i);  
            if(c < 32 || c >= 127 || TSPECIALS.indexOf(c) != -1) {  
                return false;  
            }  
        }  
  
        return true;  
    }  
  
    public Object clone() {  
        try {  
            return super.clone();  
        } catch (CloneNotSupportedException var2) {  
            throw new RuntimeException(var2.getMessage());  
        }  
    }  
  
  
    static {  
        if(Boolean.valueOf(System.getProperty("org.glassfish.web.rfc2109_cookie_names_enforced", "true")).booleanValue()) {  
            TSPECIALS = "/()<>@,;:\"[]?={} ";  
        } else {  
            TSPECIALS = ",; ";  
        }  
  
    }  
  
    public void setDomain(String domain) {  
        this.domain = domain.toLowerCase(Locale.ENGLISH);  
    }  
    public String getDomain() {  
        return this.domain;  
    }  
  
    public String getName() {  
        return this.name;  
    }  
  
  
}  
Servlet API中提供了一个javax.servlet.http.Cookie类来封装Cookie信息,它包含有生成Cookie信息和提取Cookie信息的各个属性的方法。
    •HttpServletResponse接口中定义了一个addCookie方法,它用于在发送给浏览器的HTTP响应消息中增加一个Set-Cookie响应头字段。
    •HttpServletRequest接口中定义了一个getCookies方法,它用于从HTTP请求消息的Cookie请求头字段中读取所有的Cookie项。
1-1.Cookie的发送
    •1.创建Cookie对象
    •2.设置最大时效
    •3.将Cookie放入到HTTP响应报头
     –如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie;存储在浏览器的内存中,用户退出浏览器之后被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该cookie。
     –发送cookie需要使用HttpServletResponse的addCookie方法,将cookie插入到一个Set-CookieHTTP响应报头中。由于这个方法并不修改任何之前指定的Set-Cookie报头,而是创建新的报头,因此将这个方法称为是addCookie,而非setCookie。
2.会话cookie和持久cookie的区别
    •如果不设置过期时间,则表示这个cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。
    •如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。
    •存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。
1-3.应用
    1•自动登录:不需要填写用户名和密码等信息,可以自动登录到系统
    2•显示最近浏览的内容
    3•跟踪用户上次访问站点的时间
    •功能:帮助网站实现提示客户端计算机上次访问网站的时间
    •实现原理:
        将每一个会话作为一次访问过程,将每次会话的开始时间作为每次访问网站的时间,然后将这个时间以Cookie的形式存储到客户端的计算机中,客户端进行下次访问时通过该Cookie回传上次访问站点的时间值。
      为了让Cookie信息在客户端浏览器或计算机关闭后仍然保持存在,Cookie的保存时间被设置为了一年。
Cookie 的 作用范围: 可以作用当前目录和当前目录的子目录. 但不能作用于当前目录的上一级目录。可以通过 setPath 方法来设置 Cookie 的作用范围, 其中 / 代表站点的根目录.
 
二、Session
    •session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话是从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个session。
    •session在Web开发环境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器端之间保持状态的解决方案。有时候Session也用来指这种解决方案的存储结构。
1.Session机制
    •session机制采用的是在服务器端保持 HTTP 状态信息的方案。
    •服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。
    •当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否包含了一个session标识(即sessionId),如果已经包含一个sessionId则说明以前已经为此客户创建过session,服务器就按照sessionid把这个session检索出来使用(如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的session对象,但用户人为地在请求的URL后面附加上一个JSESSION的参数)。如果客户请求不包含sessionId,则为此客户创建一个session并且生成一个与此session相关联的sessionId,这个sessionid将在本次响应中返回给客户端保存。
 
2.保存sessionid的几种方式
    •保存sessionid的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。
    •由于cookie可以被人为的禁用,必须有其它的机制以便在cookie被禁用时仍然能够把sessionid传递回服务器,经常采用的一种技术叫做URL重写,就是把sessionid附加在URL路径的后面,附加的方式也有两种,一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面。网络在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个sessionid。
 
3.Session  cookie
    •session通过SessionID来区分不同的客户,session是以cookie或URL重写为基础的,默认使用cookie来实现,系统会创造一个名为JSESSIONID的输出cookie,这称之为sessioncookie,以区别persistentcookies(也就是我们通常所说的cookie),sessioncookie是存储于浏览器内存中的,并不是写到硬盘上的,通常看不到JSESSIONID,但是当把浏览器的cookie禁止后,web服务器会采用URL重写的方式传递Sessionid,这时地址栏看到
    •session cookie针对某一次会话而言,会话结束sessioncookie也就随着消失了,而persistentcookie只是存在于客户端硬盘上的一段文本。
    •关闭浏览器,只会是浏览器端内存里的sessioncookie消失,但不会使保存在服务器端的session对象消失,同样也不会使已经保存到硬盘上的持久化cookie消失。
 
4.Session生命周期,即创建与删除
    •一个常见的错误是以为session在有客户端访问时就被创建,不一定,1、如果是第一个页面,且JSP的page指定的session被设置为false。2、若当前JSP不是客户端访问的当前WEB应用的第一个资源,且其他页面已经创建一个HttpSession对象,则当前JSP页面会返回一个会话的HttpSession对象,而不会创建一个新的HttpSession对象。然而事实是直到某server端程序(如Servlet)调用HttpServletRequest.getSession(true)或者HttpServletRequest.getSession()这样的语句时才会被创建。
  •session在下列情况下被删除:
    –A.程序调用HttpSession.invalidate()
    –B.距离上一次收到客户端发送的sessionid时间间隔超过了session的最大有效时间
    返回最大时效: getMaxInactiveInterval() 单位是秒
    设置最大时效: setMaxInactiveInterval(int interval)
    可以在 web.xml 文件中配置 Session 的最大时效, 单位是分钟.
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    –C.服务器进程被停止
•注意:关闭浏览器只会使存储在客户端浏览器内存中的sessioncookie失效,不会使服务器端的session对象失效。
HttpSession的生命周期:
 
1.什么时候创建HttpSession对象
    1.是否浏览器访问服务端的任何一个 JSP 或Servlet,服务器都会立即创建一个HttpSession对象呢?不一定。若当前的JSP(或Servlet)是客户端访问的当前 WEB 应用的第一个资源,且JSP的page指定的session属性值为false,则服务器就不会为JSP创建一个HttpSession对象;若当前JSP不是客户端访问的当前WEB应用的第一个资源,且其他页面已经创建一个HttpSession对象,则当前JSP页面会返回一个会话的HttpSession对象,而不会创建一个新的HttpSession‘对象
    2.session=“false“  到底表示什么意思?当前 JSP页面禁用session隐含变量!但可以使用其他的显式的HttpSession对象
    3.对于Serlvet而言:若Serlvet是客户端访问的第一个WEB应用的资源,则只有调用了request.getSession()或request.getSession(true)才会创建HttpSession对象
 
5.两个浏览器窗口访问应用程序会使用同一个session
    •通常sessioncookie是不能跨窗口使用的(IE 8版本以前),当你新开了一个浏览器窗口进入相同页面时,系统会赋予你一个新的sessionid,这样信息共享的目的就达不到了。
    •此时可以先把sessionid保存在persistentcookie中(通过设置cookie的最大有效时间),然后在新窗口中读出来,就可以得到上一个窗口的sessionid了,这样通过sessioncookie和persistentcookie的结合就可以实现了跨窗口的会话跟踪。
 
6.Session的超时管理
    •WEB服务器无法判断当前的客户端浏览器是否还会继续访问,也无法检测客户端浏览器是否关闭,所以,即使客户已经离开或关闭了浏览器,WEB服务器还要保留与之对应的HttpSession对象。
    •随着时间的推移而不断增加新的访问客户端,WEB服务器内存中将会因此积累起大量的不再被使用的HttpSession对象,并将最终导致服务器内存耗尽。
    •WEB服务器采用“超时限制”的办法来判断客户端是否还在继续访问,如果某个客户端在一定的时间之内没有发出后续请求,WEB服务器则认为客户端已经停止了活动,结束与该客户端的会话并将与之对应的HttpSession对象变成垃圾。
    •如果客户端浏览器超时后再次发出访问请求,WEB服务器则认为这是一个新的会话的开始,将为之创建新的HttpSession对象和分配新的会话标识号。
    •会话的超时间隔可以在web.xml文件中设置,其默认值由Servlet容器定义。
  <session-config>
 
  <session-timeout>30</session-timeout>
 
  </session-config>
 
[java] view plain copy 在CODE上查看代码片派生到我的代码片
public interface HttpSession {  
    String getId(); // 获取session id  
  
    boolean isNew();  //session是否是新的  
  
    void setMaxInactiveInterval(int var1);  //设置session最大时效  
    int getMaxInactiveInterval();  // 获取session最大时效  
  
    long getCreationTime();  // 创建时间  
    long getLastAccessedTime();  //上次访问时间  
  
    /*最重要的两个方法*/  
    Object getAttribute(String var1);  // 获取属性  
    void setAttribute(String var1, Object var2);  
  
    Enumeration<String> getAttributeNames();  
    void removeAttribute(String var1);  
  
    void invalidate();  //使session无效  
  
    ServletContext getServletContext();  
  
  
    /** @deprecated */  
    HttpSessionContext getSessionContext();  
  
    /** @deprecated */  
    Object getValue(String var1);  
  
    /** @deprecated */  
    String[] getValueNames();  
  
    /** @deprecated */  
    void putValue(String var1, Object var2);  
  
    /** @deprecated */  
    void removeValue(String var1);  
}  
7.利用URL重写实现Session跟踪
    •Servlet规范中引入了一种补充的会话管理机制,它允许不支持Cookie的浏览器也可以与WEB服务器保持连续的会话。这种补充机制要求在响应消息的实体内容中必须包含下一次请求的超链接,并将会话标识号作为超链接的URL地址的一个特殊参数。
    •将会话标识号以参数形式附加在超链接的URL地址后面的技术称为URL重写。如果在浏览器不支持Cookie或者关闭了Cookie功能的情况下,WEB服务器还要能够与浏览器实现有状态的会话,就必须对所有可能被客户端访问的请求路径(包括超链接、form表单的action属性设置和重定向的URL)进行URL重写。
    •HttpServletResponse接口中定义了两个用于完成URL重写方法:
    - encodeURL方法
    - encodeRedirectURL方法
 
8.应用
 
9. 相对路径和绝对路径:
开发时建议编写“绝对路径”:写绝对路径肯定没有问题,但写相对路径却可能有问题
1). 为什么要解决相对路径的问题: 在有一个 Servlet 转发页面的情况下, 会导致相对路径的混乱.
  a.jsp: <a href="ToBServlet">To B Page2</a>
  ToBServlet: request.getRequestDispatcher("/dir/b.jsp").forward(request, response);
注意, 此时点击 To B Page2 超链接后的浏览器的地址栏的值: http://localhost:8989/day_36/ToBServlet, 实际显示的是dir 路径下的 b.jsp,而 b.jsp 页面有一个超链接: <a href="c.jsp">TO C Page</a>. 默认情况下, c.jsp 应该和 b.jsp 在同一路径下. 此时点击超链接
将在浏览器地址栏显示: http://localhost:8989/day_36/c.jsp. 但在根目录下并没有 c.jsp, 所以会出现路径混乱的问题.
2). 使用绝对路径会解决以上的问题:
绝对路径: 相对于当前 WEB 站点根目录的路径.在当前 WEB 应用的所有的路径前都添加 contextPath 即可.
http://localhost:8989/httpsession/c.jsp: 
  - http://localhost:8989/ 是 WEB 站点的根目录, 
  - /httpsession_36 是 contextPath,
  - /c.jsp 是相对于当前 WEB 应用的一个文件路径. 我们需要在当前 WEB 应用的任何的路径下都添加上 contextPath, 即可.
比如:
  - <a href="ToBServlet">To B Page2</a> 需改为: <a href="<%= request.getContextPath() %>/ToBServlet">To B Page2</a>
  - response.sendRedirect("a.jsp"); 需改为: response.sendRedirect(request.getContextPath() + "/a.jsp");
  - <form action="AddServlet"></form> 需改为: <form action="<%= request.getContextPath() %>/AddServlet"></form>
3). 在 JavaWEB 应用中 / 代表的是什么: 
  ①.当前WEB应用的根路径:http://localhost:8989/httpsession/   若/需要交给Servlet容器来处理
    - 请求转发时:request.getRequestDispatcher("/path/b.jsp").forward(request,response);
    - web.xml文件中映射Servlet访问路径时
        <servlet-mapping>
            <servlet-name>TestServlet</servlet-name>
            <url-pattern>/testServlet</url-pattern>
        </servlet-mapping>
  ②.WEB站点的根路径:http://localhost:8989/    若 / 直接交由浏览器解析
    - 超链接:<a href="/testServlet">To B Page</a>
    - 表达式中的action:<form action="/login.jsp"></form>
    - 做请求重定向的时候:response.sendRedirect("/a.jsp");
总结: / 什么时候代表站点的根目录, 什么时候代表当前 WEB 应用的根目录
    若 / 需要服务器进行内部解析, 则代表的就是 WEB 应用的根目录. 若是交给浏览器了, 则 / 代表的就是站点的根目录
若 / 代表的是 WEB 应用的根目录, 就不需要加上 contextPath 了.
4). 如何获取 contextPath:
  ServletContext: getContextPath()
  HttpServletRequest: getContextPath()
10.避免表单的重复提交
    •调用 RequestDispatcher.forward()方法,浏览器所保留的URL是先前的表单提交的URL,此时点击”刷新”,浏览器将再次提交用户先前输入的数据,引起重复提交
    •如果采用 HttpServletResponse.sendRedirct()方法将客户端重定向到成功页面,将不会出现重复一条问题
•利用Session防止表单重复提交
    •包含有FORM表单的页面必须通过一个服务器程序动态产生,服务器程序为每次产生的页面中的FORM表单都分配一个唯一的随机标识号,并在FORM表单的一个隐藏字段中设置这个标识号,同时在当前用户的Session域中保存这个标识号。
    •当用户提交FORM表单时,负责接收这一请求的服务器程序比较FORM表单隐藏字段中的标识号与存储在当前用户的Session域中的标识号是否相同,如果相同则处理表单数据,处理完后清除当前用户的Session域中存储的标识号。在下列情况下,服务器程序将忽略提交的表单请求:
     - 当前用户的Session中不存在表单标识号
     - 用户提交的表单数据中没有标识号字段
     - 存储在当前用户的Session域中的表单标识号与表单数据中的标识号不同
   •浏览器只有重新向WEB服务器请求包含FORM表单的页面时,服务器程序才又产生另外一个随机标识号,并将这个标识号保存在Session域中和作为新返回的FORM表单中的隐藏字段值。
3. 表单的重复提交
1). 重复提交的情况:
  ①. 在表单提交到一个 Servlet, 而 Servlet 又通过请求转发的方式响应一个 JSP(HTML) 页面,
此时地址栏还保留着 Serlvet 的那个路径, 在响应页面点击 "刷新"
  ②. 在响应页面没有到达时重复点击 "提交按钮".
  ③. 点击 "返回", 再点击 "提交"
2). 不是重复提交的情况: 点击 "返回", "刷新" 原表单页面, 再 "提交"。
 
3). 如何避免表单的重复提交: 在表单中做一个标记, 提交到 Servlet 时, 检查标记是否存在且是否和预定义的标记一致, 若一致, 则受理请求,
并销毁标记, 若不一致或没有标记, 则直接响应提示信息: "重复提交"
  ①. 仅提供一个隐藏域: <input type="hidden" name="token" value="changwen"/>. 行不通: 没有方法清除固定的请求参数.
  ②. 把标记放在 request 中. 行不通, 因为表单页面刷新后, request 已经被销毁, 再提交表单是一个新的 request.
  ③. 把标记放在 session 中. 可以!
    在原表单页面, 生成一个随机值 token
    在原表单页面, 把 token 值放入 session 属性中
    在原表单页面, 把 token 值放入到 隐藏域 中.
 
    在目标的 Servlet 中: 获取 session 和 隐藏域 中的 token 值
    比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 token 属性清除
    若不一致, 则直接响应提示页面: "重复提交"
10.利用Session实现一次性验证码
    •一次性验证码的主要目的就是为了限制人们利用工具软件来暴力猜测密码,其原理与利用Session防止表单重复提交的原理基本一样,只是将表单标识号变成了验证码的形式,并且要求用户将提示的验证码手工填写进一个表单字段中,而不是通过表单的隐藏字段自动回传给服务器。
    •服务器程序接收到表单数据后,首先判断用户是否填写了正确的验证码,只有该验证码与服务器端保存的验证码匹配时,服务器程序才开始正常的表单处理流程。
    •密码猜测工具要逐一尝试每个密码的前题条件是先输入正确的验证码,而验证码是一次性有效的,这样基本上就阻断了密码猜测工具的自动地处理过程。
使用 HttpSession 实现验证码
1). 基本原理: 和表单重复提交一致:
    在原表单页面, 生成一个验证码的图片, 生成图片的同时, 需要把该图片中的字符串放入到 session 中.
    在原表单页面, 定义一个文本域, 用于输入验证码.
 
    在目标的 Servlet 中: 获取 session 和 表单域 中的 验证码的 值
    比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 验证码 属性清除
    若不一致, 则直接通过重定向的方式返回原表单页面, 并提示用户 "验证码错误"