一、前言

登录认证是后端开发里的基础内容,也是面试高频点。

刚学的时候最容易乱的就是这几个词:

  • Cookie
  • Session
  • Token
  • JWT

它们都和“登录状态”有关,但不是一回事。
这篇文章我就用最通俗的方式,把它们讲清楚。


二、先记住一句话

Cookie 是浏览器保存的小纸条,Session 是服务器保存的会话,Token 是身份令牌,JWT 是一种标准化的 Token。


三、Cookie 是什么

Cookie 是浏览器端保存的一小段数据,服务器可以把它发给浏览器,浏览器后续请求时会自动带上。

它的特点是:

  • 存在客户端
  • 数据量小
  • 会自动随请求携带
  • 适合保存少量状态,不适合放敏感信息

1. Cookie 示例

@GetMapping("/c1")
public Result cookie1(HttpServletResponse response){
    response.addCookie(new Cookie("login_username","itheima"));
    return Result.success();
}

@GetMapping("/c2")
public Result cookie2(HttpServletRequest request){
    Cookie[] cookies = request.getCookies();
    for (Cookie cookie : cookies) {
        if(cookie.getName().equals("login_username")){
            System.out.println("login_username: " + cookie.getValue());
        }
    }
    return Result.success();
}

这段代码说明了两件事:

  • cookie1 往浏览器里写 Cookie
  • cookie2 从请求里把 Cookie 取出来

一句话理解:

Cookie 更像浏览器替你保存的一张小卡片。


四、Session 是什么

Session 是服务器端保存的会话数据,通常用来记录当前用户是否登录、是谁登录了、权限是什么。

它的特点是:

  • 存在服务器端
  • 更适合保存登录状态
  • 服务器能主动管理失效
  • 用户多时会增加服务器压力

1. Session 示例

项目里的代码也很直观:

@GetMapping("/s1")
public Result session1(HttpSession session){
    log.info("HttpSession-s1: {}", session.hashCode());

    session.setAttribute("loginUser", "tom");
    return Result.success();
}

@GetMapping("/s2")
public Result session2(HttpSession session){
    log.info("HttpSession-s2: {}", session.hashCode());

    Object loginUser = session.getAttribute("loginUser");
    log.info("loginUser: {}", loginUser);
    return Result.success(loginUser);
}

这里做的事情就是:

  • /s1 往 Session 里存数据(set)
  • /s2 再从 Session 里取数据(get)

2. Cookie 和 Session 的关系

很多人会误以为它们是对立的,其实不是。

通常是这样配合的:

  • Session 存在服务器
  • 浏览器用 Cookie 带着 sessionId
  • 服务器根据 sessionId 找到对应 Session

一句话理解:

Cookie 负责“带编号”,Session 负责“存内容”。


五、Token 是什么

Token 就是“令牌”。这里不是我们平常所说的ai算力

它是一种认证思路,不是某一种固定格式。
登录成功后,服务器给前端一个 Token,之后前端每次请求都带着它,服务器通过校验 Token 判断用户身份。

一句话理解:

Token 就像你的身份证


六、JWT 是什么

JWT 全称是 JSON Web Token,它是 Token 的一种标准格式。

JWT 一般长这样:

header.payload.signature

它由三部分组成:

  • Header:说明算法和类型
  • Payload:保存用户信息和过期时间
  • Signature:签名,用来防篡改

1. JWT 的特点

  • 结构清晰
  • 适合前后端分离
  • 服务器可以少存状态
  • 适合分布式和微服务

2. JWT 的注意点

  • 不要放密码
  • 不要放特别敏感的信息
  • 一定要设置过期时间
  • 密钥不能随便泄露

3.Maven依赖

       <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

七、JWT 示例代码

1. 登录请求对象

public class LoginRequest {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

2. 登录返回对象

public class LoginResponse {
    private Integer userId;
    private String username;
    private String token;

    public LoginResponse(Integer userId, String username, String token) {
        this.userId = userId;
        this.username = username;
        this.token = token;
    }

    public Integer getUserId() {
        return userId;
    }

    public String getUsername() {
        return username;
    }

    public String getToken() {
        return token;
    }
}

3. JWT 工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtils {

    private static final String SECRET_KEY = "demo-secret-key";
    private static final long EXPIRE_TIME = 2 * 60 * 60 * 1000;

    public static String createToken(Integer userId, String username) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("username", username);

        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .addClaims(claims)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME))
                .compact();
    }

    public static Claims parseToken(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }
}

