首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Cookie、Session 與 Filter 整合實作 Java Web 登入驗證機制

     編輯:Java 相關知識

前言

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            # 部署描述檔

關鍵元件實作

1. 用戶模型(單例模擬資料庫)

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);
    }
}

2. 登入頁面

<%@ 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>

3. 登入 Servlet - 核心邏輯

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);
        }
    }
}

4. 權限過濾器 - 核心防護

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");
    }
}

5. 受保護資源 Servlet

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>");
    }
}

6. 登出 Servlet(補充)

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");
    }
}

7. web.xml 完整配置

<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>

安全性補強建議(生產環境必做)

  • 密碼加密:資料庫儲存 BCrypt/Argon2 雜湊,Cookie 存 Token 而非明文密碼
  • HTTPS 強制:所有登入/Session 傳輸必須走 TLS,設定 Cookie Secure、HttpOnly、SameSite=Strict
  • CSRF 保護:表單隱藏 Token,Filter 驗證雙重提交
  • Session 固定攻擊防範:登入成功後 session.invalidate() 再 req.getSession(true) 產生新 ID
  • 暴力破解防禦:登入失敗計數 + 驗證碼 + IP 限流

測試流程

  1. 訪問 / → 導向登入頁
  2. 輸入 admin/123456,勾選記住我 → 登入成功進入用戶列表
  3. 關閉瀏覽器重新開啟 → 自動登入(Cookie 生效)
  4. 點擊登出 → Session 與 Cookie 雙清 → 回登入頁
  5. 未登入直接訪問 /userList → 被 Filter 攔截導回登入頁
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved