Gookie與Session會話跟蹤機制

GookieSession會話跟蹤機制

簡介:Gookiesession是一種會話跟蹤,web程序中用來跟蹤用戶的整個會話。

Gookie是通過客戶端(瀏覽器)來記錄用戶的訪問信息和確定用戶的身份,

session在服務端記錄訪問信息。

一、Cookie機制

1.Cookie的出現解決的問題

在程序中我們常常需要跟蹤用戶的請求信息。每一個用戶的請求都應該只能屬於自己的會話,例如:用戶A在超市購買的任何商品都應該放在A的購物車內,不論用戶A什麼時間購買的,這都是屬於同一個會話的,不能放入用戶B或用戶c的購物車內,因爲這不是同一個會話。

 

Web應用程序是使用HTTP協議傳輸數據的。HTTP協議是無狀態的協議,這就意味着一旦數據交換完畢,客戶端與服務端的連接就會關閉,再次交換數據是需要重新建立連接。這就意味這服務器無法從連接上追蹤每個用戶的會話。 所以要跟蹤會話則就用到某種機制來解決這類問題的出現。

 

Cookie機制就是一種可以彌補HTTP協議無狀態的不足。在Session出現之前,基本上所有的網站都採用Cookie來跟蹤會話)

2.什麼是Cookie

Cookie意爲甜餅,是W3C組織提出,最早由Netscape社區發展的一種機制。目前Cookie已經成爲標準,所有的主流瀏覽器如IENetscapeFirefoxOpera等都支持Cookie

由於HTTP是一種無狀態的協議,服務器單從網絡連接上無從知道客戶身份。怎麼辦呢?就給客戶端們頒發一個通行證吧,每人一個,無論誰訪問都必須攜帶自己通行證。這樣服務器就能從通行證上確認客戶身份了。這就是Cookie的工作原理

Cookie實際上是一小段的文本信息。客戶端請求服務器,如果服務器需要記錄該用戶狀態,就使用response向客戶端瀏覽器頒發一個Cookie。客戶端瀏覽器會把Cookie保存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給服務器。服務器檢查該Cookie,以此來辨認用戶狀態。服務器還可以根據需要修改Cookie的內容。

查看某個網站頒發的Cookie很簡單。在瀏覽器地址欄輸入javascript:alert (document. cookie)就可以了(需要有網才能查看)。JavaScript腳本會彈出一個對話框顯示本網站頒發的所有Cookie的內容或者是點擊瀏覽器地址欄前端的圖標

 

 

 

注意:Cookie功能需要瀏覽器的支持。

如果瀏覽器不支持Cookie(如大部分手機中的瀏覽器)或者把Cookie禁用了,Cookie功能就會失效。因爲瀏覽器是通過請求發送一個Cookie,服務端只負責對該值進行操作

不同的瀏覽器採用不同的方式保存Cookie

IE瀏覽器會在“C:\Documents and Settings\你的用戶名\Cookies”文件夾下以文本文件形式保存,一個文本文件保存一個Cookie

 

 

3.java中使用Cookie

 

 

 

Java中把Cookie封裝成了javax.servlet.http.Cookie類。每個Cookie都是該Cookie類的對象。服務器通過操作Cookie類對象對客戶端Cookie進行操作。通過request.getCookie()獲取客戶端提交的所有Cookie(以Cookie[]數組形式返回),通過response.addCookie(Cookiecookie)向客戶端設置Cookie

Cookie對象使用key-value屬性對的形式保存用戶狀態,一個Cookie對象保存一個屬性對,一個request或者response同時使用多個Cookie。因爲Cookie類位於包javax.servlet.http.*下面,所以JSP中不需要import該類。

 

package MadeMyServlet;
 
import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 自定義Servlet類
 * @author Administrator
 *
 */
public class MyServlet extends HttpServlet {
/**
 * Cookie練習
 */
private static final long serialVersionUID = 1L;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
//獲取輸出流
PrintWriter out = resp.getWriter();
//例如記錄瀏覽器訪問對應服務器的次數
//獲取瀏覽器所有的Cookie值
Cookie[] cook = req.getCookies();
//設置一個變量來記錄cookie值或設置
int i = 1;
//判斷是否有Cookie值
if(cook == null || cook.length==0){
//如果Cookie值不存在則設置一個值
Cookie newCookie = new Cookie("i",i+"");
//將設置的Cookie返回給瀏覽器
resp.addCookie(newCookie);
}else{
//遍歷瀏覽器中的cookie值
for(Cookie coo : cook){
if("i".equals(coo.getName())){//如果有則判斷是否存在指定的i
i = Integer.parseInt(coo.getValue())+1;
coo.setValue(i+"");
//條件滿足將cookie返回到瀏覽器
resp.addCookie(coo);
out.println("<font>歡迎您第:"+i+"次訪問</font>");
return;
}
}
//如果沒有則設置我們需要的cookie值i
Cookie newCookie = new Cookie("i",i+"");
//將設置的Cookie返回給瀏覽器
resp.addCookie(newCookie);
}
out.println("<font>歡迎您第:"+i+"次訪問</font>");
out.flush();
out.close();
}
@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}


 