4. 登录接口

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {

    @PostMapping("/login")
    public LoginResponse login(@RequestBody LoginRequest request) {
        if ("admin".equals(request.getUsername()) && "123456".equals(request.getPassword())) {
            String token = JwtUtils.createToken(1, request.getUsername());
            return new LoginResponse(1, request.getUsername(), token);
        }
        throw new RuntimeException("用户名或密码错误");
    }

    @GetMapping("/profile")
    public String profile() {
        return "这是一个需要登录后才能访问的接口";
    }
}

5. JWT 拦截器

import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;

public class JwtInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String path = request.getRequestURI();

        if ("/auth/login".equals(path)) {
            return true;
        }

        String token = request.getHeader("token");
        if (token == null || token.isEmpty()) {
            response.setStatus(401);
            response.getWriter().write("token 不能为空");
            return false;
        }

        try {
            Claims claims = JwtUtils.parseToken(token);
            System.out.println("当前用户: " + claims.get("username"));
            return true;
        } catch (Exception e) {
            response.setStatus(401);
            response.getWriter().write("token 无效或已过期");
            return false;
        }
    }
}

6. JWT 过滤器

除了拦截器,其实过滤器也可以做 JWT 登录校验。

很多同学刚学到这里时容易分不清:

  • FilterServlet 规范里的组件
  • InterceptorSpring MVC 里的组件

它们都能做登录认证,但触发位置不一样。

简单理解一下:

  • 过滤器更靠前,请求刚进来时就能处理
  • 拦截器更靠近 Controller
  • 过滤器拦截范围更广
  • 拦截器更适合结合 Spring MVC 的处理流程做控制

如果只是做一个简单的 JWT 校验,过滤器也完全能胜任。下面我单独写一个更容易理解的示例。

import io.jsonwebtoken.Claims;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class JwtFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String uri = httpRequest.getRequestURI();

        // 登录接口直接放行
        if ("/auth/login".equals(uri)) {
            chain.doFilter(request, response);
            return;
        }

        // 从请求头中获取 token
        String token = httpRequest.getHeader("token");

        // 如果没带 token,直接拦截
        if (token == null || token.isEmpty()) {
            httpResponse.setStatus(401);
            httpResponse.getWriter().write("未登录,请先携带 token");
            return;
        }

        try {
            Claims claims = JwtUtils.parseToken(token);
            System.out.println("过滤器校验通过,当前用户:" + claims.get("username"));
            chain.doFilter(request, response);
        } catch (Exception e) {
            httpResponse.setStatus(401);
            httpResponse.getWriter().write("token 无效或已过期");
        }
    }
}

这段代码的逻辑也不复杂:

  1. 先拿到当前请求路径
  2. 如果是登录接口,直接放行
  3. 如果不是登录接口,就从请求头里取 token
  4. token 不存在,直接返回 401
  5. token 存在,就解析它
  6. 解析成功,继续访问后面的资源
  7. 解析失败,返回 401

这里有一个很重要的点要记住:

过滤器和拦截器都能做登录校验,但实际项目里通常不会把同一套 JWT 校验逻辑同时在两边都写一遍,否则很容易重复校验。

一般来说:

  • 想在更前面统一处理请求,可以用过滤器
  • 想结合 Spring MVC 处理器链来控制,可以用拦截器

7. 注册认证组件

前面我们已经写了拦截器,现在又补了一版过滤器。
那这两个东西怎么注册呢?

7.1 注册拦截器

拦截器还是按 Spring MVC 的方式注册:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JwtInterceptor())
                .addPathPatterns("/**");
    }
}

这表示所有请求都会先经过 JwtInterceptor

7.2 注册过滤器

如果过滤器上用了 @WebFilter,那么 Spring Boot 启动类还要加上 @ServletComponentScan,这样容器才能扫描到过滤器。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
7.3 实际开发怎么选?

如果项目里只是做普通的接口登录校验,常见做法有两种:

  • 用过滤器统一校验 token
  • 用拦截器统一校验 token

更推荐的思路是:

同一层认证逻辑尽量只放一个入口,别过滤器和拦截器同时做同样的事。

否则会出现这些问题:

  • 重复解析 token
  • 代码职责不清
  • 出错时排查麻烦

8. 整个 JWT 示例的执行流程

把前面的代码串起来,完整流程就是这样:

  1. 用户输入用户名和密码
  2. 前端调用 /auth/login
  3. 后端校验用户名和密码
  4. 校验成功后生成 JWT
  5. 后端把 token 返回给前端
  6. 前端保存 token
  7. 前端访问受保护接口时,在请求头中携带 token
  8. 过滤器或者拦截器统一校验 token
  9. 校验通过,放行请求
  10. 校验失败,返回 401

到这里,其实 JWT 登录认证的核心链路就已经完整了。


八、登录流程图

用户输入用户名和密码

前端发送登录请求

服务器校验账号密码

是否校验通过

返回登录失败

生成 JWT

返回 token

前端保存 token

后续请求访问受保护接口

过滤器或拦截器校验 token

token 是否有效

返回 401

放行请求并返回业务数据


九、JWT 时序图

过滤器或拦截器 服务器 前端 用户 过滤器或拦截器 服务器 前端 用户 alt [token 合法] [token 非法或过期] 输入用户名和密码 POST /auth/login 校验账号密码 返回 token 请求业务接口并携带 token 先进入过滤器或拦截器 校验 token 放行请求 返回业务数据 返回 401

十、过滤器和拦截器怎么区分

这一块也是很多同学容易混的点,这里顺手总结一下。

对比项 过滤器 Filter 拦截器 Interceptor
所属规范 Servlet 规范 Spring MVC
执行时机 更早,进入 Servlet 之前 更靠后,进入 Controller 之前
依赖环境 依赖 Web 容器 依赖 Spring MVC
拦截范围 更广 主要拦截 Controller 请求
适合场景 编码处理、统一日志、权限前置控制 登录校验、权限判断、业务控制

如果只用一句话来区分,可以记成:

过滤器更像“保安”,拦截器更像“单元楼的门禁”。

保安是请求一进楼就先检查,门禁则是准备进入具体业务区域前再核验一次。


十一、Cookie、Session、Token、JWT 区别总结

技术 存储位置 作用 优点 缺点
Cookie 浏览器 保存少量状态 自动携带 不适合敏感数据
Session 服务器 保存登录会话 服务端可控 分布式麻烦
Token 客户端 身份凭证 灵活 需要自己设计校验
JWT Token 的一种格式 标准化令牌 无状态,适合集群 不容易立刻失效

十二、什么时候用谁?

1. 用 Session 的场景

  • 传统 Web 项目
  • 单体系统
  • 后台管理系统
  • 用户量不大

2. 用 JWT 的场景

  • 前后端分离
  • Vue / React 项目
  • App / 小程序接口
  • 微服务 / 分布式系统

3. 一句话建议

  • 单体系统,Session 很方便
  • 分离式系统,JWT 更常见
  • 安全要求高时,要配合黑名单、刷新令牌等机制

十三、面试题总结版

1. 过滤器和拦截器的区别是什么?

过滤器属于 Servlet 规范,拦截器属于 Spring MVC。
过滤器执行更早,范围更广;拦截器更贴近 Controller,适合业务层面的统一控制。

2. Cookie 和 Session 有什么区别?

Cookie 存在浏览器端,Session 存在服务器端。
Cookie 常常负责带 sessionId,Session 负责保存登录状态。

3. Token 和 JWT 有什么区别?

Token 是令牌的统称,JWT 是 Token 的一种标准格式。

4. 为什么前后端分离常用 JWT?

因为它更适合接口化调用,也更适合集群部署,不需要像 Session 那样强依赖服务端保存会话。

5. JWT 为什么不能存密码?

因为 JWT 的载荷部分通常可以被解码看到,所以不能直接放敏感信息。

6. JWT 的三部分是什么?

  • Header
  • Payload
  • Signature

7. 过滤器和拦截器都能做登录认证吗?

都可以。
但实际项目里一般二选一,或者各司其职,不建议把同一套 token 校验逻辑重复写两遍。


十四、总结

学登录认证时,最重要的不是死记硬背概念,而是先把它们之间的关系理顺。

可以这样记:

  • Cookie 是浏览器保存的小纸条
  • Session 是服务器保存的会话
  • Token 是身份令牌
  • JWT 是一种标准化的 Token
  • FilterInterceptor 都能做认证校验,只是位置和职责不同

如果是传统项目,Session 很常见。
如果是前后端分离项目,JWT 很常见。
如果再往下细分到认证入口,过滤器和拦截器都能做,但最好职责清晰,不要重复校验。


我是新手程序猿乐锅。本次分享到此结束,感谢大家的观看与支持!如果本期内容对您有帮助,欢迎点赞、收藏,您的支持将是我持续创作的最大动力,谢谢!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