1. 程式人生 > >Java中的會話管理——HttpServlet,Cookies,URL Rewriting(譯)

Java中的會話管理——HttpServlet,Cookies,URL Rewriting(譯)

資源 gets where pre 點擊 相關 商品 另一個 格林尼治

參考谷歌翻譯,關鍵字直接使用英文,原文地址:http://www.journaldev.com/1907/java-session-management-servlet-httpsession-url-rewriting

Java Web應用程序中的會話管理(Session Management是一個非常有趣的話題。Java Servlet中的會話通過不同的方式進行管理,例如Cookie,HttpSession API,URL重寫等。

這是Java Web應用程序系列教程中的第三篇文章,您可能還需要先閱讀兩篇文章。

Java中的會話管理

本文旨在使用不同的技術和示例程序解釋servlet中的會話管理。

  1. 什麽是Session?
  2. Java中的會話管理——Cookie
  3. Java Servlet中的Session——HttpSession
  4. Java Servlet中的會話管理——URL重寫

1、什麽是Session?

HTTP協議和Web服務器都是無狀態的,這意味著對Web服務器而言,每個請求都是一個新的進程,它無法確定請求是否來自先前發送過請求的客戶端。
但有時候,我們需要知道客戶端是誰,並且相應地處理請求。例如,購物車應用程序應該知道誰正在發送添加商品的請求,誰正在發送結帳的請求,以便可以無誤地進行收費或者把商品添加到正確的購物車中。

Session是客戶端和服務器之間的會話狀態,它可以由客戶端和服務器之間的多個請求和響應組成。由於HTTP和Web服務器都是無狀態的,維護會話的唯一方法是在每個請求和響應中傳遞關於會話的唯一信息(session id)
我們可以通過以下幾種方式在請求和響應中提供唯一的標識符。

  1. 用戶認證——這是一種很常用的方法,用戶從登錄頁面提供認證憑證,然後我們可以在服務器和客戶端之間傳遞認證信息來維護會話。但是,這並非是一個非常有效的方法,因為一旦相同的用戶從不同的瀏覽器登錄,它將無法正常地工作。
  2. HTML隱藏字段(HTML Hidden Field)——我們可以在HTML中創建一個唯一的隱藏字段,當用戶開始導航時,把它設置為對用戶的唯一值,並跟蹤會話。這種方法不能與超鏈接一起使用,因為它要求每次從客戶端提交的表單都具備隱藏字段。此外,它不夠安全,因為我們可以從HTML源代碼中獲得隱藏的字段值,並利用它來破壞會話。
  3. URL重寫(URL Rewriting)——我們可以給每個請求和響應附加會話標識符參數,以跟蹤會話。這是非常繁瑣的,因為我們需要在每個響應中跟蹤這個參數,並確保它不會與其他參數沖突。
  4. Cookie——Cookie是由Web服務器發送,並存儲在瀏覽器中的小塊信息。當進一步請求時,瀏覽器將Cookie添加到Request Header中,我們可以利用它來跟蹤會話。但如果客戶端禁用cookies,那麽它將不起作用。
  5. Session Management API——會話管理API是基於上述方法構建的,用於會話跟蹤。上述方法的一些主要缺點是:1)、大多數時候,我們不僅要跟蹤會話,還需要將一些數據存儲到會話中,以便在將來的請求中使用。如果我們試圖實現這一點,需要付出很多努力。2)、所有上述方法本身都不完整,所有這些方法都會在特定情況下不起作用。因此,我們需要一種解決方案,可以利用這個方法在所有情況下提供會話管理。

這就是為什麽我們需要Session Management API,以及為什麽J2EE技術附帶了Session Management API供我們使用。

2、Java中的會話管理——Cookie

在Web應用程序中Cookie應用得很多,它可以根據你的選擇來個性化響應或跟蹤會話。在轉到Session Management API之前,我想通過一個小型Web應用程序來展示如何利用Cookie跟蹤會話。

我們將創建一個具有如下圖所示項目結構的動態Web應用程序ServletCookieExample

(原文ide使用的是Eclipse,使用Intellij Idea的朋友可以參考Intellij Idea 創建Web項目入門 by 我是一名老菜鳥)

技術分享

web應用程序的部署描述文件web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <display-name>ServletCookieExample</display-name>
    <welcome-file-list>
        <welcome-file>login.html</welcome-file>
    </welcome-file-list>
</web-app>

我們的應用程序的歡迎頁面是login.html,我們將從用戶那裏獲取身份驗證的詳細信息。

<!DOCTYPE html>
<html>
<head>
    <meta charset="US-ASCII">
    <title>Login Page</title>
</head>
<body>

<form action="LoginServlet" method="post">

    Username: <input type="text" name="user">
    <br>
    Password: <input type="password" name="pwd">
    <br>
    <input type="submit" value="Login">
