登录验证全攻略:从会话技术到 JWT,结合过滤器与拦截器实战

在 Web 应用中,登录验证是保障系统安全的第一道防线 ------ 只有通过身份认证的用户,才能访问系统的核心资源(如员工管理、部门数据、个人中心等)。实现登录验证的核心技术包括 "会话管理" 和 "请求拦截",从传统的 Session-Cookie,到如今流行的 JWT 令牌,再配合过滤器(Filter)与拦截器(Interceptor)实现全局校验,形成了一套完整的登录验证体系。本文将以 "后台管理系统" 为场景,全面讲解登录验证的实现思路、核心技术及实战落地。

一、登录验证的核心诉求:为什么需要它?

想象一个没有登录验证的后台系统:任何人都可以直接访问/emp/list(员工列表)、/dept/delete(删除部门)等核心接口,这会导致数据泄露、恶意篡改等严重安全问题。

登录验证的核心诉求有 3 点:

  1. 身份认证:验证用户输入的账号密码是否正确,确认用户身份合法性;
  2. 身份保持:用户登录成功后,在一段时间内无需重复登录,可正常访问系统资源(会话技术实现);
  3. 权限控制:拦截未登录用户的非法请求,拒绝其访问受保护资源(过滤器 / 拦截器实现)。

二、会话技术:实现用户身份保持

用户登录成功后,如何让系统 "记住" 用户身份?这就需要会话技术 ,主流方案分为两类:传统的Session-Cookie和现代的JWT(Json Web Token)。

核心原理

Session-Cookie是基于服务器端存储的会话方案,核心流程如下:

  1. 用户提交账号密码登录,服务器验证通过后,创建HttpSession对象(存储用户信息,如用户 ID、用户名),并生成唯一的SessionId
  2. 服务器将SessionId通过Set-Cookie响应头写入客户端浏览器;
  3. 客户端后续发起请求时,浏览器会自动携带该Cookie(包含SessionId)到服务器;
  4. 服务器通过SessionId查找对应的HttpSession对象,确认用户身份,实现 "免登录" 访问。
实战演示(Java Web)
java 复制代码
// 1. 登录接口:验证账号密码,创建Session
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json;charset=UTF-8");

        // 获取前端提交的账号密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        // 模拟账号密码验证(实际需查询数据库)
        if ("admin".equals(username) && "123456".equals(password)) {
            // 登录成功:创建Session,存储用户信息
            HttpSession session = req.getSession();
            session.setAttribute("loginUser", username); // 存储用户名
            session.setMaxInactiveInterval(3600); // 设置Session过期时间:1小时(无操作超时)

            resp.getWriter().write("{"code":200,"msg":"登录成功"}");
        } else {
            resp.getWriter().write("{"code":500,"msg":"账号或密码错误"}");
        }
    }
}

// 2. 核心资源接口:通过Session验证用户身份
@WebServlet("/emp/list")
public class EmpListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=UTF-8");

        // 获取Session中的用户信息
        HttpSession session = req.getSession(false); // false:不存在则返回null,不创建新Session
        if (session == null || session.getAttribute("loginUser") == null) {
            // 未登录:返回未授权提示
            resp.getWriter().write("{"code":401,"msg":"请先登录"}");
            return;
        }

        // 已登录:返回员工列表数据
        resp.getWriter().write("{"code":200,"data":[{"id":1,"name":"张三"},{"id":2,"name":"李四"}]}");
    }
}
优点 缺点
安全性较高(用户信息存储在服务器端,不易被篡改) 服务器压力大(大量 Session 存储在服务器内存 / 缓存中,高并发场景不友好)
支持会话失效手动控制(如用户退出登录,直接销毁 Session) 不支持跨域(Cookie 默认不允许跨域携带,需额外配置)
实现简单,无需额外依赖 不支持分布式部署(Session 存储在单个服务器,集群环境下需做 Session 共享)

2. 现代方案:JWT 令牌认证

核心原理

JWT(Json Web Token)是一种基于客户端存储的无状态令牌方案,无需服务器存储会话信息,核心流程如下:

  1. 用户提交账号密码登录,服务器验证通过后,根据用户信息(如用户 ID、用户名)和密钥,生成 JWT 令牌(包含头部、载荷、签名三部分);
  2. 服务器将 JWT 令牌返回给客户端(客户端可存储在 LocalStorage/SessionStorage/Cookie 中);
  3. 客户端后续发起请求时,通过请求头(如Authorization: Bearer <JWT令牌>)携带令牌到服务器;
  4. 服务器接收令牌后,通过密钥验证令牌的合法性(是否过期、是否被篡改),验证通过则确认用户身份。