4.  Cookie的不可跨域名性

很多網站都會使用Cookie。例如,Google會向客戶端頒發CookieBaidu也會向客戶端頒發Cookie。那瀏覽器訪問Google會不會也攜帶上Baidu頒發的Cookie呢?或者Google能不能修改Baidu頒發的Cookie呢?

答案是否定的。Cookie具有不可跨域名性根據Cookie規範,瀏覽器訪問Google只會攜帶GoogleCookie,而不會攜帶BaiduCookieGoogle也只能操作GoogleCookie,而不能操作BaiduCookie

Cookie在客戶端是由瀏覽器來管理的。瀏覽器能夠保證Google只會操作GoogleCookie而不會操作BaiduCookie,從而保證用戶的隱私安全。瀏覽器判斷一個網站是否能操作另一個網站Cookie的依據是域名。GoogleBaidu的域名不一樣,因此Google不能操作BaiduCookie

需要注意的是,雖然網站images.google.com與網站www.google.com同屬於Google,但是域名不一樣,二者同樣不能互相操作彼此的Cookie

 

注意:用戶登錄網站www.google.com之後會發現訪問images.google.com時登錄信息仍然有效,而普通的Cookie是做不到的。這是因爲Google做了特殊處理。本章後面也會對Cookie做類似的處理。

 

5. Unicode編碼:保存中文

中文與英文字符不同,中文屬於Unicode字符,在內存中佔4個字符,而英文屬於ASCII字符,內存中只佔2個字節Cookie中使用Unicode字符時需要對Unicode字符進行編碼,否則會亂碼。

 

 

提示:Cookie中保存中文只能編碼。一般使用UTF-8編碼即可。不推薦使用GBK等中文編碼,因爲瀏覽器不一定支持,而且JavaScript也不支持GBK編碼。

 

6. BASE64編碼:保存二進制圖片

Cookie不僅可以使用ASCII字符與Unicode字符,還可以使用二進制數據。例如在Cookie中使用數字證書,提供安全度。使用二進制數據時也需要進行編碼。

%注意:本程序僅用於展示Cookie中可以存儲二進制內容,並不實用。由於瀏覽器每次請求服務器都會攜帶Cookie,因此Cookie內容不宜過多,否則影響速度。Cookie的內容應該少而精。

7.  設置Cookie的所有屬性

除了namevalue之外,Cookie還具有其他幾個常用的屬性。每個屬性對應一個getter方法與一個setter方法。Cookie類的所有屬性如表1.1所示。

1.1  Cookie常用屬性

   

   

String name

Cookie的名稱。Cookie一旦創建,名稱便不可更改

Object value

Cookie的值。如果值爲Unicode字符,需要爲字符編碼。如果值爲二進制數據,則需要使用BASE64編碼

int maxAge

Cookie失效的時間,單位秒。如果爲正數,則該CookiemaxAge秒之後失效。如果爲負數,該Cookie爲臨時Cookie,關閉瀏覽器即失效,瀏覽器也不會以任何形式保存該Cookie。如果爲0,表示刪除該Cookie。默認爲–1

boolean secure

Cookie是否僅被使用安全協議傳輸。安全協議。安全協議有HTTPSSSL等,在網絡上傳輸數據之前先將數據加密。默認爲false

String path

Cookie的使用路徑。如果設置爲“/sessionWeb/”,則只有contextPath爲“/sessionWeb”的程序可以訪問該Cookie。如果設置爲“/”,則本域名下contextPath都可以訪問該Cookie。注意最後一個字符必須爲“/

String domain

可以訪問該Cookie的域名。如果設置爲“.google.com”,則所有以“google.com”結尾的域名都可以訪問該Cookie。注意第一個字符必須爲“.

String comment

Cookie的用處說明。瀏覽器顯示Cookie信息的時候顯示該說明

int version

Cookie使用的版本號。0表示遵循NetscapeCookie規範,1表示遵循W3CRFC 2109規範

 

8.Cookie的有效期

CookiemaxAge決定着Cookie的有效期,單位爲秒(Second)。Cookie中通過getMaxAge()方法與setMaxAge(int maxAge)方法來讀寫maxAge屬性。

如果maxAge屬性爲正數,則表示該Cookie會在maxAge秒之後自動失效。瀏覽器會將maxAge爲正數的Cookie持久化,即寫到對應的Cookie文件中。無論客戶關閉了瀏覽器還是電腦,只要還在maxAge秒之前,登錄網站時該Cookie仍然有效。下面代碼中的Cookie信息將永遠有效。

 

Cookie cookie = new Cookie("username","helloweenvsfei");   // 新建Cookie

cookie.setMaxAge(Integer.MAX_VALUE);           // 設置生命週期爲MAX_VALUE

