SpringBoot12-JSON Web Token(JWT)

在不断变化的 Web 开发和应用安全领域,强健的身份认证机制比以往任何时候都更重要。当我们将 API 暴露给公众时,必须确保只有授权的用户才能访问资源。


1-1、单体应用场景(传统 Session + Cookie)

  1. 用户在浏览器访问网站,提交用户名和密码。

  2. 服务器验证成功后,会在 服务器内存/数据库 中创建一个 Session 对象 ,保存用户信息(如 userId、角色、登录时间等),并生成一个唯一的 SessionID

  3. 服务器把这个 SessionID 通过 Set-Cookie 响应头返回给浏览器:

    复制代码
    Set-Cookie: JSESSIONID=abc123; Path=/; HttpOnly
  4. 之后用户每次发起请求时,浏览器会自动在 HTTP 请求头中带上 Cookie:

    复制代码
    Cookie: JSESSIONID=abc123
  5. 服务器收到请求后,根据 JSESSIONID 去查找内存/数据库中对应的 Session 对象,确认用户身份。

在单台服务器的情况下,Session 就保存在内存中,查找很快,也很简单。


1-2、微服务/多服务器场景下的问题

假设公司要扩展系统,原来一台服务器不够用了,于是上线了多台服务器做负载均衡(Load Balancer)。

  • 用户第一次登录时,请求被分配到 Server A

    SessionID=abc123 被保存在 Server A 内存

  • 用户第二次请求时,负载均衡把流量分到了 Server B

    Server B 并没有 abc123 这个 Session,于是用户会被当作未登录,要求重新登录。

⚠️ 这就导致:

  • Session 状态绑定在某台服务器上,无法无缝切换。

  • 要么做"会话粘滞(Session Sticky)"------强制用户一直打到同一台服务器。

  • 要么把 Session 存在 Redis 或数据库等共享存储中,增加了复杂度和性能开销。


微服务架构下的典型痛点

在微服务架构中,可能有十几个、几十个服务(例如:用户服务、订单服务、支付服务)。如果用 Session:

  • 所有服务都要访问共享的 Session 存储(Redis/DB),增加依赖和维护成本;

  • 每次请求都要去 Session 存储中查找信息,增加网络开销;

  • 部署和扩容时需要额外保证 Session 数据一致性。


1-3、为什么要用 JWT?

  • 传统的 Session + Cookie 认证需要服务器保存用户的会话信息,扩展性差(特别是微服务架构中)。

  • JWT无状态 的,服务端不需要保存会话,所有信息(用户身份、过期时间、角色等)都放在 Token 内,由客户端保存和携带。


举个直观类比

  • Session + Cookie 像寄存行李:你在 A 站台寄存了行李(服务器保存 Session),给你一个寄存小票(SessionID)。如果你跑到 B 站台去取行李,B 站台找不到你的行李(因为行李在 A 站台),就拿不到东西。

  • JWT 像随身带身份证:你拿着签发好的身份证(JWT Token),去哪个站台都能直接验证身份,不需要去找行李。


二、JWT

2-1. JWT 是什么 & 解决什么问题

JWT(JSON Web Token)是一种 自包含(self-contained)的令牌格式,用来在双方之间安全地传递声明(claims)。

****典型用法是用户登录后,服务端签发一个 JWT,之后客户端在每次请求里带上它,服务端凭签名校验 即可确认身份,无需在服务器保存会话(对比 Session)。

优点

  • 无状态、易水平扩展,天然适配微服务 / 网关。

  • 解析快,跨语言生态完善。

注意

JWT 默认只是签名(JWS),不是加密。

****能被任何人 Base64URL 解码查看内容,但不能伪造签名。所以,不要存放私密信息,若需要保密,请使用 JWE(加密的 JWT)或只放最小必要信息。


2-2. 结构拆解(对应图里的三段)

图中的三段是将对应的json内容,做base64编码后,形成的内容。

复制代码
header.payload.signature

2.1 Header(头)

最常见键:

  • alg: 签名算法,如 HS256(HMAC-SHA256)、RS256(RSA-SHA256)

  • typ: 一般为 "JWT"

  • kid:(可选)Key ID,便于密钥轮换