JWT 令牌结构

JWT 令牌是一个字符串,由三部分组成,用.分隔:

  1. Header(头部) :指定令牌类型(JWT)和加密算法(如 HS256),示例:

    json 复制代码
    {"alg": "HS256", "typ": "JWT"}
  2. Payload(载荷) :存储用户自定义信息(如用户 ID、用户名)和过期时间等元数据,示例:

    json 复制代码
    {"userId": 1, "username": "admin", "exp": 1735689600}
  3. Signature(签名) :服务器用头部指定的加密算法,将头部、载荷和密钥进行加密生成的签名,用于验证令牌是否被篡改。

实战演示(Java + JJWT 依赖)
(1)引入 JJWT 依赖(Maven)

JJWT 是 Java 生态中常用的 JWT 工具库,简化 JWT 的生成与验证:

xml 复制代码
<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>
(2)JWT 工具类
java 复制代码
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;

public class JwtUtils {
    // 密钥(生产环境需配置在配置文件中,且保证安全性)
    private static final String SECRET_KEY = "my-secret-key-32bytes-long-12345678";
    // 令牌过期时间:1小时(单位:毫秒)
    private static final long EXPIRE_TIME = 3600 * 1000L;

    // 生成JWT令牌
    public static String generateToken(String username) {
        // 创建密钥(HS256算法要求密钥长度至少32位)
        SecretKey key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
        // 构建并生成令牌
        return Jwts.builder()
                .setSubject(username) // 设置主题(存储用户名)
                .setIssuedAt(new Date()) // 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME)) // 设置过期时间
                .signWith(key) // 使用密钥签名
                .compact();
    }

    // 验证JWT令牌合法性,并获取令牌中的用户信息
    public static Claims verifyToken(String token) {
        try {
            SecretKey key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
            // 验证令牌并解析载荷
            return Jwts.parserBuilder()
                    .setSigningKey(key)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            // 令牌无效(过期、被篡改等),返回null
            return null;
        }
    }

    // 从令牌中获取用户名
    public static String getUsernameFromToken(String token) {
        Claims claims = verifyToken(token);
        return claims == null ? null : claims.getSubject();
    }
}
(3)JWT 登录与验证接口
java 复制代码
@WebServlet("/jwt/login")
public class JwtLoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json;charset=UTF-8");

        String username = req.getParameter("username");
        String password = req.getParameter("password");

        // 模拟账号密码验证
        if ("admin".equals(username) && "123456".equals(password)) {
            // 生成JWT令牌
            String token = JwtUtil.generateToken(username);
            // 返回令牌给客户端
            resp.getWriter().write("{"code":200,"msg":"登录成功","data":"" + token + ""}");
            return;
        }

        resp.getWriter().write("{"code":500,"msg":"账号或密码错误"}");
    }
}

@WebServlet("/jwt/emp/list")
public class JwtEmpListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=UTF-8");

        // 获取请求头中的JWT令牌
        String authorization = req.getHeader("Authorization");
        if (authorization == null || !authorization.startsWith("Bearer ")) {
            resp.getWriter().write("{"code":401,"msg":"请先登录"}");
            return;
        }

        // 提取令牌(去除"Bearer "前缀)
        String token = authorization.substring(7);
        // 验证令牌并获取用户名
        String username = JwtUtil.getUsernameFromToken(token);
        if (username == null) {
            resp.getWriter().write("{"code":401,"msg":"令牌无效或已过期"}");
            return;
        }

        // 令牌有效:返回员工列表
        resp.getWriter().write("{"code":200,"data":[{"id":1,"name":"张三"},{"id":2,"name":"李四"}]}");
    }
}
JWT 的优缺点
优点 缺点
无状态(服务器无需存储会话信息,降低服务器压力) 安全性较低(令牌存储在客户端,若泄露可能被恶意使用,需配合 HTTPS)
支持跨域(令牌通过请求头携带,不受跨域限制) 令牌一旦生成,无法主动作废(除非设置短期过期,或维护黑名单)
支持分布式部署(任意服务器均可通过密钥验证令牌,无需 Session 共享) 载荷部分 Base64 编码(非加密),敏感信息不可直接存储