response.addCookie(cookie);                    // 輸出到客戶端

 

如果maxAge爲負數,則表示該Cookie僅在本瀏覽器窗口以及本窗口打開的子窗口內有效,關閉窗口後該Cookie即失效。maxAge爲負數的Cookie,爲臨時性Cookie,不會被持久化,不會被寫到Cookie文件中。Cookie信息保存在瀏覽器內存中,因此關閉瀏覽器該Cookie就消失了。Cookie默認的maxAge值爲–1

如果maxAge0,則表示刪除該CookieCookie機制沒有提供刪除Cookie的方法,因此通過設置該Cookie即時失效實現刪除Cookie的效果。失效的Cookie會被瀏覽器從Cookie文件或者內存中刪除,

 

例如:

Cookie cookie = new Cookie("username","helloweenvsfei");   // 新建Cookie

cookie.setMaxAge(0);                          // 設置生命週期爲0,不能爲負數

response.addCookie(cookie);                    // 必須執行這一句

 

response對象提供的Cookie操作方法只有一個添加操作add(Cookie cookie)

要想修改Cookie只能使用一個同名的Cookie來覆蓋原來的Cookie,達到修改的目的。刪除時只需要把maxAge修改爲0即可。

 

注意:從客戶端讀取Cookie時,包括maxAge在內的其他屬性都是不可讀的,也不會被提交。瀏覽器提交Cookie時只會提交namevalue屬性。maxAge屬性只被瀏覽器用來判斷Cookie是否過期。

 

9.  Cookie的修改、刪除

Cookie並不提供修改、刪除操作。如果要修改某個Cookie,只需要新建一個同名的Cookie,添加到response中覆蓋原來的Cookie

如果要刪除某個Cookie,只需要新建一個同名的Cookie,並將maxAge設置爲0,並添加到response中覆蓋原來的Cookie。注意是0而不是負數。負數代表其他的意義。讀者可以通過上例的程序進行驗證,設置不同的屬性。

 

注意:修改、刪除Cookie時,新建的CookievaluemaxAge之外的所有屬性,例如namepathdomain等,都要與原Cookie完全一樣。否則,瀏覽器將視爲兩個不同的Cookie不予覆蓋,導致修改、刪除失敗。

 

10.  Cookie的域名

Cookie是不可跨域名的。域名www.google.com頒發的Cookie不會被提交到域名www.baidu.com去。這是由Cookie的隱私安全機制決定的。隱私安全機制能夠禁止網站非法獲取其他網站的Cookie

正常情況下,同一個一級域名下的兩個二級域名如www.helloweenvsfei.comimages.helloweenvsfei.com也不能交互使用Cookie,因爲二者的域名並不嚴格相同。如果想所有helloweenvsfei.com名下的二級域名都可以使用該Cookie,需要設置Cookiedomain參數,例如:

Cookie cookie = new Cookie("time","20080808"); // 新建Cookie

cookie.setDomain(".helloweenvsfei.com");           // 設置域名

cookie.setPath("/");                              // 設置路徑

cookie.setMaxAge(Integer.MAX_VALUE);               // 設置有效期

response.addCookie(cookie);                       // 輸出到客戶端

 

讀者可以修改本機C:\WINDOWS\system32\drivers\etc下的hosts文件來配置多個臨時域名,然後使用setCookie.jsp程序來設置跨域名Cookie驗證domain屬性。

注意:domain參數必須以點(".")開始。另外,name相同但domain不同的兩個Cookie是兩個不同的Cookie。如果想要兩個域名完全不同的網站共有Cookie,可以生成兩個Cookiedomain屬性分別爲兩個域名,輸出到客戶端。

 

11. Cookie的路徑

domain屬性決定運行訪問Cookie的域名,而path屬性決定允許訪問Cookie的路徑(ContextPath)。例如,如果只允許/sessionWeb/下的程序使用Cookie,可以這麼寫:

Cookie cookie = new Cookie("time","20080808");     // 新建Cookie

cookie.setPath("/session/");                          // 設置路徑

response.addCookie(cookie);                           // 輸出到客戶端

設置爲/”時允許所有路徑使用Cookiepath屬性需要使用符號“/”結尾。name相同但domain相同的兩個Cookie也是兩個不同的Cookie

 

注意:頁面只能獲取它屬於的PathCookie。例如/session/test/a.jsp不能獲取到路徑爲/session/abc/Cookie。使用時一定要注意。

 

12.  Cookie的安全屬性

HTTP協議不僅是無狀態的,而且是不安全的。使用HTTP協議的數據不經過任何加密就直接在網絡上傳播,有被截獲的可能。使用HTTP協議傳輸很機密的內容是一種隱患。如果不希望CookieHTTP等非安全協議中傳輸,可以設置Cookiesecure屬性爲true。瀏覽器只會在HTTPSSSL等安全協議中傳輸此類Cookie。下面的代碼設置secure屬性爲true

 

Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie

cookie.setSecure(true);                           // 設置安全屬性

response.addCookie(cookie);                        // 輸出到客戶端

 

提示:secure屬性並不能對Cookie內容加密,因而不能保證絕對的安全性。如果需要高安全性,需要在程序中對Cookie內容加密、解密,以防泄密。

 

 

13 .JavaScript操作Cookie

Cookie是保存在瀏覽器端的,因此瀏覽器具有操作Cookie的先決條件。瀏覽器可以使用腳本程序如JavaScript或者VBScript等操作Cookie。這裏以JavaScript爲例介紹常用的Cookie操作。例如下面的代碼會輸出本頁面所有的Cookie

<script>document.write(document.cookie);</script>

由於JavaScript能夠任意地讀寫Cookie,有些好事者便想使用JavaScript程序去窺探用戶在其他網站的Cookie。不過這是徒勞的,W3C組織早就意識到JavaScriptCookie的讀寫所帶來的安全隱患並加以防備了,W3C標準的瀏覽器會阻止JavaScript讀寫任何不屬於自己網站的Cookie。換句話說,A網站的JavaScript程序讀寫B網站的Cookie不會有任何結果。

 

14. 案例:永久登錄

如果用戶是在自己家的電腦上上網,登錄時就可以記住他的登錄信息,下次訪問時不需要再次登錄,直接訪問即可。實現方法是把登錄信息如賬號、密碼等保存在Cookie中,並控制Cookie的有效期,下次訪問時再驗證Cookie中的登錄信息即可。

保存登錄信息有多種方案。最直接的是把用戶名與密碼都保持到Cookie中,下次訪問時檢查Cookie中的用戶名與密碼,與數據庫比較。這是一種比較危險的選擇,一般不把密碼等重要信息保存到Cookie

還有一種方案是把密碼加密後保存到Cookie中,下次訪問時解密並與數據庫比較這種方案略微安全一些。如果不希望保存密碼,還可以把登錄的時間戳保存到Cookie與數據庫中,到時只驗證用戶名與登錄時間戳就可以了。

這幾種方案驗證賬號時都要查詢數據庫。

本例將採用另一種方案,只在登錄時查詢一次數據庫,以後訪問驗證登錄信息時不再查詢數據庫。實現方式是把賬號按照一定的規則加密後,連同賬號一塊保存到Cookie中。下次訪問時只需要判斷賬號的加密規則是否正確即可本例把賬號保存到名爲accountCookie中,把賬號連同密鑰用MD1算法加密後保存到名爲ssidCookie中。驗證時驗證Cookie中的賬號與密鑰加密後是否與Cookie中的ssid相等。相關代碼如下:

 

loginCookie.jsp
<%@ page language="java"pageEncoding="UTF-8" isErrorPage="false" %>
<%!                                                  // JSP方法
    private static final String KEY =":[email protected]";
                                                     // 密鑰 
    public final static String calcMD1(Stringss) { // MD1 加密算法
       String s = ss==null ?"" : ss;                  // 若爲null返回空
       char hexDigits[] = { '0','1', '2', '3', '4', '1', '6', '7', '8', '9',
       'a', 'b', 'c', 'd', 'e', 'f' };                        // 字典
       try {
        byte[] strTemp =s.getBytes();                          // 獲取字節
        MessageDigestmdTemp = MessageDigest.getInstance("MD1"); // 獲取MD1
       mdTemp.update(strTemp);                                // 更新數據
        byte[] md =mdTemp.digest();                        // 加密
        int j =md.length;                                 // 加密後的長度
        char str[] = newchar[j * 2];                       // 新字符串數組
        int k =0;                                         // 計數器k
        for (int i = 0; i< j; i++) {                       // 循環輸出
         byte byte0 =md[i];
         str[k++] =hexDigits[byte0 >>> 4 & 0xf];
         str[k++] =hexDigits[byte0 & 0xf];
        }
        return newString(str);                             // 加密後字符串
       } catch (Exception e){return null; }
    }