</form>
</body>
</html>

這是一個負責處理“登錄請求”的LoginServlet。

package com.journaldev.servlet.session;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class LoginServlet
 */
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private final String userID = "Pankaj";
    private final String password = "journaldev";

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) throws ServletException, IOException {

        // get request parameters for userID and password
        String user = request.getParameter("user");
        String pwd = request.getParameter("pwd");

        if(userID.equals(user) && password.equals(pwd)){
            Cookie loginCookie = new Cookie("user",user);
            //setting cookie to expiry in 30 mins
            loginCookie.setMaxAge(30*60);
            response.addCookie(loginCookie);
            response.sendRedirect("LoginSuccess.jsp");
        }else{
            RequestDispatcher rd = getServletContext().getRequestDispatcher("/login.html");
            PrintWriter out= response.getWriter();
            out.println("<font color=red>Either user name or password is wrong.</font>");
            rd.include(request, response);
        }

    }

}

請註意我們設置在response中的cookie,它在之後將被轉發到LoginSuccess.jsp,此cookie將用於跟蹤會話。還要註意,“cookie超時”設置為30分鐘。理想情況下,應該有一個復雜的邏輯來設置跟蹤會話的cookie值,以便它不會與別的請求相沖突。

<%@ page language="java" contentType="text/html; charset=US-ASCII"
         pageEncoding="US-ASCII"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
    <title>Login Success Page</title>
</head>
<body>
<%
    String userName = null;
    Cookie[] cookies = request.getCookies();
    if(cookies !=null){
        for(Cookie cookie : cookies){
            if(cookie.getName().equals("user")) userName = cookie.getValue();
        }
    }
    if(userName == null) response.sendRedirect("login.html");
%>
<h3>Hi <%=userName %>, Login successful.</h3>
<br>
<form action="LogoutServlet" method="post">
    <input type="submit" value="Logout" >
</form>
</body>
</html>

請註意,如果嘗試直接訪問JSP,它將自動把我們轉到“登錄頁面”。當我們點擊註銷按鈕時,應該確保cookie已經從瀏覽器中刪除。

package com.journaldev.servlet.session;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class LogoutServlet
 */
@WebServlet("/LogoutServlet")
public class LogoutServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        Cookie loginCookie = null;
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
            for(Cookie cookie : cookies){
                if(cookie.getName().equals("user")){
                    loginCookie = cookie;
                    break;
                }
            }
        }
        if(loginCookie != null){
            loginCookie.setMaxAge(0);
            response.addCookie(loginCookie);
        }
        response.sendRedirect("login.html");
    }

}

沒有方法可以刪除cookie,但是我們可以將cookie的maximum age設置為0,它將立刻被瀏覽器移除。

當我們運行上面的應用程序時,將得到如下響應。

技術分享

技術分享

3、Java Servlet中的Session——HttpSession

Servlet API通過Http Session接口提供會話管理。我們可以使用以下方法從HttpServletRequest對象中獲取session。HttpSession允許我們將對象設置為屬性,以便在將來的請求中能夠檢索。

  1. HttpSession getSession() – 這個方法總是返回一個HttpSession對象,如果HttpServletRequest對象沒有與session關聯,則創建一個新的session返回。
  2. HttpSession getSession(boolean flag) – 返回一個HttpSession對象,如果HttpServletRequest對象沒有與session關聯就返回null

HttpSession的一些重要方法是:

  1. String getId() – 返回一個字符串,這個字符串包含了該session的唯一標識符。
  2. Object getAttribute(String name) – 返回在session中用指定名稱綁定的對象,如果沒有對象綁定在該名稱下,則返回null。其它與會話屬性相關的方法是 getAttributeNames(), removeAttribute(String name)setAttribute(String name, Object value).
  3. long getCreationTime() – 返回此session創建的時間,起始時間是1970年1月1日GMT格林尼治時間,單位為毫秒。我們可以用getLastAccessedTime()方法獲得最後訪問的時間
  4. setMaxInactiveInterval(int interval) – 用來指定“session超時值”,以秒為單位,servlet容器將會在這段時間內保持session有效,我們可以用getMaxInactiveInterval()方法獲取“session超時值”。
  5. ServletContext getServletContext() – 返回Web應用的ServletContext對象。
  6. boolean isNew() – 如果客戶端還不知道會話或者客戶端選擇不加入會話,則返回true。
  7. void invalidate() – 使這個對話無效,並且解除所有綁定的對象。

理解JSESSIONID Cookie

