【Java 后端】SpringBoot 登录认证与会话跟踪实战(JWT + Filter/Interceptor)

前言:在 Web 开发中,安全性是系统的重中之重。本文将带你从零实现一个企业级的登录认证系统,涵盖从基础的登录接口开发到进阶的 JWT 令牌技术,以及如何利用 SpringBoot 的过滤器和拦截器实现统一权限校验,确保系统资源不被非法访问。

⚡ 快速参考

  • 适用场景:需要实现用户登录、权限校验、移动端适配及集群环境下的会话共享。
  • 核心结论:JWT 令牌是当前前后端分离架构下的主流会话跟踪方案,优于传统的 Cookie-Session。
  • 最简步骤:1. 实现登录接口生成 JWT;2. 前端存储并携带 JWT;3. 后端拦截器统一校验。
  • 必备代码Jwts.builder().signWith(...).compact() 生成令牌;HandlerInterceptor 实现拦截。
  • 高危避坑:JWT 秘钥泄露、过期时间设置过长、拦截器路径配置错误导致登录接口被拦截。

📚 学习目标

  1. 掌握基于 JWT 令牌的登录认证流程及代码实现。
  2. 理解会话跟踪技术(Cookie, Session, Token)的区别与优缺点。
  3. 能够熟练运用 Filter 和 Interceptor 实现 SpringBoot 的统一请求拦截。

一、基础概念

1.1 登录功能本质

登录功能的本质是查询:根据用户提供的用户名和密码,在数据库中匹配对应的记录。如果匹配成功,则下发身份凭证。

1.2 会话技术

  • 会话:浏览器与服务器之间的一次连接(从打开浏览器访问到关闭)。
  • 会话跟踪:服务器识别多次请求是否来自同一浏览器的过程。
技术方案 存储位置 跨域支持 集群支持 适用场景
Cookie 客户端 不支持 支持 简单 Web 应用
Session 服务端 不支持 不支持(默认) 传统单体应用
Token (JWT) 客户端 支持 支持 移动端/前后端分离/微服务

二、原理详解

2.1 为什么选择 JWT?

HTTP 协议是无状态的,服务器无法自动识别用户身份。

  • Cookie-Session 痛点:Session 存储在服务端,集群环境下无法共享;Cookie 不支持跨域且移动端支持差。
  • JWT 优势:自包含(载荷可存数据)、无状态(服务器不存数据)、安全可靠(签名防篡改)、天然支持跨域。

2.2 JWT 结构

JWT 字符串由三部分组成(. 分隔):

  1. Header (头部):签名算法和令牌类型。
  2. Payload (有效载荷):自定义数据(如用户 ID、姓名等)。
  3. Signature (签名):防止篡改的核心,由 Header + Payload + 秘钥通过算法生成。

三、完整实战代码

3.1 登录接口实现

1. 结果封装类 LoginInfo
java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginInfo {
    private Integer id;
    private String username;
    private String name;
    private String token; // 登录成功后下发的令牌
}
2. Controller 层
java 复制代码
@Slf4j
@RestController
public class LoginController {
    @Autowired
    private EmpService empService;

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        log.info("员工登录: {}", emp);
        LoginInfo loginInfo = empService.login(emp);
        return loginInfo != null ? Result.success(loginInfo) : Result.error("用户名或密码错误~");
    }
}
3. Service 实现与 JWT 生成
java 复制代码
@Override
public LoginInfo login(Emp emp) {
    Emp empLogin = empMapper.getUsernameAndPassword(emp);
    if(empLogin != null){
        // 生成 JWT 令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", empLogin.getId());
        claims.put("username", empLogin.getUsername());
        
        String jwt = JwtUtils.generateJwt(claims);
        return new LoginInfo(empLogin.getId(), empLogin.getUsername(), empLogin.getName(), jwt);
    }
    return null;
}

3.2 JWT 工具类

java 复制代码
public class JwtUtils {
    private static String signKey = "SVRIRUlNQQ=="; // 秘钥
    private static Long expire = 43200000L; // 有效期 12 小时

    public static String generateJwt(Map<String, Object> claims){
        return Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
    }

    public static Claims parseJWT(String jwt){
        return Jwts.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody();
    }
}

3.3 统一拦截校验(Interceptor 方案)