%>
<%
   request.setCharacterEncoding("UTF-8");             // 設置request編碼
    response.setCharacterEncoding("UTF-8");        // 設置response編碼
   
    String action =request.getParameter("action"); // 獲取action參數
   
    if("login".equals(action)){                       // 如果爲login動作
        String account =request.getParameter("account");
                                                     // 獲取account參數
        String password =request.getParameter("password");
                                                     // 獲取password參數
        int timeout = newInteger(request.getParameter("timeout"));
                                                     // 獲取timeout參數
              
        String ssid =calcMD1(account + KEY); // 把賬號、密鑰使用MD1加密後保存
       
        CookieaccountCookie = new Cookie("account", account);
                                                     // 新建Cookie
       accountCookie.setMaxAge(timeout);              // 設置有效期
       
        Cookie ssidCookie =new Cookie("ssid", ssid);   // 新建Cookie
       ssidCookie.setMaxAge(timeout);                 // 設置有效期
       
       response.addCookie(accountCookie);             // 輸出到客戶端
       response.addCookie(ssidCookie);            // 輸出到客戶端
       
        // 重新請求本頁面,參數中帶有時間戳,禁止瀏覽器緩存頁面內容
       response.sendRedirect(request.getRequestURI() + "?" + System.
        currentTimeMillis());
        return;
    }
    elseif("logout".equals(action)){                  // 如果爲logout動作
       
        CookieaccountCookie = new Cookie("account", "");
                                                 // 新建Cookie,內容爲空
       accountCookie.setMaxAge(0);                // 設置有效期爲0,刪除
              
        Cookie ssidCookie =new Cookie("ssid", ""); // 新建Cookie,內容爲空
       ssidCookie.setMaxAge(0);                   // 設置有效期爲0,刪除
       response.addCookie(accountCookie);         // 輸出到客戶端
       response.addCookie(ssidCookie);         // 輸出到客戶端
        //重新請求本頁面,參數中帶有時間戳,禁止瀏覽器緩存頁面內容
       response.sendRedirect(request.getRequestURI() + "?" + System.
        currentTimeMillis());
        return;
    }
    boolean login = false;                        // 是否登錄
    String account = null;                        // 賬號
    String ssid = null;                           // SSID標識
   
    if(request.getCookies() !=null){               // 如果Cookie不爲空
        for(Cookie cookie :request.getCookies()){  // 遍歷Cookie
           if(cookie.getName().equals("account"))  // 如果Cookie名爲
                                                    account
               account = cookie.getValue();       // 保存account內容
           if(cookie.getName().equals("ssid")) // 如果爲SSID
               ssid = cookie.getValue();          // 保存SSID內容
        }
    }
    if(account != null && ssid !=null){    // 如果account、SSID都不爲空
        login =ssid.equals(calcMD1(account + KEY));
                                      // 如果加密規則正確, 則視爲已經登錄
    }
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
       <legend><%= login ? "歡迎您回來" : "請先登錄"%></legend>
        <% if(login){%>
            歡迎您, ${ cookie.account.value }.   
           <a href="${ pageContext.request.requestURI }?action=logout">
            註銷</a>
        <% } else {%>
        <formaction="${ pageContext.request.requestURI }?action=login"
        method="post">
           <table>
               <tr><td>賬號: </td>
                   <td><input type="text"name="account" style="width:
                   200px; "></td> 
               </tr>
               <tr><td>密碼: </td>
                   <td><inputtype="password" name="password"></td>
               </tr>
               <tr>
                   <td>有效期: </td>
                   <td><inputtype="radio" name="timeout" value="-1"
                   checked> 關閉瀏覽器即失效 <br/> <input type="radio" 
                   name="timeout" value="<%= 30 *24 * 60 * 60 %>"> 30天
                   內有效 <br/><input type="radio" name="timeout" value= 
                   "<%= Integer.MAX_VALUE %>"> 永久有效 <br/> </td> </tr>
               <tr><td></td>
                   <td><input type="submit"value=" 登  錄 " class= 
                   "button"></td>
               </tr>
           </table>
        </form>
        <% } %>

登錄時可以選擇登錄信息的有效期:關閉瀏覽器即失效、30天內有效與永久有效。通過設置Cookieage屬性來實現

 

 

提示:該加密機制中最重要的部分爲算法與密鑰。由於MD1算法的不可逆性,即使用戶知道了賬號與加密後的字符串,也不可能解密得到密鑰。因此,只要保管好密鑰與算法,該機制就是安全的。

 

 

二、Session 機制

除了使用CookieWeb應用程序中還經常使用Session來記錄客戶端狀態。Session是服務器端使用的一種記錄客戶端狀態的機制,使用上比Cookie簡單一些,相應的也增加了服務器的存儲壓力

 

1.原理:Session是另一種記錄客戶狀態的機制,不同的是Cookie保存在客戶端瀏覽器中,而Session保存在服務器上。客戶端瀏覽器訪問服務器的時候,服務器把客戶端信息以某種形式記錄在服務器上。這就是Session客戶端瀏覽器再次訪問時只需要從該Session中查找該客戶的狀態就可以了。

如果說Cookie機制是通過檢查客戶身上的“通行證”來確定客戶身份的話,那麼Session機制就是通過檢查服務器上的“客戶明細表”來確認客戶身份。Session相當於程序在服務器上建立的一份客戶檔案,客戶來訪的時候只需要查詢客戶檔案表就可以了。

 

2 . 實現用戶登錄

Session對應的類爲javax.servlet.http.HttpSession類。每個來訪者對應一個Session對象,所有該客戶的狀態信息都保存在這個Session對象裏。Session對象是在客戶端第一次請求服務器的時候創建的Session也是一種key-value的屬性對,通過getAttribute(Stringkey)setAttribute(String keyObjectvalue)方法讀寫客戶狀態信息。Servlet裏通過request.getSession()方法獲取該客戶的Session

例如:

