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 是浏览器自动发的探测包,后端拦截器需要允许它通过,否则前端请求会在浏览器端被拦掉。

相关推荐
后端小张12 分钟前
【JAVA 进阶】Mybatis-Plus 实战使用与最佳实践
java·spring boot·spring·spring cloud·tomcat·mybatis·mybatis plus
摇滚侠2 小时前
Spring Boot3零基础教程,SpringApplication 自定义 banner,笔记54
java·spring boot·笔记
摇滚侠2 小时前
Spring Boot3零基础教程,Spring Boot 完成了哪些Spring MVC 自动配置,笔记49
spring boot·spring·mvc
摇滚侠5 小时前
Spring Boot3零基础教程,KafkaTemplate 发送消息,笔记77
java·spring boot·笔记·后端·kafka
计算机学长felix8 小时前
基于SpringBoot的“面向校园的助力跑腿系统”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·后端
java水泥工9 小时前
课程答疑系统|基于SpringBoot和Vue的课程答疑系统(源码+数据库+文档)
spring boot·vue·计算机毕业设计·java毕业设计·大学生毕业设计·课程答疑系统
Rocket MAN11 小时前
Spring Boot 缓存:工具选型、两级缓存策略、注解实现与进阶优化
spring boot·后端·缓存
程序定小飞14 小时前
基于springboot的民宿在线预定平台开发与设计
java·开发语言·spring boot·后端·spring
FREE技术14 小时前
山区农产品售卖系统
java·spring boot
摇滚侠16 小时前
Spring Boot3零基础教程,云服务停机不收费,笔记71
java·spring boot·笔记