當使用HttpServletRequest的getSession()方法時,我們將獲得一個新的HttpSession對象,與此同時,一個特殊的Cookie將被添加到對應的Response對象,這個Cookie名字叫做JSESSIONID,它的值就是“session id這個cookie用於在客戶端的進一步請求中識別HttpSession對象。如果Cookie在客戶端被禁用,並且我們正在使用URL重寫,則該方法使用request URL中的JSESSIONID值來查找相應的會話。JSESSIONID cookie用於會話跟蹤,因此我們不應該將其用於我們的應用程序,以避免造成任何會話相關的問題。

讓我們看看使用HttpSession對象的會話管理示例。我們將在Eclipse中創建一個動態Web項目,項目結構如下圖所示。技術分享

login.html與前面的示例一樣,並定義應用程序的歡迎頁面。

LoginServlet servlet將創建session,並在其中設置我們可以在其他資源或將來請求中使用的屬性

package com.journaldev.servlet.session;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class LoginServlet
 */
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private final String userID = "admin";
    private final String password = "password";

    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

        // get request parameters for userID and password
        String user = request.getParameter("user");
        String pwd = request.getParameter("pwd");
        
        if(userID.equals(user) && password.equals(pwd)){
            HttpSession session = request.getSession();
            session.setAttribute("user", "Pankaj");
            //setting session to expiry in 30 mins
            session.setMaxInactiveInterval(30*60);
            Cookie userName = new Cookie("user", user);
            userName.setMaxAge(30*60);
            response.addCookie(userName);
            response.sendRedirect("LoginSuccess.jsp");
        }else{
            RequestDispatcher rd = getServletContext().getRequestDispatcher("/login.html");
            PrintWriter out= response.getWriter();
            out.println("<font color=red>Either user name or password is wrong.</font>");
            rd.include(request, response);
        }

    }

}

我們的LoginSuccess.jsp 如下所示。

<%@ page language="java" contentType="text/html; charset=US-ASCII"
    pageEncoding="US-ASCII"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Login Success Page</title>
</head>
<body>
<%
//allow access only if session exists
String user = null;
if(session.getAttribute("user") == null){
    response.sendRedirect("login.html");
}else user = (String) session.getAttribute("user");
String userName = null;
String sessionID = null;
Cookie[] cookies = request.getCookies();
if(cookies !=null){
for(Cookie cookie : cookies){
    if(cookie.getName().equals("user")) userName = cookie.getValue();
    if(cookie.getName().equals("JSESSIONID")) sessionID = cookie.getValue();
}
}
%>
<h3>Hi <%=userName %>, Login successful. Your Session ID=<%=sessionID %></h3>
<br>
User=<%=user %>
<br>
<a href="CheckoutPage.jsp">Checkout Page</a>
<form action="LogoutServlet" method="post">
<input type="submit" value="Logout" >
</form>
</body>
</html>

當使用JSP資源時,容器會自動為其創建一個session,所以我們不能檢查會話是否為空,來確保用戶是否通過登錄頁面,因此我們使用session的屬性來驗證請求。

CheckoutPage.jsp是另外一個頁面,它的代碼如下所示。

<%@ page language="java" contentType="text/html; charset=US-ASCII"
    pageEncoding="US-ASCII"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Login Success Page</title>
</head>
<body>
<%
//allow access only if session exists
if(session.getAttribute("user") == null){
    response.sendRedirect("login.html");
}
String userName = null;
String sessionID = null;
Cookie[] cookies = request.getCookies();
if(cookies !=null){
for(Cookie cookie : cookies){
    if(cookie.getName().equals("user")) userName = cookie.getValue();
}
}
%>
<h3>Hi <%=userName %>, do the checkout.</h3>
<br>
<form action="LogoutServlet" method="post">
<input type="submit" value="Logout" >
</form>
</body>
</html>

我們的LogoutServlet代碼如下所示。

package com.journaldev.servlet.session;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class LogoutServlet
 */
@WebServlet("/LogoutServlet")
public class LogoutServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
        for(Cookie cookie : cookies){
            if(cookie.getName().equals("JSESSIONID")){
                System.out.println("JSESSIONID="+cookie.getValue());
                break;
            }
        }
        }
        //invalidate the session if exists
        HttpSession session = request.getSession(false);
        System.out.println("User="+session.getAttribute("user"));
        if(session != null){
            session.invalidate();
        }
        response.sendRedirect("login.html");
    }

}

請註意,我在日誌中打印了JSESSIONID cookie的值,你可以檢查服務器日誌在哪裏打印了與LoginSuccess.jsp中的Session id相同的值。

下圖顯示了我們的Web應用程序的執行。

技術分享

技術分享

技術分享

4、Java Servlet中的會話管理 - URL重寫

正如我們在上一節中看到的,我們可以使用HttpSession來管理一個會話,但是如果我們在瀏覽器中禁用了cookies,它將不起作用,因為服務器將不會從客戶端收到JSESSIONID cookie。Servlet API提供了對URL重寫的支持,以便我們可以在這種情況下管理會話。

