Java Web实现“十天内免登录”功能

发布于:2025-09-14 ⋅ 阅读:(22) ⋅ 点赞:(0)

Java Web实现“十天内免登录”功能

一、 功能原理

“十天内免登录”的本质是在客户端(浏览器)持久化一个加密的、唯一的身份凭证,从而让服务器在用户关闭浏览器再次打开时,依然能够识别出他的身份。

其核心流程如下图所示:

flowchart TD
    A[用户登录] --> B{选择'十天内免登录'?}
    B -- 是 --> C[服务器生成Token<br>(用户名+随机数+过期时间)]
    B -- 否 --> D[结束]
    C --> E[Token与用户关联并存储至数据库]
    C --> F[Token加密后写入用户浏览器Cookie]
    E --> G[完成登录]
    F --> G

    H[用户再次访问] --> I[服务器读取Cookie中的Token]
    I --> J{Token验证是否有效?<br>(存在、未过期、匹配)}
    J -- 无效或不存在 --> K[视为未登录]
    J -- 有效 --> L[自动登录<br>并根据Token查询对应用户]
    L --> M[为用户创建登录会话Session]
    M --> N[完成自动登录]

二、 实现步骤(基于Token方案)

这是一种安全且常用的方案。

  1. 数据库准备

    • 在用户表中添加两个字段(或使用单独的表):
      • remember_token (VARCHAR):用于存放唯一的令牌字符串。
      • token_validity (DATETIME/TIMESTAMP):用于存放令牌的过期时间。
  2. 登录成功后的处理

    • 用户登录时,如果勾选了“十天内免登录”,服务器除了正常创建Session之外,还需要:
      • 生成一个唯一且加密安全的随机令牌 (Token)。
      • 计算过期时间(如:当前时间 + 10天)。
      • 将令牌和过期时间与当前用户关联,并存入数据库
      • 将令牌和用户名(或用户ID)通过Cookie发送给客户端浏览器
  3. 自动登录的过滤与验证

    • 编写一个Filter或Interceptor,在用户访问网站时进行拦截检查。
    • 检查顺序
      1. 检查请求中是否已有有效的Session?如果有,直接放行。
      2. 如果没有Session,则检查客户端是否携带了“记住我”的Cookie。
      3. 如果找到了Cookie,则从中解析出令牌和用户名。
      4. 根据用户名/用户ID到数据库中查找有效的令牌和过期时间。
      5. 验证:令牌是否存在、是否匹配、是否未过期。
      6. 如果验证通过:则自动为用户创建一个新的Session,视为已登录,然后放行。
      7. 如果验证失败:则删除客户端无效的Cookie,并让其保持未登录状态。
  4. 退出登录的处理

    • 用户点击退出时,除了要无效化(invalidate)当前的Session,还需要:
      • 删除数据库中对该应用户的令牌记录
      • 删除客户端浏览器中存储“记住我”的Cookie(通过设置一个同名的、值为null的、过期时间为0的Cookie来实现)。

三、 核心代码示例

1. 生成并存储Token(登录Servlet中)

// ... 用户密码验证成功后 ...
if ("on".equals(request.getParameter("rememberMe"))) { // 判断是否勾选
    // 1. 生成随机Token
    String token = UUID.randomUUID().toString() + ThreadLocalRandom.current().nextLong();
    // 更安全的做法是使用 SecureRandom

    // 2. 计算过期时间 (10天后)
    long validity = System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 10); // 毫秒
    Date expiryDate = new Date(validity);

    // 3. 将Token和过期时间存入数据库(关联对应用户)
    userService.updateRememberToken(currentUser.getId(), token, expiryDate);

    // 4. 创建Cookie并发送给客户端
    Cookie cookie = new Cookie("rememberMe", token);
    cookie.setMaxAge(60 * 60 * 24 * 10); // 秒为单位,10天
    cookie.setPath("/"); // 设置为全站有效
    cookie.setHttpOnly(true); // 防止XSS攻击获取Cookie,重要!
    // cookie.setSecure(true); // 如果使用HTTPS,请启用此选项
    response.addCookie(cookie);
}

// 正常创建Session
HttpSession session = request.getSession();
session.setAttribute("user", currentUser);
response.sendRedirect("home.jsp");

2. 自动登录过滤器 (AutoLoginFilter)

@WebFilter("/*") // 拦截所有请求
public class AutoLoginFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        HttpSession session = request.getSession(false); // 获取现有session,不创建新session

        // 1. 如果用户session已存在,直接放行
        if (session != null && session.getAttribute("user") != null) {
            chain.doFilter(request, response);
            return;
        }

        // 2. 检查Remember-Me Cookie
        Cookie[] cookies = request.getCookies();
        String rememberMeToken = null;
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("rememberMe".equals(cookie.getName())) {
                    rememberMeToken = cookie.getValue();
                    break;
                }
            }
        }

        // 3. 如果找到了Cookie
        if (rememberMeToken != null) {
            // 调用Service,通过Token查找用户信息(包含过期时间)
            User user = userService.findByRememberToken(rememberMeToken);

            // 4. 验证Token和用户是否存在且未过期
            if (user != null && user.getTokenValidity().after(new Date())) {
                // 验证成功,自动为用户创建Session
                session = request.getSession();
                session.setAttribute("user", user);
                // 可以选择更新Token有效期(滑动过期时间)
            } else {
                // Token无效或已过期,删除客户端的Cookie
                if (user == null) {
                    // 数据库无此Token,清理无效Cookie
                    Cookie cookie = new Cookie("rememberMe", null);
                    cookie.setMaxAge(0);
                    cookie.setPath("/");
                    response.addCookie(cookie);
                }
            }
        }

        // 5. 继续执行后续的过滤器或请求
        chain.doFilter(request, response);
    }
}

四、 安全考量与最佳实践

  1. Token必须是不可预测的:使用SecureRandomUUID生成高熵(高随机性)的令牌,防止攻击者暴力猜测。
  2. HttpOnly Cookie:设置Cookie的HttpOnly属性为true,防止JavaScript窃取Cookie(防御XSS攻击)。
  3. Secure Cookie:如果你的网站使用HTTPS,务必设置Cookie的Secure属性为true,保证Cookie只在加密通道中传输。
  4. 令牌仅使用一次:更安全的做法是每次自动登录后都生成一个新的令牌替换掉旧的,并更新数据库和Cookie。
  5. 关联用户ID而非用户名:在Cookie中,可以存储userId:token的组合,而不是username:token,因为ID通常是不可变的,且更难被枚举。
  6. 重要操作需重新认证:即使用户通过“记住我”功能保持了登录状态,在进行修改密码、支付等敏感操作时,应要求用户重新输入密码进行验证。
  7. 提供注销选项:一定要提供明显的“退出登录”功能,并正确清理Session和Cookie。

通过以上步骤,你就可以在Java Web项目中实现一个相对安全可靠的“十天内免登录”功能了。