HttpSession session = request.getSession();       // 獲取Session對象

session.setAttribute("loginTime", new Date());     // 設置Session中的屬性

   

out.println("登錄時間爲:" +(Date)session.getAttribute("loginTime"));      // 獲取Session屬性

request還可以使用getSession(boolean create)來獲取Session。區別是如果該客戶的Session不存在,request.getSession()方法會返回null,而getSession(true)會先創建Session再將Session返回。

Servlet中必須使用request來編程式獲取HttpSession對象,而JSP中內置了Session隱藏對象,可以直接使用。如果使用聲明瞭<%@page session="false" %>,則Session隱藏對象不可用。下面的例子使用Session記錄客戶賬號信息。

源代碼如下:

  session.jsp
<%@ page language="java" pageEncoding="UTF-8"%>
<jsp:directive.page import="com.helloweenvsfei.sessionWeb.bean.Person"/>
<jsp:directive.page import="java.text.SimpleDateFormat"/>
<jsp:directive.page import="java.text.DateFormat"/>
<jsp:directive.page import="java.util.Date"/>
<%!
    DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");         // 日期格式化器
%>
<%
    response.setCharacterEncoding("UTF-8");        // 設置request編碼
    Person[] persons =
    {           
       // 基礎數據,保存三個人的信息
        new Person("Liu Jinghua","password1", 34, dateFormat.parse
        ("1982-01-01")),
        new Person("Hello Kitty","hellokitty", 23, dateFormat.parse
        ("1984-02-21")),
        new Person("Garfield", "garfield_pass",23, dateFormat.parse
        ("1994-09-12")),
     };
   
    String message = "";                      // 要顯示的消息
   
    if(request.getMethod().equals("POST"))
    { 
        // 如果是POST登錄       
        for(Person person :persons)
        {           
            // 遍歷基礎數據,驗證賬號、密碼
            // 如果用戶名正確且密碼正確
           if(person.getName().equalsIgnoreCase(request.getParameter("username"))&&person.getPassword().equals(request.getParameter("password")))
           {              
               // 登錄成功,設置將用戶的信息以及登錄時間保存到Session
               session.setAttribute("person", person);                   // 保存登錄的Person
               session.setAttribute("loginTime", new Date());          // 保存登錄的時間              
               response.sendRedirect(request.getContextPath() + "/welcome.jsp");
               return;
            }
        }      
        message = "用戶名密碼不匹配,登錄失敗。";       // 登錄失敗
    }
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
<html>
    // ... HTML代碼爲一個FORM表單,代碼略,請看隨書光盤
</html>
 
登錄界面驗證用戶登錄信息,如果登錄正確,就把用戶信息以及登錄時間保存進Session,然後轉到歡迎頁面welcome.jsp。welcome.jsp中從Session中獲取信息,並將用戶資料顯示出來。
welcome.jsp代碼如下:
代碼1.10  welcome.jsp
<%@ page language="java" pageEncoding="UTF-8"%>
<jsp:directive.pageimport="com.helloweenvsfei.sessionWeb.bean.Person"/>
<jsp:directive.page import="java.text.SimpleDateFormat"/>
<jsp:directive.page import="java.text.DateFormat"/>
<jsp:directive.page import="java.util.Date"/>
<%!
    DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd");         // 日期格式化器
%>
<%
    Person person =(Person)session.getAttribute("person");                       // 獲取登錄的person
    Date loginTime =(Date)session.getAttribute("loginTime");                     // 獲取登錄時間
%>
    // ... 部分HTML代碼略
            <table>
               <tr><td>您的姓名:</td>
                   <td><%= person.getName()%></td>
               </tr>
               <tr><td>登錄時間:</td>
                   <td><%= loginTime%></td>
               </tr>
               <tr><td>您的年齡:</td>
                   <td><%= person.getAge()%></td>
               </tr>
               <tr><td>您的生日:</td>
                   <td><%=dateFormat.format(person.getBirthday()) %></td>
               </tr>
            </table>


注意程序中Session中直接保存了Person類對象與Date類對象,使用起來要比Cookie方便。

當多個客戶端執行程序時,服務器會保存多個客戶端的Session。獲取Session的時候也不需要聲明獲取誰的SessionSession機制決定了當前客戶只會獲取到自己的Session,而不會獲取到別人的Session。各客戶的Session也彼此獨立,互不可見

 

提示Session的使用比Cookie方便,但是過多的Session存儲在服務器內存中,會對服務器造成壓力。

 

 

3  Session的生命週期

Session保存在服務器端。爲了獲得更高的存取速度,服務器一般把Session放在內存裏。每個用戶都會有一個獨立的Session。如果Session內容過於複雜,當大量客戶訪問服務器時可能會導致內存溢出。因此,Session裏的信息應該儘量精簡。

Session在用戶第一次訪問服務器的時候自動創建需要注意只有訪問JSPServlet等程序時纔會創建Session,只訪問HTMLIMAGE等靜態資源並不會創建Session。如果尚未生成Session,也可以使用request.getSession(true)強制生成Session