從編碼的角度來看,它很容易使用,它涉及到了一個步驟 - 編碼URL(encoding the URL)。編碼URL的另一個好處是它是一種“後備方法”,只有在瀏覽器Cookie被禁用的情況下才會啟用。

我們可以使用HttpServletResponse的 encodeURL()方法對URL進行編碼,如果我們必須將請求重定向到另一個資源,並且提供會話信息,那麽我們可以使用encodeRedirectURL()方法。

我們將創建一個類似的項目,這個項目將使用URL重寫來確保會話管理工作正常(即使瀏覽器禁用了cookies)。

技術分享

package com.journaldev.servlet.session;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class LoginServlet
 */
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private final String userID = "admin";
    private final String password = "password";

    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

        // get request parameters for userID and password
        String user = request.getParameter("user");
        String pwd = request.getParameter("pwd");
        
        if(userID.equals(user) && password.equals(pwd)){
            HttpSession session = request.getSession();
            session.setAttribute("user", "Pankaj");
            //setting session to expiry in 30 mins
            session.setMaxInactiveInterval(30*60);
            Cookie userName = new Cookie("user", user);
            response.addCookie(userName);
            //Get the encoded URL string
            String encodedURL = response.encodeRedirectURL("LoginSuccess.jsp");
            response.sendRedirect(encodedURL);
        }else{
            RequestDispatcher rd = getServletContext().getRequestDispatcher("/login.html");
            PrintWriter out= response.getWriter();
            out.println("<font color=red>Either user name or password is wrong.</font>");
            rd.include(request, response);
        }

    }

}
<%@ page language="java" contentType="text/html; charset=US-ASCII"
    pageEncoding="US-ASCII"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Login Success Page</title>
</head>
<body>
<%
//allow access only if session exists
String user = null;
if(session.getAttribute("user") == null){
    response.sendRedirect("login.html");
}else user = (String) session.getAttribute("user");
String userName = null;
String sessionID = null;
Cookie[] cookies = request.getCookies();
if(cookies !=null){
for(Cookie cookie : cookies){
    if(cookie.getName().equals("user")) userName = cookie.getValue();
    if(cookie.getName().equals("JSESSIONID")) sessionID = cookie.getValue();
}
}else{
    sessionID = session.getId();
}
%>
<h3>Hi <%=userName %>, Login successful. Your Session ID=<%=sessionID %></h3>
<br>
User=<%=user %>
<br>
<!-- need to encode all the URLs where we want session information to be passed -->
<a href="<%=response.encodeURL("CheckoutPage.jsp") %>">Checkout Page</a>
<form action="<%=response.encodeURL("LogoutServlet") %>" method="post">
<input type="submit" value="Logout" >
</form>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=US-ASCII"
    pageEncoding="US-ASCII"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Login Success Page</title>
</head>
<body>
<%
String userName = null;
//allow access only if session exists
if(session.getAttribute("user") == null){
    response.sendRedirect("login.html");
}else userName = (String) session.getAttribute("user");
String sessionID = null;
Cookie[] cookies = request.getCookies();
if(cookies !=null){
for(Cookie cookie : cookies){
    if(cookie.getName().equals("user")) userName = cookie.getValue();
}
}
%>
<h3>Hi <%=userName %>, do the checkout.</h3>
<br>
<form action="<%=response.encodeURL("LogoutServlet") %>" method="post">
<input type="submit" value="Logout" >
</form>
</body>
</html>
package com.journaldev.servlet.session;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class LogoutServlet
 */
@WebServlet("/LogoutServlet")
public class LogoutServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
        for(Cookie cookie : cookies){
            if(cookie.getName().equals("JSESSIONID")){
                System.out.println("JSESSIONID="+cookie.getValue());
            }
            cookie.setMaxAge(0);
            response.addCookie(cookie);
        }
        }
        //invalidate the session if exists
        HttpSession session = request.getSession(false);
        System.out.println("User="+session.getAttribute("user"));
        if(session != null){
            session.invalidate();
        }
        //no encoding because we have invalidated the session
        response.sendRedirect("login.html");
    }

}

當我們在禁用Cookie的狀況下運行此項目時,將顯示如下響應頁面,請註意瀏覽器地址欄URL中的jsessionid。另外請註意,在LoginSuccess頁面上,用戶名是null,因為瀏覽器沒有發送最後一個響應所發送的cookie。

技術分享

技術分享

技術分享

如果Cookie未被禁用,你將不會在URL中看到jsessionid,因為Servlet Session API會在這種情況下使用cookie。

原文地址:http://www.journaldev.com/1907/java-session-management-servlet-httpsession-url-rewriting

Java中的會話管理——HttpServlet,Cookies,URL Rewriting(譯)