管理用戶狀態——Cookie與Session

利用Cookie跟蹤用戶狀態

博客系統由多個功能(頁面)組成:

  1. 首頁——包含博客功能簡介、用戶列表
  2. 用戶博客列表——包含某一用戶的文章列表
  3. 文章詳細頁面——包含某一篇文章的標題、詳細內容、創建時間等信息
  4. 創建文章頁面——包含文章標題、內容的表單

在創建文章時,除了標題和內容,還需要知道是誰創建的這篇文章,當然我們不能夠在表單中添加一個輸入框讓用戶輸入自己是誰——因爲用戶的身份很有可能被僞造。一個辦法是在每一個頁面被訪問之前,我們要求用戶輸入在網站註冊時輸入的用戶名/密碼進行驗證,如果驗證通過纔可以訪問對應的頁面。HTTP基本認證正好可以完成這樣的功能,可是這樣實在是太麻煩了,沒訪問一個頁面都需要用戶進行輸入。

我們知道HTTP協議本身是無狀態的,也就是說任何完全一致的請求都將會得到完全相同的返回。但是我們可以認爲的在HTTP請求中定義狀態。

Cookie是由客戶端保存的小型文本文件,其內容爲一系列的鍵值對,在瀏覽器訪問同一個域名的不同頁面時,會在HTTP請求中附上Cookie。

Cookie可以保存在內存中(關閉瀏覽器即消失),也可以保存在硬盤中(到達過期時間後消失)。另外,Cookie也是一個比較古老的東西,與JavaScript同樣由網景公司發明,現已被標準化爲RFC2109。

我們正是通過Cookie的這些特性來實現用戶狀態的保存的,每當用戶訪問一個網站時,服務器程序會分配給它一個唯一的id,這個id就是設置在Cookie中的,以打開瀏覽器第一次訪問http://tianmaying.com爲例,因爲是打開瀏覽器後第一次訪問,所以請求頭中不會有任何網站相關的Cookie存在,這時服務器會隨機分配的一個字符串作爲用戶的id並放在Cookie中:

Set-Cookie:JSESSIONID=9074327855952DA4A296B67685523812; Path=/; HttpOnly

這裏沒有指定Cookie的過期時間(Expire),那麼當瀏覽器關閉後,Cookie自動失效,從獲取網站的Cookie到瀏覽器關閉的這個週期,被稱爲一個會話——Session。那麼在這個會話中,在同一瀏覽器中任何一次對tianmaying.com的訪問,都會帶上這個名字爲JSESSIONID(Servlet默認的名字)的Cookie值(在服務器上隨機生成的字符串,服務器並會將其保存下來)。

可以簡單地這樣理解,服務端建立了一個Map<String, Object>,對於每一個請求都可以拿到Cookie中保存的JSESSIONID值,那麼服務器程序可以往這個Map裏寫入任何對象:

map.put(JSESSIONID, <obj>);

那麼在這一次會話中,不管是怎樣的請求,都可以訪問到這個Map中的對象。而服務器保存的這個Map,也被稱爲Session)。Session是一種服務器端保存用戶狀態的技術,它最常見的實現方式是在Cookie中加入一個字段存儲Session ID;但同時Cookie並不是實現Session的唯一手段,在URL中通過參數來保存Session ID同樣也是可行的。

用戶登錄

在註冊用戶後,用戶需要登錄網站來確認自己的身份,登錄的方式就是在HTML表單中填入用戶名和密碼並提交給服務器進行驗證。在瀏覽器的一次Session會話中,Cookie中的JSESSIONID保持不變,所以登錄成功(服務器端通過驗證)後,我們可以在服務端Session中放入當前的用戶對象,這樣以後訪問任何URL,在對應的JSP/Servlet中都能輕易的拿到它。以下是一個多用戶訪問的Web應用示意圖:

Clipboard Image.png

可以看到,每一個用戶都有一個自己的Session。而在JSP/Servlet中,通過API可以很容易的獲取到當前用戶的Session並將狀態信息放入其中。

在Servlet中設置Session屬性值

以用戶登錄爲例:

@WebServlet("/account/login")
public class LoginController extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        User user = Data.getByUsername(username);
        if (user == null || !user.getPassword().equals(password)) {
            //登錄不成功,返回錯誤信息
        } else {
            req.getSession().setAttribute("user", user);
            //返回登錄成功信息
        }
    }
}

獲取Session的API是req.getSession(),接下來通過getAttribute()setAttribute()方法就可以像一個Map一樣獲取/設置Session的屬性值。這裏我們將User對象放入了Session,名字爲user

在JSP中獲取Session屬性值

在渲染頁面時,很多時候需要獲取當前用戶的信息,例如導航欄的顯示:

  1. 根據用戶是否登錄顯示不同的信息
  2. 如果用戶已經登錄,需要根據當前用戶信息顯示某些內容——例如“我的首頁”的鏈接

在JSP Scriptlet中可以通過session對象來獲取Session的屬性值,如果使用EL表達式,那麼需要使用${sessionScope}得到Session對象,下面以導航欄爲例:

<div id="navbar" class="navbar-collapse collapse">
  <ul class="nav navbar-nav">
  <c:choose>
    <c:when test="${sessionScope.user != null}">
    <li><a href="userPosts?username=${sessionScope.user.username}">我的首頁</a></li>
    <li class="dropdown">
      <a href="#" class="dropdown-toggle" data-toggle="dropdown">管理
        <span class="caret"></span></a>
      <ul class="dropdown-menu">
        <li class="dropdown-header">博客</li>
        <li><a href="#">博客信息</a></li>
        <li><a href="createPost">創建博文</a></li>
        <li><a href="admin/posts">博客文章</a></li>
        <li class="divider"></li>
        <li class="dropdown-header">賬號</li>
        <li><a href="#">個人信息</a></li>
        <li><a href="#">更改密碼</a></li>
        <li><a href="account/logout">退出</a></li>
      </ul>
    </li>
    <li><a href="account/logout">退出登錄</a></li>
    </c:when>
    <c:otherwise>
    <li><a href="account/login">登錄</a>
    <li><a href="jsp/register.jsp">註冊</a>
    </c:otherwise>
  </c:choose>
  </ul>
</div>

${sessionScope.user != null}作爲判斷條件表示當前用戶已經登錄;href="userPosts?username=${sessionScope.user.username}"則是利用當前用戶的屬性值渲染鏈接。

發佈了12 篇原創文章 · 獲贊 13 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章