Session生成後,只要用戶繼續訪問,服務器就會更新Session的最後訪問時間,並維護該Session用戶每訪問服務器一次,無論是否讀寫Session,服務器都認爲該用戶的Session“活躍(active)”了一次。

 

4  Session的有效期

由於會有越來越多的用戶訪問服務器,因此Session也會越來越多。爲防止內存溢出,服務器會把長時間內沒有活躍的Session從內存刪除。這個時間就是Session的超時時間。如果超過了超時時間沒訪問過服務器,Session就自動失效了。

Session的超時時間爲maxInactiveInterval屬性,可以通過對應的getMaxInactiveInterval()獲取,通過setMaxInactiveInterval(longinterval)修改。

Session的超時時間也可以在web.xml中修改。另外,通過調用Sessioninvalidate()方法可以使Session失效。

 

5  Session的常用方法

Session中包括各種方法,使用起來要比Cookie方便得多。Session的常用方法如表1.2所示。

1.2  HttpSession的常用方法

   

   

void setAttribute(String attribute, Object value)

設置Session屬性。value參數可以爲任何Java Object。通常爲Java Beanvalue信息不宜過大

String getAttribute(String attribute)

返回Session屬性

Enumeration getAttributeNames()

返回Session中存在的屬性名

void removeAttribute(String attribute)

移除Session屬性

String getId()

返回SessionID。該ID由服務器自動創建,不會重複

long getCreationTime()

返回Session的創建日期。返回類型爲long,常被轉化爲Date類型,例如:Date createTime = new Date(session.get CreationTime())

long getLastAccessedTime()

返回Session的最後活躍時間。返回類型爲long

int getMaxInactiveInterval()

返回Session的超時時間。單位爲秒。超過該時間沒有訪問,服務器認爲該Session失效

void setMaxInactiveInterval(int second)

設置Session的超時時間。單位爲秒

void putValue(String attribute, Object value)

不推薦的方法。已經被setAttribute(String attribute, Object Value)替代

Object getValue(String attribute)

不被推薦的方法。已經被getAttribute(String attr)替代

boolean isNew()

返回該Session是否是新創建的

void invalidate()

使該Session失效

TomcatSession的默認超時時間爲20分鐘。通過setMaxInactiveInterval(int seconds)修改超時時間。可以修改web.xml改變Session的默認超時時間。例如修改爲60分鐘:

<session-config>

   <session-timeout>60</session-timeout>      <!-- 單位:分鐘 -->

</session-config>

 

注意:<session-timeout>參數的單位爲分鐘,而setMaxInactiveInterval(int s)單位爲秒。

 

6  Session對瀏覽器的要求

雖然Session保存在服務器,對客戶端是透明的,它的正常運行仍然需要客戶端瀏覽器的支持。這是因爲Session需要使用Cookie作爲識別標誌。HTTP協議是無狀態的,Session不能依據HTTP連接來判斷是否爲同一客戶,因此服務器向客戶端瀏覽器發送一個名爲JSESSIONIDCookie,它的值爲該Sessionid(也就是HttpSession.getId()的返回值)。Session依據該Cookie來識別是否爲同一用戶。

Cookie爲服務器自動生成的,它的maxAge屬性一般爲–1,表示僅當前瀏覽器內有效,並且各瀏覽器窗口間不共享,關閉瀏覽器就會失效。

因此同一機器的兩個瀏覽器窗口訪問服務器時,會生成兩個不同的Session。但是由瀏覽器窗口內的鏈接、腳本等打開的新窗口(也就是說不是雙擊桌面瀏覽器圖標等打開的窗口)除外。這類子窗口會共享父窗口的Cookie,因此會共享一個Session

 

注意:新開的瀏覽器窗口會生成新的Session,但子窗口除外。子窗口會共用父窗口的Session例如,在鏈接上右擊,在彈出的快捷菜單中選擇“在新窗口中打開”時,子窗口便可以訪問父窗口的Session

如果客戶端瀏覽器將Cookie功能禁用,或者不支持Cookie怎麼辦?例如,絕大多數的手機瀏覽器都不支持CookieJava Web提供了另一種解決方案:URL地址重寫。

 

7  URL地址重寫

URL地址重寫是對客戶端不支持Cookie的解決方案。URL地址重寫的原理是將該用戶Sessionid信息重寫到URL地址中。服務器能夠解析重寫後的URL獲取Sessionid。這樣即使客戶端不支持Cookie,也可以使用Session來記錄用戶狀態。HttpServletResponse類提供了encodeURL(Stringurl)實現URL地址重寫,例如:

<td>

    <a href="<%=response.encodeURL("index.jsp?c=1&wd=Java") %>"> 
    Homepage</a>

</td>

該方法會自動判斷客戶端是否支持Cookie。如果客戶端支持Cookie,會將URL原封不動地輸出來。如果客戶端不支持Cookie,則會將用戶Sessionid重寫到URL中。重寫後的輸出可能是這樣的:

