在不断变化的 Web 开发和应用安全领域,强健的身份认证机制比以往任何时候都更重要。当我们将 API 暴露给公众时,必须确保只有授权的用户才能访问资源。
一、传统 Session + Cookie 认证
1-1、单体应用场景(传统 Session + Cookie)
-
用户在浏览器访问网站,提交用户名和密码。
-
服务器验证成功后,会在 服务器内存/数据库 中创建一个 Session 对象 ,保存用户信息(如 userId、角色、登录时间等),并生成一个唯一的 SessionID。
-
服务器把这个 SessionID 通过 Set-Cookie 响应头返回给浏览器:
Set-Cookie: JSESSIONID=abc123; Path=/; HttpOnly
-
之后用户每次发起请求时,浏览器会自动在 HTTP 请求头中带上 Cookie:
Cookie: JSESSIONID=abc123
-
服务器收到请求后,根据
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 部分主要是用来存放与用户和会话相关的信息,如
username
、roles
、tenantId
等,用于服务端快速鉴权和授权。【注意】:
不要放敏感信息
JWT 的 Payload 是 Base64URL 编码,任何人都能解码并查看内容。
过期时间必须设置
exp
是必须的,避免令牌长期有效导致安全风险。
2.3 Signature(签名)
对 base64url(header) + "." + base64url(payload)
进行签名:
-
对称:
HS256
(一把 secret,签验同键,简单但要妥善保密) -
非对称:
RS256
/ES256
(私钥签名,公钥验签,更适合微服务与密钥轮换)
图中底部展示的是 Base64URL 编码 (URL 安全字符集,通常无
=
填充)。再次强调 :这是可解码的,不是加密。
2-3. 认证流程

-
登录 :校验用户名/密码 → 生成 JWT(含
sub/exp
等) → 返回给客户端(常见做法:放在Authorization: Bearer <token>
头;也可放 HttpOnly Cookie)。 -
访问受保护资源 :客户端在请求头携带
Bearer
token。 -
服务端校验:
-
验签(算法与密钥/公钥)
-
校时(
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 时用的一样)
-
-
校验时会做两件事:
-
验证签名是否匹配,防止 token 被篡改;
-
验证过期时间、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;
});
这种方式是最常见的,需要前端显式处理。
2. 如果你把 JWT 存在 Cookie 里
-
另一种做法是后端在登录成功时把 JWT 放在 Set-Cookie 响应头里:
Set-Cookie: token=xxx; HttpOnly; Secure
-
这样浏览器后续请求时会自动带上 Cookie(前提是同域或允许跨域携带 Cookie)。
但:
-
用 Cookie 方式虽然省事,但可能不完全符合 无状态 JWT 的设计理念;
-
而且如果 Cookie 没有设置
HttpOnly
、Secure
等安全属性,容易被 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 预检请求:
javaif ("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(如UserService
、RedisTemplate
)。 -
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:8080
和http://localhost:8080/api
同源 -
但以下都算跨域:
-
http://localhost:8080
→http://localhost:8081
(端口不同) -
http://localhost:8080
→https://localhost:8080
(协议不同) -
http://a.com
→http://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,就会导致:
-
浏览器发的预检请求(OPTIONS)不带 Token;
-
拦截器发现没有 Token → 返回 401;
-
浏览器就认为服务端拒绝跨域,正式请求根本发不出去。
解决办法:在拦截器中对 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 是浏览器自动发的探测包,后端拦截器需要允许它通过,否则前端请求会在浏览器端被拦掉。