三、请求拦截:实现全局登录验证

无论是 Session-Cookie 还是 JWT,若每个接口都单独编写身份验证逻辑,会造成代码冗余。此时需要 过滤器(Filter)拦截器(Interceptor) 实现全局请求拦截,统一处理登录验证。

1. 过滤器(Filter):Servlet 规范中的全局拦截

Filter 是 Java Servlet 规范定义的组件,运行在 Servlet 之前,可对请求和响应进行统一拦截处理,支持对所有 Web 资源(Servlet、JSP、静态资源)进行过滤。

实战演示:JWT 全局验证过滤器
java 复制代码
package com.tgt.filters;

import cn.hutool.core.util.StrUtil;
import com.tgt.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

@Slf4j
@WebFilter("/*")
public class LoginFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //1.获取请求url。
        // servletRequest.getRequestURI() 这个api是子接口HttpServletRequest才有
        // 将父接口 ServletRequest 转换为 子接口HttpServletRequest
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String uri = request.getRequestURI();

        //2.判断请求url中是否包含login,
        if (uri.contains("/login")){
            //如果包含,说明是登录操作,放行。
            filterChain.doFilter(request,response);

            return;
        }

        //3.获取请求头中的令牌(token)
        String token = request.getHeader("token");

        //4.判断令牌是否存在,如果不存在,响应401。
        if (StrUtil.isEmpty(token)){
            response.setStatus(401);

            return;
        }

        try {
            //5.解析token,如果解析失败,响应401
            Claims claims = JwtUtils.parseJWT(token);//如果篡改或过期会发生异常
            
            //6.放行。
            filterChain.doFilter(request,response);
        } catch (Exception e) {
            log.error("登录校验失败,解析token失败:",e);

            //解析token失败 返回401
            response.setStatus(401);
        }
    }
}

2. 拦截器(Interceptor):Spring 框架中的全局拦截

Interceptor 是 Spring MVC 框架提供的组件,运行在 Controller 方法执行前后,仅对 Controller 请求进行拦截,功能更灵活(支持 Spring 容器管理,可注入 Service 等 Bean)。

实战演示:Spring MVC JWT 拦截器
(1)编写 JWT 拦截器
java 复制代码
import cn.hutool.core.util.StrUtil;
import com.tgt.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
@Slf4j
public class LoginInterceptors implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //3.获取请求头中的令牌(token)
        String token = request.getHeader("token");

        //4.判断令牌是否存在,如果不存在,响应401。
        if (StrUtil.isEmpty(token)){
            response.setStatus(401);

            return false;
        }

        try {
            //5.解析token,如果解析失败,响应401
            Claims claims = JwtUtils.parseJWT(token);//如果篡改或过期会发生异常

            Integer empId = Integer.valueOf(claims.get("empId").toString());

            log.info("登录成功,用户id为:{}",empId);

            //6.放行。
            return true;
        } catch (Exception e) {
            log.error("登录校验失败,解析token失败:",e);

            //解析token失败 返回401
            response.setStatus(401);
            return false;
        }
    }
}
(2)配置拦截器(Spring MVC 配置类)
java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptors loginInterceptors;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册拦截器并绑定拦截路径,注意 是两个*,代表任意路径
        registry.addInterceptor(loginInterceptors)
                .addPathPatterns("/**")
                .excludePathPatterns("/login");
    }
}

3. 过滤器 vs 拦截器:核心区别

对比维度 过滤器(Filter) 拦截器(Interceptor)
所属规范 Servlet 规范(Java EE 标准) Spring MVC 框架自定义
拦截范围 所有 Web 资源(Servlet、JSP、静态资源) 仅拦截 Controller 请求
执行时机 运行在 Servlet 容器层面,早于 Interceptor 运行在 Spring MVC 层面,晚于 Filter
依赖容器 不依赖 Spring 容器,可独立使用 依赖 Spring 容器,可注入 Bean(Service/Mapper)
执行次数 一次请求只执行一次 若存在视图转发,会执行多次
功能灵活性 功能单一,仅支持请求 / 响应拦截 功能丰富,支持前置拦截、后置处理、视图渲染后处理

四、完整登录验证流程(实战总结)