<td>

    <ahref="index.jsp;jsessionid=0CCD096E7F8D97B0BE608AFDC3E1931E?c=
    1&wd=Java">Homepage</a>

</td>

即在文件名的後面,在URL參數的前面添加了字符串“;jsessionid=XXX”。其中XXXSessionid。分析一下可以知道,增添的jsessionid字符串既不會影響請求的文件名,也不會影響提交的地址欄參數。用戶單擊這個鏈接的時候會把Sessionid通過URL提交到服務器上,服務器通過解析URL地址獲得Sessionid

如果是頁面重定向(Redirection),URL地址重寫可以這樣寫:

<%

    if(“administrator”.equals(userName))

    {

       response.sendRedirect(response.encodeRedirectURL(“administrator.jsp”));

        return;

    }

%>

效果跟response.encodeURL(String url)是一樣的:如果客戶端支持Cookie,生成原URL地址,如果不支持Cookie,傳回重寫後的帶有jsessionid字符串的地址。

對於WAP程序,由於大部分的手機瀏覽器都不支持CookieWAP程序都會採用URL地址重寫來跟蹤用戶會話。比如用友集團的移動商街等。

 

注意:TOMCAT判斷客戶端瀏覽器是否支持Cookie的依據是請求中是否含有Cookie。儘管客戶端可能會支持Cookie,但是由於第一次請求時不會攜帶任何Cookie(因爲並無任何Cookie可以攜帶),URL地址重寫後的地址中仍然會帶有jsessionid當第二次訪問時服務器已經在瀏覽器中寫入Cookie了,因此URL地址重寫後的地址中就不會帶有jsessionid了。

 

8  Session中禁止使用Cookie

既然WAP上大部分的客戶瀏覽器都不支持Cookie,索性禁止Session使用Cookie,統一使用URL地址重寫會更好一些。Java Web規範支持通過配置的方式禁用Cookie

 

下面舉例說一下怎樣通過配置禁止使用Cookie:

打開項目sessionWebWebRoot目錄下的META-INF文件夾(跟WEB-INF文件夾同級,如果沒有則創建),打開context.xml(如果沒有則創建),編輯內容如下:

代碼1.11 /META-INF/context.xml

<?xml version='1.0' encoding='UTF-8'?>

<Context path="/sessionWeb"cookies="false">

</Context>

 

或者修改Tomcat全局的conf/context.xml,修改內容如下:

代碼1.12  context.xml

<!-- The contents of this file will be loaded for eachweb application -->

<Context cookies="false">

    <!-- ... 中間代碼略 -->

</Context>

部署後TOMCAT便不會自動生成名JSESSIONIDCookieSession也不會以Cookie爲識別標誌,而僅僅以重寫後的URL地址爲識別標誌了。

 

注意:該配置只是禁止Session使用Cookie作爲識別標誌,並不能阻止其他的Cookie讀寫。也就是說服務器不會自動維護名爲JSESSIONIDCookie了,但是程序中仍然可以讀寫其他的Cookie

 

 

9.使用session記錄用戶訪問的次數

package Session;
 
import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
 * 連接該servlet,顯示session的次數,點擊超連接session次數減一
 * @author Administrator
 *
 */
public class SessionServlet extends HttpServlet {
 
/**
 *
 */
private static final long serialVersionUID = 1L;
 
/**
 * The doGet method of the servlet. <br>
 *
 * This method is called when a form has its tag value method equals to get.
 *
 * @param request the request send by the client to the server
 * @param response the response send by the server to the client
 * @throws ServletException if an error occurred
 * @throws IOException if an error occurred
 */
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
 
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//創建一個Session對象
HttpSession session = request.getSession();
//查看是點擊還是刷新
Object name =request.getParameter("name");
//查看是否有Session
Object ses = session.getAttribute("i");
int i = 1;
//如果爲空則是選擇刷新
if(name==null){
//判斷sessioin是否存在
if(ses != null){
//若session存在則設置i的值
i = (Integer)session.getAttribute("i")+1;
}
}else{//否則是選擇點擊減一
//如果是點擊連接則需要減一併且重定向因爲減一後會重定向繼續訪問則需要直接減而
if(ses != null){
i = (Integer)session.getAttribute("i")-2;
}
//重定向到該Session
response.sendRedirect("SessionServlet");
}
//將session的值設置爲i
session.setAttribute("i", i);
out.print("您第"+i+"次訪問");
out.print("<a href='http://localhost:8080/servlet/SessionServlet?name=a'>點擊減一</a>");
out.flush();
out.close();
}
 
/**
 * The doPost method of the servlet. <br>
 *
 * This method is called when a form has its tag value method equals to post.
 *
 * @param request the request send by the client to the server
 * @param response the response send by the server to the client
 * @throws ServletException if an error occurred
 * @throws IOException if an error occurred
 */
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
 
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章