使用 Sa-Token 的全局过滤器解决跨域问题(三种方式全版)

在 web 开发中,跨域绝对是比较折磨新同学的一个问题,本文将讲解三种常见的跨域情形,并讲解如何使用 Sa-Token 框架解决跨域问题。

什么情况下会发生跨域

简单理解,就是你在 A 域名下的页面,去调用 B 域名的接口,浏览器感觉你这次调用可能是不安全的请求行为,于是它需要用 cors 安全策略来确认一下这个请求是由用户真实的意愿发出的,而不是被 csrf 伪造请求攻击偷偷发送的。(这么说只是为了方便大家理解,不是特别严谨,实际上同域名下部分情形也会出现跨域问题)

请仔细理解上面这段话,因为它说明了两点:

  • 跨域不是后端接口对前端浏览器的限制,而是前端浏览器对用户的限制。
  • 跨域不是在保护后端接口免受攻击,而是浏览器在保护用户,避免用户发送自己不想发送的请求。

请一定要记住上面跨域的本质,明白了症状和原因,我们才能对症下药。

一般情况下,我们会碰到三种跨域场景:

  • 1、本地页面调用测试服务器,只在项目开发阶段会有跨域问题。(比较简单)
  • 2、使用 header 头提交 token,产生的跨域问题。(比较常见+通用,推荐使用)
  • 3、使用第三方 Cookie 提交 token,产生的跨域问题。(最古老的方案,目前新版浏览器对此方案限制越来越严格,非必要不选择此方案,如果对此方案不是很熟悉就贸然使用也容易出现安全问题)

跨域情形一:只在项目开发阶段会有跨域问题

有些公司项目的开发方式为:

  • 在项目开发时:使用本地页面调用测试服务器接口。(域名不同,存在跨域问题)
  • 在项目部署时:将后端接口和前端页面部署在同一域名下。(域名一致,不存在跨域问题)

这种情况下比较好解决,在代码层面我们无需任何更改,只在前端客户端做出一定的更改就行了。比如说:在前端配置一个代理服务器,或者修改一下 Chrome 客户端使其去除跨域限制。

具体的方案有很多,大家可参考这篇博客:手把手教你解决web前端跨域问题

上面是说的普通前后端分离开发,而在APP、小程序 开发中,其天然就是个没有跨域限制的客户端,我们什么都不用做就能解决跨域问题。

跨域情形二:使用 header 头提交 token,产生的跨域问题(比较常见+通用,推荐使用)

当你使用 header 头提交 token 时,会产生跨域问题。此方案比较常见+通用,推荐使用。

jquery 代码示例:

	$.ajax({
		url: "/user/getInfo",
		type: "post", 
		data: {},
		dataType: 'json',
		headers: {
			"X-Requested-With": "XMLHttpRequest",
			// 重点处:请求的 header 头里塞入自定义参数
			"satoken": localStorage.getItem("satoken")
		},
		success: function(res){
			console.log(res);
		},
		error: function(xhr, type, errorThrown){
			return alert("异常:" + JSON.stringify(xhr));
		}
	});

Axios 代码示例:

    axios({
        url: "/user/getInfo",
        method: 'post',
        data: {},
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
			// 重点处:请求的 header 头里塞入自定义参数
            "satoken": localStorage.getItem("satoken")
        }
    }).
    then(function (response) { // 成功时执行
        const res = response.data;
		console.log(res);
    }).
    catch(function (error) {
        return alert("异常:" + JSON.stringify(error));
    })

此时在后端,我们应该添加以下响应头:

/**
 * [Sa-Token 权限认证] 配置类 
 *
 * @author click33
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

	/**
     * 注册 [Sa-Token 全局过滤器] 
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
        		
        		// 指定 [拦截路由] 与 [放行路由]
        		.addInclude("/**").addExclude("/favicon.ico")
        		
        		// 认证函数: 每次请求执行 
        		.setAuth(obj -> {
					SaManager.getLog().debug("----- 请求path={}  提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
        			// ...
        		})
        		
        		// 异常处理函数:每次认证函数发生异常时执行此函数 
        		.setError(e -> {
        			return SaResult.error(e.getMessage());
        		})
        		
        		// 前置函数:在每次认证函数之前执行
        		.setBeforeAuth(obj -> {
					SaHolder.getResponse()

        			// ---------- 设置跨域响应头 ----------
        			// 允许指定域访问跨域资源
        			.setHeader("Access-Control-Allow-Origin", "*")
        			// 允许所有请求方式
        			.setHeader("Access-Control-Allow-Methods", "*")
        			// 允许的header参数
        			.setHeader("Access-Control-Allow-Headers", "*")
        			// 有效时间
        			.setHeader("Access-Control-Max-Age", "3600")
        			;
        			
        			// 如果是预检请求,则立即返回到前端 
        			SaRouter.match(SaHttpMethod.OPTIONS)
        				.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
        				.back();
        		})
        		;
    }

}

如果你的项目是 WebFlux 环境,只需要把过滤器名称从 SaServletFilter 更换为 SaReactorFilter 即可,其它保持不变。

跨域情形三:使用第三方 Cookie 提交 token,产生的跨域问题。

这是最古老的方案,目前新版浏览器对此方案限制越来越严格,非必要不选择此方案,如果对此方案不是很熟悉就贸然使用也容易出现安全问题。

jquery 代码示例:

	$.ajax({
		url: "/user/getInfo",
		type: "post", 
		data: {},
		dataType: 'json',
		// 重点处:指定是跨域模式,需要提交第三方 Cookie 
		crossDomain: true,
		xhrFields:{
			withCredentials: true
		},
		headers: {
			"X-Requested-With": "XMLHttpRequest"
		},
		success: function(res){
			console.log(res);
		},
		error: function(xhr, type, errorThrown){
			return alert("异常:" + JSON.stringify(xhr));
		}
	});

Axios 代码示例:

    axios({
        url: "/user/getInfo",
        method: 'post',
        data: {},
		// 重点处:开启第三方 Cookie 
		withCredentials: true,
        headers: {
            "Content-Type": "application/x-www-form-urlencoded"
        }
    }).
    then(function (response) { // 成功时执行
        console.log(res);
    }).
    catch(function (error) {
        return alert("异常:" + JSON.stringify(error));
    })

此时在后端,我们应该添加以下响应头:

/**
 * [Sa-Token 权限认证] 配置类 
 *
 * @author click33
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

	/**
     * 注册 [Sa-Token 全局过滤器] 
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
        		
        		// 指定 [拦截路由] 与 [放行路由]
        		.addInclude("/**").addExclude("/favicon.ico")
        		
        		// 认证函数: 每次请求执行 
        		.setAuth(obj -> {
					SaManager.getLog().debug("----- 请求path={}  提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
        			// ...
        		})
        		
        		// 异常处理函数:每次认证函数发生异常时执行此函数 
        		.setError(e -> {
        			return SaResult.error(e.getMessage());
        		})
        		
        		// 前置函数:在每次认证函数之前执行
        		.setBeforeAuth(obj -> {

					// 获得客户端domain
					SaRequest request = SaHolder.getRequest();
					String origin = request.getHeader("Origin");
					if (origin == null) {
						origin = request.getHeader("Referer");
					}

        			// ---------- 设置跨域响应头 ----------
					SaHolder.getResponse()
					// 允许第三方 Cookie 
					.setHeader("Access-Control-Allow-Credentials", "true")
        			// 允许指定域访问跨域资源
        			.setHeader("Access-Control-Allow-Origin", origin)
        			// 允许所有请求方式
        			.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
					// 允许的header参数
					.setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken")
        			// 有效时间
        			.setHeader("Access-Control-Max-Age", "3600")
					;
        			
        			// 如果是预检请求,则立即返回到前端 
        			SaRouter.match(SaHttpMethod.OPTIONS)
        				.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
        				.back();
        		})
        		;
    }

}

如果你的项目是 WebFlux 环境,只需要把过滤器名称从 SaServletFilter 更换为 SaReactorFilter 即可,其它保持不变。

### sa-token 中 CORS 设置 为了在 sa-token 中配置资源共享(CORS),可以采用多种方式来满足不同应用场景的需求。通常情况下,在 Spring Boot 应用中集成 sa-token 时,可以通过全局过滤器或特定端点的方式来进行 CORS 配置。 #### 方法一:通过 WebMvcConfigurer 实现全局 CORS 支持 如果希望为整个应用程序提供统一的 CORS 策略,则可以在 `@Configuration` 类中实现 `WebMvcConfigurer` 接口并重写相应方法: ```java @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 对所有路径生效 .allowedOrigins("*") // 允许来自任何源的请求 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .maxAge(3600); } } ``` 这种方法适用于大多数情况下的简单需求[^2]。 #### 方法二:针对单个控制器或接口单独设置 CORS 属性 对于某些特殊场景下仅需对部分 API 开启 CORS 功能的情况,可以直接在 Controller 或者具体 Handler 上添加 `@CrossOrigin` 注解: ```java @RestController @RequestMapping("/api/v1") @CrossOrigin(origins = {"http://example.com"}, maxAge = 3600, methods={RequestMethod.GET}) public class MyController { @GetMapping("/data") public ResponseEntity<String> getData() { return new ResponseEntity<>("Data from server.", HttpStatus.OK); } } ``` 此法灵活性较高,适合细粒度控制各个资源之间的访问策略。 #### 方法三:利用 Sa-Token 自带功能处理预检请求 当涉及到更复杂的业务逻辑时,比如动态调整允许的 Origins 列表或是根据用户角色决定是否放行等操作,sa-token 提供了一种优雅的方式来拦截 OPTIONS 请求,并在此基础上做进一步判断: ```java import cn.dev33.satoken.interceptor.SaInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @Configuration public class SaTokenConfigure { private final List<String> allowedOrigins = Arrays.asList( "http://localhost:8080", "https://yourdomain.com" ); /** * Register the interceptor to handle preflight requests. */ public void registerInterceptors(InterceptorRegistry registry){ InterceptorRegistration ir = registry.addInterceptor(new SaInterceptor()); // Customize your rules here... ir.addPathPatterns("/**"); ir.excludePathPatterns("/login"); // Handle Preflight Requests ir.addPathPatterns("/").addHandlerMethod(this::handlePreflightRequest); } private boolean handlePreflightRequest(HttpServletRequest request, HttpServletResponse response) throws IOException{ if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { String originHeader = request.getHeader("Origin"); if (originHeader != null && allowedOrigins.contains(originHeader)) { response.setHeader("Access-Control-Allow-Origin", originHeader); response.setHeader("Access-Control-Allow-Methods", "*"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type"); response.setStatus(HttpServletResponse.SC_OK); return true; } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid Origin Header."); return false; } } return false; } } ``` 上述代码片段展示了如何借助 sa-token 的拦截机制捕获所有的 HTTP 请求,并特别关注于 OPTIONS 类型的预检请求。这样做的好处是可以集中管理规则而不必修改原有业务逻辑[^1]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值