工作中七天免登录如何实现
作为一名 Java 后端高级开发,我敢说“七天免登录”是业务系统里最常见的需求之一——用户登录一次后,一周内再次访问系统无需重复输入账号密码,直接就能进入主页。这个需求看似简单,但实现不好很容易踩坑:要么免登录失效影响用户体验,要么出现安全漏洞导致账号被盗。
很多初级开发会直接把用户信息存 cookie,或者简单用 Session 过期时间控制,这些做法要么不安全,要么在分布式环境下失效。今天这篇文章,我就结合实际工作经验,讲透“七天免登录”的标准实现方案,从原理到代码全拆解,看完就能直接落地。
一、先搞懂:七天免登录的核心原理
免登录的本质很简单:用户首次登录成功后,服务器生成一个“身份凭证”返回给客户端,客户端持久化存储;后续用户访问时,自动携带这个凭证,服务器验证通过后就直接放行 。
这里的关键是解决三个问题: #后端 #Java #2025 AI/Vibe Coding 对我的影响
- 凭证怎么生成?要唯一、不可伪造、带过期时间;
- 凭证存在哪?客户端存储方案要兼顾安全和可用性;
- 怎么验证?服务器要快速校验凭证的合法性,还要支持分布式部署。
工作中最成熟的方案是:cookie + JWT Token + Redis 黑名单 。为什么选这个组合?
核心优势:JWT 自带过期时间和签名机制,能避免伪造;cookie 自动携带凭证,无需前端额外处理;Redis 存储黑名单,解决 JWT 无法主动失效的问题,还能支撑分布式系统。
二、分步实现:七天免登录完整流程(附实战代码)
我们基于 Spring Boot 框架实现,整体流程分为5步:用户登录生成凭证→客户端存储凭证→拦截器校验凭证→活跃续期→退出登录失效。下面逐一拆解,代码可直接复用。
1. 第一步:准备依赖和核心配置
首先引入 JWT 和 Redis 依赖(如果是单体应用,Redis 可选,但分布式必须要):
<!-- JWT依赖 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><!-- Redis 依赖(分布式必选) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>然后在 application.yml 配置 JWT 密钥、过期时间、cookie 参数:
# JWT配置jwt: secret: your-secret-key-32bytes-long-12345678 # 密钥必须足够长(建议32位),放配置中心,不要硬编码expire: 604800000 # 7天过期(单位:毫秒)refresh-expire: 86400000 # 1天内活跃自动续期(单位:毫秒)# cookie配置cookie: name: auto_login_token # cookie 名称domain: localhost # 域名(生产环境填实际域名,如 xxx.com)path: / # 作用路径max-age: 604800 # 7天(单位:秒)http-only: true # 仅 HTTP 访问,禁止 JS 操作(防 XSS)secure: false # 生产环境开启 HTTPS 后设为 true(仅 HTTPS 传输)same-site: Lax # 防 CSRF 攻击2. 第二步:封装JWT工具类(核心)
JWT 负责生成和解析身份凭证,核心是“签名防伪造”和“自带过期时间”。工具类包含3个核心方法:生成 Token、解析 Token、验证 Token 合法性。
import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.security.Keys;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import javax.crypto.SecretKey; import java.util.Date; import java.util.Map;@Component public class JwtUtil { // 注入 JWT 密钥和过期时间 @Value("${jwt.secret}") private String secret; @Value("${jwt.expire}") private long expire;// 生成 JWT Token(传入用户信息,如 userId、username) public String generateToken(Map<String, Object> claims) { // 密钥编码(必须和配置的密钥长度匹配) SecretKey key = Keys.hmacShaKeyFor(secret.getBytes()); return Jwts.builder() .setClaims(claims) // 自定义载荷(存放用户信息) .setIssuedAt(new Date()) // 签发时间 .setExpiration(new Date(System.currentTimeMillis() + expire)) // 过期时间 .signWith(key) // 签名 .compact(); }// 解析 Token,获取载荷信息 public Claims parseToken(String token) { SecretKey key = Keys.hmacShaKeyFor(secret.getBytes()); return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); }// 验证 Token 是否合法(未过期+签名正确) public boolean validateToken(String token) { try { Claims claims = parseToken(token); // 检查是否过期 return !claims.getExpiration().before(new Date()); } catch (Exception e) { // 解析失败(签名错误、过期、格式错误)都返回 false return false; } } }3. 第三步:登录接口生成凭证(核心流程)
用户首次登录成功后,生成 JWT Token,然后通过 cookie 返回给客户端存储。这里要注意:敏感信息(如密码)不能放进 JWT 载荷,只放非敏感的用户标识(如 userId、username)。
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.cookie; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map;@RestController public class LoginController { @Autowired private UserService userService; // 自定义用户服务(校验账号密码) @Autowired private JwtUtil jwtUtil; @Autowired private RedisTemplate<String, Object> redisTemplate; // Redis 模板(分布式用)// 注入 cookie 配置 @Value("${cookie.name}") private String cookieName; @Value("${cookie.domain}") private String cookieDomain; @Value("${cookie.path}") private String cookiePath; @Value("${cookie.max-age}") private int cookieMaxAge; @Value("${cookie.http-only}") private boolean cookieHttpOnly; @Value("${cookie.secure}") private boolean cookieSecure; @Value("${cookie.same-site}") private String cookieSameSite;@PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO, HttpServletResponse response) { // 1. 校验账号密码(实际业务中要加密校验,如 BCrypt) User user = userService.verifyUser(loginDTO.getUsername(), loginDTO.getPassword()); if (user == null) { return Result.fail("账号或密码错误"); }// 2. 生成 JWT Token(载荷放 userId 和 username,非敏感信息) Map<String, Object> claims = new HashMap<>(); claims.put("userId", user.getId()); claims.put("username", user.getUsername()); String token = jwtUtil.generateToken(claims);// 3. (分布式必做)将 Token 存入 Redis(可选,用于黑名单校验) // redisTemplate.opsForValue().set("auto_login:blacklist:" + token, user.getId(), jwtUtil.getExpire(), TimeUnit.MILLISECONDS);// 4. 生成 cookie,返回给客户端 cookie cookie = new cookie(cookieName, token); cookie.setDomain(cookieDomain); cookie.setPath(cookiePath); cookie.setMaxAge(cookieMaxAge); // 7天过期 cookie.setHttponly(cookieHttpOnly); // 防 XSS cookie.setSecure(cookieSecure); // 生产环境 HTTPS 开启 cookie.setAttribute("SameSite", cookieSameSite); // 防 CSRF response.addcookie(cookie);return Result.success("登录成功"); } }4. 第四步:拦截器校验凭证(自动登录核心)
用户后续访问系统时,浏览器会自动携带 cookie 中的 Token。我们用 Spring 拦截器拦截所有请求,校验 Token 合法性——合法则放行,不合法则跳转到登录页。
4.1 自定义拦截器
import io.jsonwebtoken.Claims;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;public class AutoLoginInterceptor implements HandlerInterceptor { @Autowired private JwtUtil jwtUtil; @Autowired private RedisTemplate<String, Object> redisTemplate;@Value("${cookie.name}") private String cookieName; @Value("${jwt.refresh-expire}") private long refreshExpire; // 1天内活跃自动续期@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 跳过登录接口(避免拦截登录请求) if (request.getRequestURI().contains("/login")) { return true; }// 2. 从 cookie 中获取 Token String token = null; cookie[] cookies = request.getcookies(); if (cookies != null) { for (cookie cookie : cookies) { if (cookieName.equals(cookie.getName())) { token = cookie.getValue(); break; } } }// 3. Token 不存在,跳转到登录页 if (token == null) { response.sendRedirect("/login.html"); return false; }// 4. 校验 Token 合法性(未过期+签名正确) if (!jwtUtil.validateToken(token)) { response.sendRedirect("/login.html"); return false; }// 5. (分布式必做)校验 Token 是否在黑名单(用户退出登录后失效) Boolean isBlack = redisTemplate.hasKey("auto_login:blacklist:" + token); if (Boolean.TRUE.equals(isBlack)) { response.sendRedirect("/login.html"); return false; }// 6. 解析 Token,获取用户信息,存入 Request(后续业务可用) Claims claims = jwtUtil.parseToken(token); request.setAttribute("userId", claims.get("userId")); request.setAttribute("username", claims.get("username"));// 7. 活跃续期:如果 Token 剩余有效期小于1天,自动刷新 Token(提升用户体验) long remainTime = claims.getExpiration().getTime() - System.currentTimeMillis(); if (remainTime < refreshExpire) { Map<String, Object> newClaims = new HashMap<>(); newClaims.put("userId", claims.get("userId")); newClaims.put("username", claims.get("username")); String newToken = jwtUtil.generateToken(newClaims);// 更新 cookie 中的 Token cookie newcookie = new cookie(cookieName, newToken); newcookie.setDomain(request.getServerName()); newcookie.setPath("/"); newcookie.setMaxAge(cookieMaxAge); newcookie.setHttponly(true); newcookie.setSecure(false); newcookie.setAttribute("SameSite", "Lax"); response.addcookie(newcookie);// 更新 Redis 中的 Token(分布式必做) // redisTemplate.delete("auto_login:blacklist:" + token); // redisTemplate.opsForValue().set("auto_login:blacklist:" + newToken, claims.get("userId"), jwtUtil.getExpire(), TimeUnit.MILLISECONDS); }// 8. 校验通过,放行 return true; } }4.2 注册拦截器
import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Resource private AutoLoginInterceptor autoLoginInterceptor;@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(autoLoginInterceptor) .addPathPatterns("/**") // 拦截所有请求 .excludePathPatterns("/login", "/login.html", "/static/**"); // 排除登录页和静态资源 } }5. 第五步:退出登录(凭证失效)
用户主动退出登录时,需要清除客户端的 cookie,同时将 Token 加入 Redis 黑名单(避免被盗用)。
@PostMapping("/logout")public Result logout(HttpServletRequest request, HttpServletResponse response) { // 1. 从cookie中获取Token String token = null; cookie[] cookies = request.getcookies(); if (cookies != null) { for (cookie cookie : cookies) { if (cookieName.equals(cookie.getName())) { token = cookie.getValue(); break; } } }// 2. 将 Token 加入 Redis 黑名单(分布式必做) if (token != null) { // 黑名单有效期和 Token 一致 redisTemplate.opsForValue().set("auto_login:blacklist:" + token, request.getAttribute("userId"), jwtUtil.getExpire(), TimeUnit.MILLISECONDS); }// 3. 清除 cookie(设置 maxAge=0) cookie cookie = new cookie(cookieName, null); cookie.setDomain(cookieDomain); cookie.setPath(cookiePath); cookie.setMaxAge(0); // 立即过期 cookie.setHttponly(true); cookie.setSecure(false); cookie.setAttribute("SameSite", "Lax"); response.addcookie(cookie);return Result.success("退出成功"); }三、高级开发必关注:安全防护细节
七天免登录的核心风险是“凭证被盗用”,一旦 Token 被别人获取,就能直接登录用户账号。作为高级开发,必须做好以下5点防护:
1. cookie安全属性必须设对
- Httponly=true :禁止Javascript操作cookie,防止XSS攻击窃取Token;
- Secure=true :仅在HTTPS协议下传输cookie,避免HTTP协议被抓包窃取;
- SameSite=Lax :限制cookie仅在同站点请求中携带,防止CSRF攻击;
- Domain和Path精准配置 :不要设为顶级域名(如.com),避免cookie被同域名下的其他应用获取。
2. JWT密钥不能硬编码
JWT 的安全性依赖于密钥,必须将密钥放在配置中心(如 Nacos、Apollo),禁止硬编码在代码里。密钥长度至少32位,建议用随机字符串生成(如 UUID)。
3. 分布式环境必须用Redis黑名单
JWT 本身是无状态的,一旦生成无法主动失效。用户退出登录后,必须将 Token 加入 Redis 黑名单,拦截器校验时先查黑名单,避免 Token 被复用。
4. 载荷不存敏感信息
JWT 的载荷是 base64编码的,不是加密的,任何人都能解码查看。因此不能存放密码、手机号、身份证等敏感信息,只放 userId、username 等非敏感标识。
5. 可选:结合设备/IP验证
如果业务安全性要求高,可以在生成 Token 时,将用户的设备信息(如浏览器版本、系统版本)、IP 地址存入载荷。校验时对比当前请求的设备/IP,不一致则拒绝登录(注意:IP 可能动态变化,需平衡安全性和用户体验)。
四、避坑指南:工作中常见问题解决
结合实际开发经验,我总结了3个常见坑,帮你快速避坑:
坑1:免登录在分布式环境下失效
原因:不同服务节点生成的 Token 不同,或者 cookie 没有共享。
解决方案:
- 所有服务使用相同的JWT密钥(配置中心统一配置);
- cookie的Domain设为服务的统一域名(如api.xxx.com);
- 用Redis统一存储Token黑名单,所有服务共享黑名单。
坑2:Token过期前用户活跃,却被要求重新登录
原因:没有做活跃续期,Token 到期后直接失效。
解决方案:在拦截器中判断 Token 剩余有效期,小于1天(或其他阈值)时,自动生成新 Token 并更新 cookie,实现“无缝续期”。
坑3:cookie跨域无法携带
原因:前后端分离项目中,前端和后端域名不同,cookie 跨域不携带。
解决方案:
- 后端配置CORS,允许前端域名的跨域请求,同时设置 allowCredentials=true ;
- 前端请求时设置 withCredentials=true (如Axios、Fetch);
- cookie的Domain设为后端域名,确保跨域请求时能携带。
五、总结
七天免登录的核心实现逻辑很简单:登录生成 JWT Token→cookie 存储→拦截器校验→活跃续期→退出加入黑名单 。但关键在于“安全”和“兼容性”——既要防止 Token 被盗用,又要保证分布式环境下正常工作,还要兼顾用户体验。
本文给出的方案是工作中的标准实现,代码可直接落地。核心要点总结:
- 用JWT生成带签名和过期时间的凭证,避免伪造;
- 用cookie存储凭证,开启HttpOnly、Secure等安全属性;
- 用拦截器统一校验凭证,实现自动登录;
- 分布式环境必须用Redis维护黑名单,解决JWT无法主动失效的问题;
- 做好活跃续期,提升用户体验。

