当前位置: 代码网 > it编程>编程语言>Java > 基于SpringBoot实现七天免登录的完整流程

基于SpringBoot实现七天免登录的完整流程

2026年01月14日 Java 我要评论
引言作为一名java后端高级开发,我敢说“七天免登录”是业务系统里最常见的需求之一——用户登录一次后,一周内再次访问系统无需重复输入账号密码,直接就能进

引言

作为一名java后端高级开发,我敢说“七天免登录”是业务系统里最常见的需求之一——用户登录一次后,一周内再次访问系统无需重复输入账号密码,直接就能进入主页。这个需求看似简单,但实现不好很容易踩坑:要么免登录失效影响用户体验,要么出现安全漏洞导致账号被盗。

很多初级开发会直接把用户信息存cookie,或者简单用session过期时间控制,这些做法要么不安全,要么在分布式环境下失效。今天这篇文章,我就结合实际工作经验,讲透“七天免登录”的标准实现方案,从原理到代码全拆解,看完就能直接落地。

一、先搞懂:七天免登录的核心原理

免登录的本质很简单:用户首次登录成功后,服务器生成一个“身份凭证”返回给客户端,客户端持久化存储;后续用户访问时,自动携带这个凭证,服务器验证通过后就直接放行

这里的关键是解决三个问题:

  • 凭证怎么生成?要唯一、不可伪造、带过期时间;
  • 凭证存在哪?客户端存储方案要兼顾安全和可用性;
  • 怎么验证?服务器要快速校验凭证的合法性,还要支持分布式部署。

工作中最成熟的方案是: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被盗用,又要保证分布式环境下正常工作,还要兼顾用户体验。

本文给出的方案是工作中的标准实现,代码可直接落地。核心要点总结:

  1. 用jwt生成带签名和过期时间的凭证,避免伪造;
  2. 用cookie存储凭证,开启httponly、secure等安全属性;
  3. 用拦截器统一校验凭证,实现自动登录;
  4. 分布式环境必须用redis维护黑名单,解决jwt无法主动失效的问题;
  5. 做好活跃续期,提升用户体验。

以上就是基于springboot实现七天免登录的完整流程的详细内容,更多关于springboot七天免登录的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com