以 "JWT + 拦截器" 为例,完整的登录验证流程如下:

  1. 用户登录 :前端提交账号密码到/jwt/login接口,后端验证通过后生成 JWT 令牌,返回给前端;

  2. 前端存储令牌:前端将 JWT 令牌存储在 LocalStorage 中;

  3. 前端发起请求 :前端后续访问核心接口(如/emp/list)时,通过Authorization请求头携带 JWT 令牌;

  4. 全局拦截验证:后端拦截器拦截请求,提取并验证 JWT 令牌;

  5. 请求放行 / 拦截

    • 令牌有效:放行请求,Controller 处理业务并返回数据;
    • 令牌无效 / 未携带:拦截请求,返回 401 未授权提示,前端跳转至登录页。

五、登录验证最佳实践

  1. 令牌安全存储

    • JWT 令牌优先存储在HttpOnly Cookie 中(防止 XSS 攻击),其次存储在 LocalStorage(需做 XSS 防护);
    • 敏感信息不存储在 JWT 载荷中(载荷仅 Base64 编码,可被解码)。
  2. 设置合理过期时间

    • 短期令牌:访问令牌(Access Token)过期时间设为 1 小时,减少令牌泄露风险;
    • 长期令牌:刷新令牌(Refresh Token)过期时间设为 7 天,用于过期后重新获取访问令牌,无需用户重复登录。
  3. HTTPS 传输:所有请求(尤其是登录请求和携带令牌的请求)使用 HTTPS 协议,防止令牌被中间人劫持。

  4. 避免匿名访问:除登录接口、静态资源外,所有核心接口均需拦截验证,禁止匿名访问。

  5. 令牌黑名单机制:若用户退出登录(令牌未过期),后端需维护 JWT 黑名单,拦截已注销的令牌。

六、总结与拓展

本文全面讲解了登录验证的核心技术,包括 Session-Cookie、JWT 两种会话方案,以及 Filter、Interceptor 两种全局拦截方案,核心要点总结:

  1. 会话方案选择:传统单体应用可使用 Session-Cookie,分布式 / 跨域应用优先使用 JWT;
  2. 拦截方案选择:简单场景用 Filter,Spring Boot/Spring MVC 项目优先用 Interceptor(更灵活);
  3. 安全核心:令牌存储需防 XSS、传输需用 HTTPS、过期时间需合理设置;
  4. 代码规范:全局拦截统一处理登录验证,避免接口内重复编写验证逻辑。

拓展方向:

  • 权限细化:基于 RBAC 模型(角色 - 权限 - 用户),实现 "基于角色的访问控制"(如管理员可删除部门,普通用户仅可查看);
  • 单点登录(SSO) :基于 JWT 或 CAS 协议,实现多系统统一登录(一次登录,多系统免登);
  • 令牌刷新机制:实现 Access Token 过期后,通过 Refresh Token 自动刷新令牌,提升用户体验;
  • 防暴力破解:登录接口添加验证码、限流机制,防止恶意账号密码爆破。

登录验证是 Web 系统安全的基石,掌握本文的核心技术,可搭建一套高效、安全的登录验证体系,为系统的稳定运行提供保障。

相关推荐
源代码•宸19 分钟前
goframe框架签到系统项目开发(实现总积分和积分明细接口、补签日期校验)
后端·golang·postman·web·dao·goframe·补签
无限进步_24 分钟前
【C语言】堆(Heap)的数据结构与实现:从构建到应用
c语言·数据结构·c++·后端·其他·算法·visual studio
初次攀爬者25 分钟前
基于知识库的知策智能体
后端·ai编程
喵叔哟25 分钟前
16.项目架构设计
后端·docker·容器·.net
强强强79526 分钟前
python代码实现es文章内容向量化并搜索
后端
A黑桃29 分钟前
Paimon 表定时 Compact 数据流程与逻辑详解
后端
掘金者阿豪30 分钟前
JVM由简入深学习提升分(生产项目内存飙升分析)
后端
天天摸鱼的java工程师34 分钟前
RocketMQ 与 Kafka 对比:消息队列选型的核心考量因素
java·后端
星浩AI36 分钟前
10 行代码带你上手 LangChain 智能 Agent
人工智能·后端
洛卡卡了40 分钟前
从活动编排到积分系统:事件驱动在业务系统中的一次延伸
前端·后端·面试