当前位置: 首页 > news >正文

从 “跨域报错到彻底解决”:Spring Boot+Security+JWT 实战踩坑指南

作为后端开发者,你是否曾遇到过这样的场景:前端调用接口时控制台疯狂报错 Access to fetch at 'xxx' from origin 'xxx' has been blocked by CORS policy,注释掉一行 permitAll() 就跨域,加上又能正常访问?明明配置了 CorsFilter 却始终不生效,甚至出现 Failed to load response data: No data found for resource with given identifier 这样的 “幽灵错误”?

跨域问题看似简单,实则涉及浏览器同源策略、Spring 过滤器链、Spring Security 拦截逻辑等多个层面的联动。本文将从实际项目踩坑场景出发,用 “问题复现→底层原理→分步解决→代码实战” 的逻辑,带你彻底搞懂跨域问题的本质,掌握 Spring Boot+Security+JWT 环境下跨域配置的标准方案,从此不再被跨域 “折磨”。

一、问题复现:那些年我们踩过的跨域坑

在开始讲解原理前,先复现几个实际项目中高频出现的跨域场景,看看你是否也曾遇到过类似问题。

1.1 场景 1:配置了 CorsFilter,注释 permitAll () 就跨域

项目环境

  • Spring Boot 3.2.5(最新稳定版)
  • Spring Security 6.2.4
  • JDK 17
  • JWT 认证(jjwt-api 0.12.5)

核心代码

(1)CORS 配置类(WebDefaultConfig.java)
package com.sq.twinbee.config.web;import org.springframework.context.annotation.Bean;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** Web基础配置类,包含CORS、资源映射、路径匹配等配置* @author ken*/
public class WebDefaultConfig implements WebMvcConfigurer {/*** 跨域过滤器配置* 解决前后端分离场景下的跨域请求问题* @return CorsFilter 跨域过滤器实例*/@Beanpublic CorsFilter corsFilter() {CorsConfiguration config = new CorsConfiguration();// 允许所有来源(此处为后续问题埋下伏笔)config.addAllowedOriginPattern("*");// 允许所有HTTP方法(GET、POST、PUT、DELETE、OPTIONS等)config.addAllowedMethod("*");// 允许所有请求头(包括自定义头如token、app-id)config.addAllowedHeader("*");// 允许携带凭证(如Cookie、JWT Token)config.setAllowCredentials(true);// 预检请求缓存时间(秒),减少重复预检请求config.setMaxAge(3600L);UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();// 对所有/api/**路径的请求应用跨域配置configSource.registerCorsConfiguration("/api/**", config);return new CorsFilter(configSource);}/*** 静态资源映射配置* 解决Swagger、前端静态资源访问问题* @param registry 资源处理器注册器*/@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/**").addResourceLocations("classpath:/static/").addResourceLocations("classpath:/META-INF/resources/");}/*** 路径匹配配置:忽略URL大小写* 提高接口访问的容错性* @param configurer 路径匹配配置器*/@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {AntPathMatcher matcher = new AntPathMatcher();// 设置路径匹配不区分大小写matcher.setCaseSensitive(false);configurer.setPathMatcher(matcher);}
}
(2)Spring Security 配置类(SecurityConfig.java)
package com.sq.twinbee.config.security;import com.sq.twinbee.config.web.point.JwtAuthenticationEntryPoint;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;/*** Spring Security配置类,负责认证、授权、会话管理等配置* @author ken*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {private final JwtAuthenticationFilter jwtAuthenticationFilter;private final AuthenticationProvider authenticationProvider;private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;/*** 配置Security过滤器链* 定义请求授权规则、认证异常处理、会话策略等* @param http HttpSecurity配置对象* @return SecurityFilterChain 安全过滤器链实例* @throws Exception 配置过程中可能抛出的异常*/@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// 关闭CSRF保护(JWT认证场景下无需CSRF).csrf(csrf -> csrf.disable())// 配置未认证请求的处理策略.exceptionHandling(ex -> ex.authenticationEntryPoint(jwtAuthenticationEntryPoint))// 配置会话策略:无状态(JWT是无状态认证).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 配置请求授权规则.authorizeHttpRequests(auth -> auth// Swagger3相关路径允许匿名访问.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()// 登录、部门列表接口允许匿名访问.requestMatchers("/api/client/department/login").permitAll().requestMatchers("/api/client/department/getDepartmentList").permitAll()// 关键配置:/api/client/**路径允许匿名访问(注释后跨域报错).requestMatchers("/api/client/**").permitAll()// 其他静态资源允许匿名访问.requestMatchers("/doc.html", "/webjars/**", "/favicon.ico").permitAll()// 所有其他请求必须认证.anyRequest().authenticated())// 设置认证提供者.authenticationProvider(authenticationProvider)// 在UsernamePasswordAuthenticationFilter前添加JWT认证过滤器.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}
}
(3)认证失败处理类(JwtAuthenticationEntryPoint.java)
package com.sq.twinbee.config.web.point;import com.alibaba.fastjson2.JSON;
import com.sq.twinbee.config.exception.ExceptionEnum;
import com.sq.twinbee.model.ApiResult;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.nio.charset.StandardCharsets;/*** JWT认证入口点,处理未认证的请求* 当用户访问受保护资源但未提供有效认证信息时被调用* @author ken*/
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {/*** 当认证失败时调用此方法* 返回401未授权响应给前端* @param request 请求对象* @param response 响应对象* @param authException 认证异常(包含认证失败原因)* @throws IOException IO异常(如响应流写入失败)*/@Overridepublic void commence(HttpServletRequest request,HttpServletResponse response,AuthenticationException authException) throws IOException {// 记录认证失败日志,包含请求URL和异常信息(便于问题排查)log.warn("访问未授权资源,URL: {}, 原因: {}", request.getRequestURI(), authException.getMessage());// 设置响应内容类型和编码(确保前端能正确解析JSON)response.setContentType("application/json;charset=UTF-8");response.setCharacterEncoding(StandardCharsets.UTF_8.name());// 设置响应状态码:401未授权response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);// 构建统一响应结果(符合项目接口规范)ApiResult<Void> result = ApiResult.error(ExceptionEnum.NO_PERMISSION.getResultCode(),ExceptionEnum.NO_PERMISSION.getResultMsg(),"访问被拒绝,请先进行认证: " + authException.getMessage());// 将响应结果写入输出流(返回给前端)response.getWriter().write(JSON.toJSONString(result));}
}

问题现象

  • 保留 .requestMatchers("/api/client/**").permitAll() 时,前端调用 /api/client/work/order/list 接口正常,无跨域报错;
  • 注释掉该配置后,前端立即报错:Access to fetch at 'http://10.10.10.13:8866/twinbee/api/client/work/order/list' from origin 'http://10.10.10.237:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.,同时 Network 面板显示 Failed to load response data: No data found for resource with given identifier

1.2 场景 2:OPTIONS 预检请求被拦截,跨域直接失败

问题现象:前端发送带自定义头(如 token: Bearer xxx)的请求时,浏览器先发送 OPTIONS 预检请求,但该请求被后端拦截,返回 403 或 401 状态码,导致后续业务请求无法执行。

Network 面板关键信息

  • 请求方法:OPTIONS
  • 请求地址:http://10.10.10.13:8866/twinbee/api/client/work/order/list
  • 状态码:403 Forbidden
  • Response Headers:空(无任何 CORS 相关头)

1.3 场景 3:配置了 CorsFilter,但响应始终无 CORS 头

问题现象:明明在 WebDefaultConfig 中配置了 CorsFilter,但前端请求的响应头中始终没有 Access-Control-Allow-OriginAccess-Control-Allow-Credentials 等字段,跨域报错无法解决。

Network 面板关键信息

  • 状态码:200 OK(业务逻辑正常)
  • Response Headers:仅包含 Content-Type: application/json;charset=UTF-8Date: Tue, 14 May 2024 08:00:00 GMT 等基础头,无任何 CORS 头。

二、底层原理:搞懂跨域问题的 “根”

要解决跨域问题,必须先搞懂 浏览器同源策略 和 CORS(跨域资源共享)机制 的底层逻辑 —— 这是所有跨域配置的理论基础,也是避免 “头痛医头” 的关键。

2.1 同源策略:浏览器的 “安全门卫”

2.1.1 什么是同源?

“同源” 指的是 协议、域名、端口三者完全一致。例如:

  • http://a.com:8080 与 http://a.com:8080/api:同源(协议、域名、端口均一致);
  • http://a.com:8080 与 https://a.com:8080:不同源(协议不同:http vs https);
  • http://a.com:8080 与 http://b.com:8080:不同源(域名不同:a.com vs b.com);
  • http://a.com:8080 与 http://a.com:8081:不同源(端口不同:8080 vs 8081)。
2.1.2 同源策略的作用

同源策略是浏览器的核心安全机制,目的是 防止恶意网站窃取其他网站的敏感数据。它会限制以下行为:

  1. 无法读取不同源网页的 Cookie、
http://www.dtcms.com/a/466137.html

相关文章:

  • 嵌入模型蓝图与扫盲
  • 中核华泰建设有限公司网站小游戏网站网址
  • 做网站技术服务费属于什么科目做ppt好用的网站有哪些
  • 简单网站建设方案icp备案查询官方网站
  • Python中内置的常用装饰器
  • 年度进化挑战:从“前端实现者”到“智能体协作者”与“复杂问题定义者”
  • 靠谱的CC公益站
  • 做网站哪家便宜如何做企业招聘网站
  • HI3798MV100 运营商机顶盒NAS LINUX OS - IP地址静态绑定指南
  • 外贸网站建设公司价格湛江建站程序
  • 第三章:字符串增强与模板字符串
  • 网站开发建设技术规范书没经验可以做电商运营吗
  • Jira:设置语言 / 创建史诗 / 创建冲刺 / 创建问题
  • CancellationToken与Abort
  • linux达梦数据库操作
  • [自荐]一款mac电脑历史剪切板工具,类似著名的Paste
  • 二级域名可以做不同的网站吗网站建设网络推广广告语
  • MapReduce简介
  • FreeType 2.7 – 卓越的 Linux 字体质量
  • 龙江建站技术wordpress 管理 主题
  • 企业网站建设 制作网站建设基本流程流程图
  • 【Homebrew安装 MySQL 】macOS 用 Homebrew 安装 MySQL 完整教程
  • 【图像处理基石】暗光增强算法入门:从原理到实战(Python+OpenCV)
  • Asp.net core Kestrel服务器详解
  • OpenFeign使用
  • 如何在鸿蒙中实现毫秒级数据检索?哈希表与二分查找的双引擎优化方案
  • 实现支持链式调用的 JavaScript 类
  • 中文wordpress模板免费seo教程分享
  • 如何用ps做网站顺德网站建设7starry
  • 京东agent之joyagent解读