1. 定义拦截器
java 复制代码
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURL().toString();
        // 1. 放行登录请求
        if(url.contains("login")){ return true; }

        // 2. 获取并校验令牌
        String jwt = request.getHeader("token");
        if(!StringUtils.hasLength(jwt)){
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }

        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        return true;
    }
}
2. 注册拦截器
java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private TokenInterceptor tokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login");
    }
}

四、场景应用

场景1:前后端分离管理系统

  • 需求:前端 Vue/React,后端 SpringBoot,部署在不同域名下。
  • 方案 :使用 JWT 存储在 LocalStorage 中,Axios 拦截器统一在 Header 携带 token
  • 收益:解决跨域 Cookie 无法使用的难题,实现灵活的权限控制。

场景2:移动端 APP 登录

  • 需求:Android/iOS 客户端登录。
  • 方案:移动端不具备浏览器 Cookie 环境,通过请求头传递 JWT。
  • 收益:一套认证逻辑完美适配 PC、移动端及平板。

五、开发避坑总结

  1. 问题 :JWT 令牌过期后解析报错。
    原因Jwts.parser() 在解析过期令牌时会抛出 ExpiredJwtException
    解决 :在拦截器中使用 try-catch 捕获异常,并统一返回 401 状态码。
  2. 问题 :拦截器配置后,登录接口也无法访问。
    原因addPathPatterns("/**") 拦截了所有请求。
    解决 :使用 excludePathPatterns("/login") 排除登录接口。
  3. 问题 :JWT Payload 存放了敏感信息。
    原因 :JWT 的 Header 和 Payload 仅是 Base64 编码,可被轻易解码。
    解决:禁止在 JWT 中存放密码、手机号等敏感信息。

六、面试考点

  • Q1:JWT 令牌和 Session 认证的区别是什么?

    • A:Session 是有状态的,数据存在服务端,不支持分布式集群;JWT 是无状态的,数据存在客户端,适合分布式和微服务架构。
  • Q2:Filter 和 Interceptor 有什么区别?

    • A:Filter 是 Servlet 规范定义的,拦截范围广;Interceptor 是 Spring 提供的,能访问 Spring 上下文。
  • Q3:如何防止 JWT 被篡改?

    • A:依靠 Signature(签名)部分。签名是由头部、载荷加秘钥生成的,攻击者没有秘钥无法生成有效的签名。
  • 追问1:JWT 秘钥泄露了怎么办?(需立即更换秘钥,所有旧令牌失效)

  • 追问2:如何主动让某个 JWT 令牌失效?(JWT 是无状态的,通常需配合 Redis 黑名单实现)

七、总结

本文从基础的登录业务出发,深入剖析了会话跟踪技术的演进,并通过代码实战演示了如何在 SpringBoot 中集成 JWT 令牌。掌握了 Filter 和 Interceptor 的使用,你就掌握了 Web 系统安全防护的第一道防线。下一步,建议学习 Spring Security 或 Shiro 等专业安全框架,以应对更复杂的权限需求。

附录:

本文为MY_TRUCK原创实战学习笔记,持续更新Java后端与AI应用领域干货,问题欢迎评论区交流。

相关推荐
今天长肉了吗1 小时前
银行风控项目踩坑实录:指标跑了6小时,风险评分全挂了
java
QQ2422199791 小时前
基于python+微信小程序的家教管理系统_mh3j9
开发语言·python·微信小程序
计算机程序定制辅导1 小时前
计算机小程序毕设实战-基于Spring Boot与微信小程序的考研资源共享平台设计与实现基于springboot+微信小程序的考研复习辅助平台【完整源码+LW+部署说明+演示视频,全bao一条龙等】
spring boot·微信小程序·小程序·课程设计
随读手机2 小时前
多式联运信息交互平台完整方案(2026版)
java·ai·eclipse·云计算·区块链
沐知全栈开发2 小时前
JavaScript 条件语句
开发语言
RSTJ_16252 小时前
PYTHON+AI LLM DAY THREETY-SEVEN
开发语言·人工智能·python
清水白石0082 小时前
《Python性能深潜:从对象分配开销到“小对象风暴”的破解之道(含实战与最佳实践)》
开发语言·python
Je1lyfish2 小时前
CMU15-445 (2025 Fall/2026 Spring) Project#3 - QueryExecution
linux·c语言·开发语言·数据结构·数据库·c++·算法
许彰午2 小时前
03-二叉树——从递归遍历到非递归实现
java·算法