HTTP 無狀態特性使得 Web 應用必須依賴 Cookie(客戶端)、Session(伺服器端)與 Filter(攔截器)三大機制協作,才能建立完整的登入驗證體系。本文透過一個完整可執行的範例,示範如何整合這三者實現:登入驗證、自動登入(記住我)、權限攔截、登出清理。
[瀏覽器] --HTTP Request (Cookie)--> [Filter 驗證] --通過?--> [Servlet 業務邏輯]
<--HTTP Response (Set-Cookie/Session)--- [登入/登出 Servlet] <--
src/
├── models/UserModel.java # 單例模擬用戶資料
├── servletTest/
│ ├── LoginServlet.java # 登入處理
│ └── UserListServlet.java # 受保護資源
├── filterTest/FilterTest.java # 權限攔截器
└── WebContent/
├── index.jsp # 登入頁
└── WEB-INF/web.xml # 部署描述檔
package models;
public class UserModel {
private String userName = "admin";
private String password = "123456";
private static final UserModel INSTANCE = new UserModel();
private UserModel() {}
public static UserModel getInstance() { return INSTANCE; }
public String getUserName() { return userName; }
public String getPassword() { return password; }
public boolean validate(String username, String pwd) {
return userName.equals(username) && password.equals(pwd);
}
}
<%@ page contentType="text/html;charset=UTF-8" %>
<html><body>
<h2>使用者登入</h2>
<form action="<%=request.getContextPath()%>/loginServlet" method="post">
帳號:<input name="userName" required><br>
密碼:<input type="password" name="password" required><br>
<input type="checkbox" name="remember"> 記住我 (7天)<br>
<button type="submit">登入</button>
</form>
</body></html>
package servletTest;
import models.UserModel;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String username = req.getParameter("userName");
String password = req.getParameter("password");
boolean remember = "on".equals(req.getParameter("remember"));
UserModel user = UserModel.getInstance();
if (user.validate(username, password)) {
// 1. 建立 Session
HttpSession session = req.getSession(true);
session.setAttribute("loginUser", username);
session.setMaxInactiveInterval(30 * 60); // 30 分鐘
// 2. 記住我:設定 Cookie(7 天)
if (remember) {
Cookie userCookie = new Cookie("autoUser", username);
Cookie pwdCookie = new Cookie("autoPwd", password); // 實務應加密/存 token
userCookie.setMaxAge(7 * 24 * 3600);
pwdCookie.setMaxAge(7 * 24 * 3600);
userCookie.setPath(req.getContextPath());
pwdCookie.setPath(req.getContextPath());
resp.addCookie(userCookie);
resp.addCookie(pwdCookie);
}
// 3. 導向受保護頁面
req.getRequestDispatcher("/userList").forward(req, resp);
} else {
req.setAttribute("error", "帳號或密碼錯誤");
req.getRequestDispatcher("/index.jsp").forward(req, resp);
}
}
}
package filterTest;
import models.UserModel;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class FilterTest implements Filter {
private static final String[] WHITE_LIST = {
"/loginServlet", "/index.jsp", "/", "/favicon.ico"
};
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String uri = req.getRequestURI();
String ctx = req.getContextPath();
String path = uri.substring(ctx.length()); // 相對路徑
// 1. 白名單直接放行
for (String white : WHITE_LIST) {
if (path.equals(white) || path.startsWith("/static/")) {
chain.doFilter(request, response);
return;
}
}
// 2. 檢查 Session
HttpSession session = req.getSession(false);
if (session != null && session.getAttribute("loginUser") != null) {
chain.doFilter(request, response);
return;
}
// 3. 嘗試 Cookie 自動登入
Cookie[] cookies = req.getCookies();
if (cookies != null) {
String autoUser = null, autoPwd = null;
for (Cookie c : cookies) {
if ("autoUser".equals(c.getName())) autoUser = c.getValue();
if ("autoPwd".equals(c.getName())) autoPwd = c.getValue();
}
if (autoUser != null && autoPwd != null
&& UserModel.getInstance().validate(autoUser, autoPwd)) {
// 自動登入成功,重建 Session
HttpSession newSession = req.getSession(true);
newSession.setAttribute("loginUser", autoUser);
chain.doFilter(request, response);
return;
}
}
// 4. 全部失敗,重導登入頁
resp.sendRedirect(ctx + "/index.jsp");
}
}
package servletTest;
public class UserListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
String user = (String) req.getSession().getAttribute("loginUser");
resp.getWriter().write("<h3>歡迎:" + user + "</h3><a href='/logout'>登出</a>");
}
}
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
// 清除 Session
HttpSession session = req.getSession(false);
if (session != null) session.invalidate();
// 清除 Cookie
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if ("autoUser".equals(c.getName()) || "autoPwd".equals(c.getName())) {
c.setMaxAge(0);
c.setPath(req.getContextPath());
resp.addCookie(c);
}
}
}
resp.sendRedirect(req.getContextPath() + "/index.jsp");
}
}
<web-app version="3.0" xmlns="https://java.sun.com/xml/ns/javaee">
<display-name>AuthDemo</display-name>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>servletTest.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/loginServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>userList</servlet-name>
<servlet-class>servletTest.UserListServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>userList</servlet-name>
<url-pattern>/userList</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>logout</servlet-name>
<servlet-class>servletTest.LogoutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>logout</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
<filter>
<filter-name>authFilter</filter-name>
<filter-class>filterTest.FilterTest</filter-class>
</filter>
<filter-mapping>
<filter-name>authFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Secure、HttpOnly、SameSite=Strictsession.invalidate() 再 req.getSession(true) 產生新 ID/ → 導向登入頁/userList → 被 Filter 攔截導回登入頁