示例: {"alg":"HS256","typ":"JWT","kid":"v3-2025-10"}

2.2 Payload(有效载荷,Claims)

  • 注册声明

    • iss(issuer,签发者)

    • sub(subject,主体,通常为用户ID)

    • aud(audience,受众/客户端或服务名)

    • exp(过期时间,秒级 UTC)

    • nbf(在此之前无效)

    • iat(签发时间)

    • jti(JWT ID,用于防重放/黑名单)

  • 自定义声明 :如 roles, tenantId, scope 等。越少越好,避免泄露 PII。

示例:

复制代码
{
  "iss": "auth.example.com",
  "sub": "user:123",
  "aud": "orders-api",
  "exp": 1730467200,
  "iat": 1730463600,
  "roles": ["USER","ADMIN"]
}

JWT 的 Payload 部分主要是用来存放与用户和会话相关的信息,如 usernamerolesemailtenantId 等,用于服务端快速鉴权和授权。

【注意】:

不要放敏感信息

JWT 的 Payload 是 Base64URL 编码,任何人都能解码并查看内容。

过期时间必须设置
exp 是必须的,避免令牌长期有效导致安全风险。

2.3 Signature(签名)

base64url(header) + "." + base64url(payload) 进行签名:

  • 对称:HS256(一把 secret,签验同键,简单但要妥善保密

  • 非对称:RS256/ES256(私钥签名,公钥验签,更适合微服务与密钥轮换

图中底部展示的是 Base64URL 编码 (URL 安全字符集,通常无 = 填充)。再次强调 :这是可解码的,不是加密。


2-3. 认证流程

  1. 登录 :校验用户名/密码 → 生成 JWT(含 sub/exp 等) → 返回给客户端(常见做法:放在 Authorization: Bearer <token> 头;也可放 HttpOnly Cookie)。

  2. 访问受保护资源 :客户端在请求头携带 Bearer token。

  3. 服务端校验

    • 验签(算法与密钥/公钥)

    • 校时(exp/nbf/iat + 容忍几秒时钟偏差)

    • 校语境(iss/aud

    • 可选:校业务版号(如 tokenVersion

    • 通过则在 SecurityContext 注入认证信息与权限。


2-3. 在 Spring Boot 3 中怎么落地(JJWT 方案)

添加maven依赖:

XML 复制代码
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
</dependency>

编写Java 代码:

java 复制代码
@Test
void genToken() {
    // 1. 定义要放进 JWT 里的用户信息(自定义 claims)
    Map<String, Object> claims = new HashMap<>();
    claims.put("id", "1");
    claims.put("username", "张三");

    // 2. 创建 Token
    String token = JWT.create()
            .withClaim("user", claims)      // 把上面定义的 Map 放入一个名为 "user" 的 Claim
            .withExpiresAt(new Date(System.currentTimeMillis() + 1000*60*60*3)) // 过期时间:当前时间+3小时
            .sign(Algorithm.HMAC256("itheima")); // 使用 HMAC256 算法和密钥 "itheima" 签名

    // 3. 打印生成的 token
    System.out.println(token);
}

关键点解释

  • Map<String, Object> claims

    用来保存自定义数据(比如用户 id、用户名)。这些数据会写进 JWT 的 Payload 部分。

  • .withClaim("user", claims)

    把整个 claims Map 作为一个名为 "user" 的字段写入 token。

  • .withExpiresAt(...)

    设置 token 的过期时间。这里是当前时间 + 1000*60*60*3 毫秒(3 小时)。

  • .sign(Algorithm.HMAC256("itheima"))

    HMAC SHA-256 算法对 token 进行签名,密钥是 "itheima"

    只有知道这个密钥的服务端才能验证 token 的合法性。

JWT 生成后的结构

生成的 JWT 一般分三段,用点号 . 分隔:

Header.Payload.Signature

  • Header :算法和类型(如 { "alg": "HS256", "typ": "JWT" }

  • Payload :用户数据(这里就是 user: {id:1, username:三})+ 系统字段(exp、iat)

  • Signature:用密钥和算法生成的签名,防止被篡改。

2-4、JWT的检验

java 复制代码
@Test
void parseToken() {
    // 1. 假设这是之前生成的 JWT
    String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
                   ".eyJ1c2VyIjp7ImlkIjoiMSIsInVzZXJuYW1lIjoi5byg5LiJIn0sImZpcnN0TmFtZSI6luW8oCIsImV4cCI6MTY5MzY5OTA0MH0" +
                   ".j1_xvimDV4AD4fHACzFFNi7LH0r5HAjh8wF-Qqu7Baw";

    // 2. 构造一个校验器(指定算法和密钥)
    JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("itheima")).build();

    // 3. 校验并解析 token
    DecodedJWT decodedJWT = jwtVerifier.verify(token);

    // 4. 获取所有 Claims
    Map<String, Claim> claims = decodedJWT.getClaims();

    // 5. 打印 "user" 字段
    System.out.println(claims.get("user"));
}

关键点讲解

(1) JWT.require(Algorithm.HMAC256("itheima")).build()

  • 创建一个 JWT 验证器,指定:

    • 签名算法HMAC256

    • 签名密钥"itheima"(必须和生成 token 时用的一样)

  • 校验时会做两件事:

    1. 验证签名是否匹配,防止 token 被篡改;

    2. 验证过期时间、Issuer 等约束(如果有配置)。

(2) jwtVerifier.verify(token)

  • 真正去解析并校验 token。

  • 如果 token 非法(签名不对、已过期、格式错误),这里会抛出异常,如:

    • SignatureVerificationException(签名错误)

    • TokenExpiredException(token 已过期)

    • JWTDecodeException(格式不对)

(3) decodedJWT.getClaims()

  • 获取 payload 中所有的声明(claims)。

  • 返回值是 Map<String, Claim>,key 是 claim 名称。

(4) claims.get("user")

  • 取出之前在生成 token 时 withClaim("user", claims) 放入的那部分自定义数据。

用户登录成功后 ,系统会自动下发 JWT 令牌,然后在后续的每次请求中,浏览器都需要在请求头 header 中携带到服务端,请求头的名称为 Authorization,值为 登录时下发的 JWT 令牌

如果检测到用户未登录,则 http 响应状态码为 401。

【备注】:和其他状态码的区别

状态码 含义 使用场景
401 Unauthorized 没有提供有效的认证信息 Token 缺失、过期、签名错误
403 Forbidden 已认证,但没有权限 用户已登录但没有访问某接口的权限
400 Bad Request 请求参数错误 请求格式不对、参数不合法
404 Not Found 资源不存在 URL 不存在或隐藏

三、前端处理JWT

1. 浏览器 不会自动 把 JWT 加到请求头里

  • HTTP 标准里,Authorization 这个头部不会像 Cookie 那样被浏览器自动携带。

  • 所以如果你用 JWT + Authorization 头 这种方案,前端必须自己在每次请求时手动把 Token 加上去

例如在前端(Vue / React / Axios)常见写法:

javascript 复制代码
// 登录成功后把 token 存储起来(通常在 localStorage 或 sessionStorage)
localStorage.setItem("token", response.data.token);

// 后续请求时加到请求头
axios.get("/api/user/info", {
  headers: {
    Authorization: "Bearer " + localStorage.getItem("token")
  }
});

或者用全局请求拦截器:

javascript 复制代码
axios.interceptors.request.use(config => {
  const token = localStorage.getItem("token");
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

这种方式是最常见的,需要前端显式处理


  • 另一种做法是后端在登录成功时把 JWT 放在 Set-Cookie 响应头里:

    复制代码
    Set-Cookie: token=xxx; HttpOnly; Secure
  • 这样浏览器后续请求时会自动带上 Cookie(前提是同域或允许跨域携带 Cookie)。

但:

  • 用 Cookie 方式虽然省事,但可能不完全符合 无状态 JWT 的设计理念;

  • 而且如果 Cookie 没有设置 HttpOnlySecure 等安全属性,容易被 XSS 攻击窃取。


3. 总结对比

存储方式 前端是否要手动加到请求头 特点
LocalStorage / SessionStorage ✅ 需要手动加 灵活、和 REST API 常见做法,但要注意 XSS 风险
Cookie ❌ 浏览器自动带 简单,但要注意 CSRF 和安全设置

目前多数 SPA(Vue/React)项目都选择 LocalStorage + Axios 拦截器 ,手动把 JWT 加到 Authorization 头中。


四、拦截器

4-1、为什么要用拦截器

  • 集中处理:不用在每个 Controller 手写校验逻辑,统一放在拦截器。

  • 更安全:防止忘记在某个接口里写验证逻辑。

  • 可扩展:后续可以在拦截器中增加日志、权限检查等功能。

4-2、代码的编写

javascript 复制代码
@Component
public class JwtInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        String token = request.getHeader("Authorization");
        if (token == null || token.isEmpty()) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401
            return false;
        }
        try {
            JWT.require(Algorithm.HMAC256("itheima")).build().verify(token);
            return true; // 验证通过, 放行
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
    }
}

然后在 WebMvcConfigurer 中注册这个拦截器:

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    // 在拦截器中jwtInterceptor已经通过@component注入到bean中
    // 所以,这里可以通过@Autowired获取
    @Autowired
    private JwtInterceptor jwtInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/**")            // 拦截所有接口
                .excludePathPatterns("/login");    // 登录接口放行
    }
}

4-3、【回顾】拦截器

系统地梳理一下 Spring Boot 中拦截器(Interceptor)的通用写法,包括定义、注册和常见使用场景。


1、拦截器的作用

  • 统一处理请求:在 Controller 方法执行前、后进行处理,比如:

    • 登录验证 / 权限校验

    • 记录日志

    • 统计接口耗时

  • 与过滤器 Filter 区别

    • Filter 是 Servlet 级别,更底层,和 Spring 无关;

    • Interceptor 是 Spring MVC 提供的,更面向业务,支持依赖注入。


2、HandlerInterceptor 接口

Spring MVC 提供了 HandlerInterceptor 接口(最常用),包含三个方法:

java 复制代码
public interface HandlerInterceptor {

    // Controller 方法执行前(类似前置处理)
    default boolean preHandle(HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler) throws Exception {
        return true; // 返回 false 会中断请求
    }

    // Controller 方法执行后,视图渲染前
    default void postHandle(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            @Nullable ModelAndView modelAndView) throws Exception {}

    // 完成请求后(视图渲染后或异常发生后)
    default void afterCompletion(HttpServletRequest request,
                                 HttpServletResponse response,
                                 Object handler,
                                 @Nullable Exception ex) throws Exception {}
}

三个阶段:

  • preHandle:请求到达 Controller 前执行(最常用,用来校验 Token、权限等)。

  • postHandle :Controller 执行完成,但还没渲染视图。

  • afterCompletion:整个请求结束后,做资源清理或记录日志。


3、通用写法

① 定义拦截器类
java 复制代码
@Component
public class JwtInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(">>> preHandle: 请求开始");

        // 假设要做登录校验
        String token = request.getHeader("Authorization");
        if (token == null || token.isEmpty()) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401
            response.getWriter().write("Unauthorized");
            return false; // 中断后续执行
        }

        // TODO: 可以在这里解析 token,验证合法性
        return true; // 返回 true 继续执行 Controller
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(">>> postHandle: Controller 执行完毕");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        System.out.println(">>> afterCompletion: 请求结束");
    }
}

② 注册拦截器

有两种常见注册方式:

方式 1:实现 WebMvcConfigurer
java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private JwtInterceptor jwtInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/**")          // 拦截所有请求
                .excludePathPatterns("/login",   // 放行登录接口
                                     "/register",
                                     "/error");  // 放行异常接口
    }
}
方式 2:使用 Spring Boot 自动装配

如果拦截器上加了 @Component,直接通过 new JwtInterceptor() 注册也行(但推荐方式 1 因为支持依赖注入)。


③ 请求流程
复制代码
[前端请求] --> preHandle() --> Controller --> postHandle() --> 视图渲染 --> afterCompletion()
  • preHandle() 返回 false 时:请求被拦截,不会继续执行 Controller。

  • postHandle()afterCompletion() 只有在 preHandle() 返回 true 时才执行。


4、常见使用场景

场景 写法示例
JWT 登录校验 preHandle 检查 Header 中的 Token,验证后放行
权限控制 preHandle 判断用户角色是否匹配
统一日志记录 preHandle 记录请求信息;afterCompletion 记录耗时
防重复提交 preHandle 中用 Redis/内存加锁
国际化语言切换 preHandle 根据 Header 设置 Locale

5、小技巧与注意事项

  • excludePathPatterns 一定要配置登录、注册、静态资源等不需要拦截的路径,否则可能导致死循环或无法访问登录页。

  • 对于跨域 (CORS),如果用拦截器校验 Token,要注意放行 OPTIONS 预检请求:

    java 复制代码
    if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
        return true;
    }
  • 如果需要在拦截器里使用 Spring Bean(例如 RedisTemplate、UserService),记得用 @Component 或构造注入。


6、总结

通用写法 = 实现 HandlerInterceptor + 在 WebMvcConfigurer 中注册

  • preHandle:权限验证、登录校验、接口限流

  • postHandle:对返回结果做处理、修改模型数据

  • afterCompletion:日志、资源清理

最常用的就是 JWT 校验统一登录验证


4-4、对比 Spring 容器管理的 Bean手动 new 出来的对象 的区别

上文说到,注册拦截器的方式2:使用 Spring Boot 自动装配

如果拦截器上加了 @Component,直接通过 new JwtInterceptor() 注册也行(但推荐方式 1 因为支持依赖注入)。

这其实是,对比 Spring 容器管理的 Bean手动 new 出来的对象 的区别,背后关系到 依赖注入 (DI) 和 Spring 管理生命周期。


1、背景

在 Spring Boot 中,拦截器本质上是一个普通的 Java 类。

  • 如果你在类上加了 @Component,Spring 就会自动扫描并创建它的 Bean 实例,放进容器里。

  • 如果你没有加 @Component,就只能自己用 new 去创建一个对象。


2、两种注册方式对比

方式 1:依赖注入(推荐)
java 复制代码
@Component  // 让 Spring 托管
public class JwtInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService; // 假设要用到业务层
    ...
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private JwtInterceptor jwtInterceptor; // 注入 Spring 容器里的拦截器实例

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

特点:

  • JwtInterceptor 被 Spring 托管,可以使用 @Autowired 注入别的 Bean(如 UserServiceRedisTemplate)。

  • Spring 管理它的生命周期,自动单例、自动初始化。


方式 2:手动 new(不推荐)
java 复制代码
// @Component
public class JwtInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService; // ⚠️ 注意:这里不会生效
    ...
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 手动创建一个拦截器对象
        registry.addInterceptor(new JwtInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login");
    }
}

⚠️ 缺点:

  • new JwtInterceptor() 得到的对象是普通的 Java 对象,不在 Spring 容器管理范围内。

  • 拦截器里的 @Autowired@Value 等注入将 无法生效(因为 Spring 不知道你手动 new 的对象)。

  • 不享受 Spring 生命周期管理(比如 AOP、事务增强、配置属性注入都失效)。


3、总结

方式 对象是否在 Spring 容器中 是否能 @Autowired 推荐度
@Component + @Autowired 注入 ⭐⭐⭐⭐⭐
手动 new

一句话记忆:

如果拦截器需要用到 Spring 里的任何 Bean(数据库服务、配置、Redis 等),一定要让 Spring 托管(@Component + 注入)。

如果只是一个完全独立的简单拦截器(不用依赖 Spring),可以手动 new,但这种情况很少。

4-5、跨域 (CORS)

1、什么是跨域 (CORS)

同源策略 (Same-Origin Policy) :浏览器的一种安全机制,要求前端网页和后端接口必须"同源"才能直接通信。

"同源" = 协议、域名、端口三者都相同

  • 例如:http://localhost:8080http://localhost:8080/api 同源

  • 但以下都算跨域

    • http://localhost:8080http://localhost:8081 (端口不同)

    • http://localhost:8080https://localhost:8080 (协议不同)

    • http://a.comhttp://b.com (域名不同)

CORS (Cross-Origin Resource Sharing):浏览器为了安全限制跨域请求,但提供的一种标准解决方案。

  • 允许服务器通过返回特殊的 HTTP 响应头告诉浏览器:哪些域可以访问我的资源。

例如后端返回:

复制代码
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
Access-Control-Allow-Headers: Authorization,Content-Type

前端就可以从 http://localhost:3000 发起跨域请求了。


2、为什么有 OPTIONS 预检请求

当浏览器发现前端要发一个"复杂请求"(带自定义 Header、使用 PUT/DELETE 等方法)时,会先发一个 OPTIONS 请求,询问服务器:

"我可以用这种方式请求你吗?"

这就是 CORS 预检请求 (Preflight Request)

  • 请求示例:

    复制代码
    OPTIONS /api/user/info HTTP/1.1
    Origin: http://localhost:3000
    Access-Control-Request-Method: GET
    Access-Control-Request-Headers: Authorization
  • 如果服务器允许,就返回:

    复制代码
    HTTP/1.1 204 No Content
    Access-Control-Allow-Origin: http://localhost:3000
    Access-Control-Allow-Methods: GET,POST,PUT,DELETE
    Access-Control-Allow-Headers: Authorization,Content-Type
  • 浏览器收到允许后,才会继续发真正的 GET/POST 请求。


3、和拦截器的关系

拦截器会在请求到达 Controller 前先执行校验。

如果你在 preHandle() 里写了严格的 Token 校验,没有特别放行 OPTIONS,就会导致:

  1. 浏览器发的预检请求(OPTIONS)不带 Token;

  2. 拦截器发现没有 Token → 返回 401;

  3. 浏览器就认为服务端拒绝跨域,正式请求根本发不出去。

解决办法:在拦截器中对 OPTIONS 请求放行:

java 复制代码
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    // 如果是预检请求,直接放行
    if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
        return true;
    }

    String token = request.getHeader("Authorization");
    if (token == null || token.isEmpty()) {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        return false;
    }

    // ... 验证 token 合法性
    return true;
}

总结

  • 跨域 (CORS) = 浏览器安全策略 + 服务器配置允许不同源访问。

  • OPTIONS 预检请求 = 浏览器在真正发送请求前先问"可不可以用这种方式访问",用来检查跨域是否被允许。

  • 拦截器注意点

    • 如果校验 Token 时不放行 OPTIONS,跨域请求会失败;

    • 正确做法是直接放行 OPTIONS,让预检顺利通过。

一句话记忆

跨域是浏览器的安全限制,OPTIONS 是浏览器自动发的探测包,后端拦截器需要允许它通过,否则前端请求会在浏览器端被拦掉。

相关推荐
Terio_my2 小时前
Spring Boot 缓存集成实践
spring boot·后端·缓存
JIngJaneIL6 小时前
图书馆自习室|基于SSM的图书馆自习室座位预约小程序设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·图书馆自习室
不要再敲了6 小时前
SSM框架下的redis使用以及token认证
数据库·spring boot·redis·缓存·mybatis
Terio_my7 小时前
Spring Boot 集成 EHCache 缓存解决方案
spring boot·spring·缓存
岁岁岁平安7 小时前
SpringBoot3+WebSocket+Vue3+TypeScript实现简易在线聊天室(附完整源码参考)
java·spring boot·websocket·网络协议·typescript·vue
java水泥工9 小时前
网上摄影工作室|基于SpringBoot和Vue的网上摄影工作室(源码+数据库+文档)
数据库·vue.js·spring boot
计算机毕业设计小帅14 小时前
【2026计算机毕业设计】基于Springboot的Android校园周边美食汇系统
android·spring boot·课程设计
花花鱼16 小时前
spring boot项目使用tomcat发布,也可以使用Undertow(理论)
spring boot·后端·tomcat
这里是杨杨吖18 小时前
SpringBoot+Vue医院预约挂号系统 附带详细运行指导视频
vue.js·spring boot·医院·预约挂号