「記住我」自動登入是 Web 應用的標配功能。本文基於 Struts2 框架,展示如何結合 Cookie、Session、Service 層解密校驗、攔截器思路 實現自動登入,並整合簡單的圖形驗證碼防暴力破解。示例包含完整分層代碼:DAO、Service、Action、JSP、配置文件。
autoLogin() 讀取 Cookie → 解密密碼 → 調用 Service 驗證 → 成功則寫 Session 並返回 "success",否則返回 "login"。# db.properties
dbDriver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:orcl
userName=test
password=password
package com.struts.dao.impl;
import java.sql.*;
import com.struts.dao.UserDao;
import com.struts.proj.User;
import com.struts.util.BeanConnection;
public class UserDaoImpl implements UserDao {
private BeanConnection dbconn = new BeanConnection();
@Override
public User login(String loginname) {
String sql = "SELECT * FROM t_scoa_sys_user WHERE f_loginname = ?";
try (Connection conn = dbconn.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, loginname);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
User u = new User();
u.setId(rs.getLong(1));
u.setF_username(rs.getString(2));
u.setF_loginname(rs.getString(3));
u.setF_sex(rs.getString(4));
u.setF_state(rs.getString(5));
u.setF_email(rs.getString(6));
u.setF_mobilephone(rs.getString(7));
u.setF_secretaryid(rs.getLong(8));
u.setF_password(rs.getString(9)); // 已加密存儲
u.setF_order(rs.getLong(10));
u.setF_note(rs.getString(11));
u.setF_infomodifytemplateid(rs.getLong(12));
return u;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
// save() 略
}
package com.struts.util;
import javax.servlet.http.*;
import org.apache.struts2.ServletActionContext;
import com.struts.action.LoginAction;
import com.struts.proj.User;
import com.struts.service.UserService;
import com.struts.service.impl.UserServiceImpl;
public class CookieUtils {
public static final String USER_COOKIE = "user.cookie";
// 生成 Cookie:值 = loginname + "," + 解密後密碼(示例為明文,生產必須再加密)
public Cookie addCookie(User user) {
Cookie cookie = new Cookie(USER_COOKIE,
user.getF_loginname() + "," + DESEDE.decryptIt(user.getF_password()));
cookie.setMaxAge(60 * 60 * 24 * 365); // 1 年
cookie.setPath("/"); // 全路徑有效
return cookie;
}
// 讀取並驗證 Cookie
public boolean getCookie(HttpServletRequest request, UserService userService) {
Cookie[] cookies = request.getCookies();
if (cookies == null) return false;
for (Cookie c : cookies) {
if (USER_COOKIE.equals(c.getName())) {
String[] parts = c.getValue().split(",");
if (parts.length == 2) {
String loginname = parts[0];
String password = parts[1]; // 已解密
User user = userService.login(loginname, password);
if (user != null) {
request.getSession().setAttribute(LoginAction.USER_SESSION, user);
return true;
}
}
}
}
return false;
}
// 刪除 Cookie
public Cookie delCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if (USER_COOKIE.equals(c.getName())) {
c.setValue("");
c.setMaxAge(0);
c.setPath("/");
return c;
}
}
}
return null;
}
}
package com.struts.service.impl;
import com.struts.dao.UserDao;
import com.struts.dao.impl.UserDaoImpl;
import com.struts.proj.User;
import com.struts.service.UserService;
import com.struts.util.DESEDE;
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public User login(String loginname, String password) {
User user = userDao.login(loginname);
if (user == null) {
System.out.println("用戶不存在: " + loginname);
return null;
}
// 數據庫存儲的是加密密碼,解密後與輸入明文比對
if (!DESEDE.decryptIt(user.getF_password()).equals(password)) {
System.out.println("密碼錯誤: " + loginname);
return null;
}
return user;
}
}
/success.jsp
/error.jsp
/login.jsp
60
20
18
4
image/jpeg
inputStream
package com.struts.action;
import javax.servlet.http.*;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.struts.proj.User;
import com.struts.service.UserService;
import com.struts.service.impl.UserServiceImpl;
import com.struts.util.CookieUtils;
import com.struts.util.DESEDE;
public class LoginAction extends ActionSupport {
private static final long serialVersionUID = 1L;
public static final String USER_SESSION = "user.session";
// 表單字段
private String f_loginname;
private String f_password;
private boolean userCookie; // 記住我
private String validateCode; // 驗證碼輸入
private CookieUtils cookieUtils = new CookieUtils();
private UserService userService = new UserServiceImpl();
// === 自動登入入口 ===
public String autoLogin() {
HttpServletRequest request = ServletActionContext.getRequest();
if (cookieUtils.getCookie(request, userService)) {
return "success";
}
return "login";
}
// === 手動登入 ===
@Override
public String execute() {
HttpSession session = ServletActionContext.getRequest().getSession();
// 1. 校驗驗證碼
String code = (String) session.getAttribute("validateCode");
if (validateCode == null || !validateCode.equalsIgnoreCase(code)) {
addActionError("驗證碼錯誤");
return "error";
}
// 2. 校驗用戶名密碼
if (f_loginname != null && !f_loginname.isEmpty()
&& f_password != null && !f_password.isEmpty()) {
User user = userService.login(f_loginname, f_password);
if (user != null) {
// 3. 記住我 → 寫 Cookie
if (userCookie) {
Cookie cookie = cookieUtils.addCookie(user);
ServletActionContext.getResponse().addCookie(cookie);
}
// 4. 寫 Session
session.setAttribute(USER_SESSION, user);
return "success";
}
}
addActionError("用戶名或密碼錯誤");
return "login";
}
// === 登出 ===
public String logout() {
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
HttpSession session = request.getSession(false);
if (session != null) session.removeAttribute(USER_SESSION);
Cookie cookie = cookieUtils.delCookie(request);
if (cookie != null) response.addCookie(cookie);
return "login";
}
// getter/setter 略
}
package com.struts.action;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Random;
import javax.imageio.ImageIO;
import com.opensymphony.xwork2.ActionSupport;
public class ValidateCodeAction extends ActionSupport {
private ByteArrayInputStream inputStream;
private int width = 60, height = 20, fontSize = 18, codeLength = 4;
@Override
public String execute() {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
Random r = new Random();
// 背景
g.setColor(getRandomColor(r, 200, 255));
g.fillRect(0, 0, width, height);
// 干擾線
g.setFont(new Font("Times New Roman", Font.PLAIN, fontSize));
g.setColor(getRandomColor(r, 160, 200));
for (int i = 0; i < 155; i++) {
int x = r.nextInt(width), y = r.nextInt(height);
g.drawLine(x, y, x + r.nextInt(12), y + r.nextInt(12));
}
// 驗證碼字符
StringBuilder sb = new StringBuilder();
for (int i = 0; i < codeLength; i++) {
String ch = String.valueOf(r.nextInt(10));
sb.append(ch);
g.setColor(new Color(20 + r.nextInt(110), 20 + r.nextInt(110), 20 + r.nextInt(110)));
g.drawString(ch, (width / codeLength) * i + r.nextInt(width / (codeLength * 2)),
r.nextInt(height - fontSize) + fontSize);
}
// 存入 Session
ServletActionContext.getRequest().getSession().setAttribute("validateCode", sb.toString());
// 輸出圖片流
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
ImageIO.write(img, "JPEG", ImageIO.createImageOutputStream(bos));
inputStream = new ByteArrayInputStream(bos.toByteArray());
} catch (Exception e) {
e.printStackTrace();
}
g.dispose();
return "success";
}
private Color getRandomColor(Random r, int fc, int bc) {
if (fc > 255) fc = 255;
if (bc > 255) bc = 255;
int c = fc + r.nextInt(bc - fc);
return new Color(c, c, c);
}
// getter/setter 略
}
<%@ taglib prefix="s" uri="/struts-tags"%>
登入
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ page import="com.struts.util.CookieUtils" %>
<%@ page import="org.apache.commons.lang3.StringUtils" %>
<%
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if (CookieUtils.USER_COOKIE.equals(c.getName()) && StringUtils.isNotBlank(c.getValue())) {
String[] p = c.getValue().split(",");
out.println(p[0] + " 歡迎回來!
");
}
}
}
%>
安全退出
cookie.setHttpOnly(true); cookie.setSecure(true); 防 XSS、強制 HTTPS。本示例展示了 Struts2 下Cookie 自動登入 + 驗證碼的完整鏈路:
CookieUtils 封裝 Cookie 讀寫刪,業務邏輯清晰。LoginAction.autoLogin() 作為入口攔截,配合 Struts2 攔截器可進一步抽象為全局自動登入攔截器。理解此流程,遷移至 Spring MVC / Spring Boot 只需替換 Controller 註解與依賴注入方式,核心邏輯完全復用。