什么情况下会发生跨域 简单理解,就是你在 A 域名下的页面,去调用 B 域名的接口,浏览器感觉你这次调用可能是不安全的请求行为,于是它需要用 cors 安全策略来确认一下这个请求是由用户真实的意愿发出的,而不是被 csrf 伪造请求攻击偷偷发送的。(这么说只是为了方便大家理解,不是特别严谨,实际上同域名下部分情形也会出现跨域问题)
请仔细理解上面这段话,因为它说明了两点:
跨域不是后端接口对前端浏览器的限制,而是前端浏览器对用户的限制。
跨域不是在保护后端接口免受攻击,而是浏览器在保护用户,避免用户发送自己不想发送的请求。
请一定要记住上面跨域的本质,明白了症状和原因,我们才能对症下药。
一般情况下,我们会碰到三种跨域场景:
1、本地页面调用测试服务器,只在项目开发阶段会有跨域问题。(比较简单)
2、使用 header 头提交 token,产生的跨域问题。(比较常见+通用,推荐使用)
3、使用第三方 Cookie 提交 token,产生的跨域问题。(最古老的方案,目前新版浏览器对此方案限制越来越严格,非必要不选择此方案,如果对此方案不是很熟悉就贸然使用也容易出现安全问题)
跨域情形一:只在项目开发阶段会有跨域问题 有些公司项目的开发方式为:
在项目开发时:使用本地页面调用测试服务器接口。(域名不同,存在跨域问题)
在项目部署时:将后端接口和前端页面部署在同一域名下。(域名一致,不存在跨域问题)
这种情况下比较好解决,在代码层面我们无需任何更改,只在前端客户端做出一定的更改就行了。比如说:在前端配置一个代理服务器,或者修改一下 Chrome 客户端使其去除跨域限制。
具体的方案有很多,大家可参考这篇博客:手把手教你解决web前端跨域问题
上面是说的普通前后端分离开发,而在APP、小程序 开发中,其天然就是个没有跨域限制的客户端,我们什么都不用做就能解决跨域问题。
当你使用 header 头提交 token 时,会产生跨域问题。此方案比较常见+通用,推荐使用。
jquery 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 js 代码解读复制代码 $.ajax ({ url : "/user/getInfo" , type : "post" , data : {}, dataType : 'json' , headers : { "X-Requested-With" : "XMLHttpRequest" , "satoken" : localStorage .getItem ("satoken" ) }, success : function (res ){ console .log (res); }, error : function (xhr, type, errorThrown ){ return alert ("异常:" + JSON .stringify (xhr)); } });
Axios 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 js 代码解读复制代码 axios ({ url : "/user/getInfo" , method : 'post' , data : {}, headers : { "Content-Type" : "application/x-www-form-urlencoded" , "satoken" : localStorage .getItem ("satoken" ) } }). then (function (response ) { const res = response.data ; console .log (res); }). catch (function (error ) { return alert ("异常:" + JSON .stringify (error)); })
此时在后端,我们应该添加以下响应头:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 java 代码解读复制代码 @Configuration public class SaTokenConfigure implements WebMvcConfigurer { @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" , "*" ) .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 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 js 代码解读复制代码 $.ajax ({ url : "/user/getInfo" , type : "post" , data : {}, dataType : 'json' , 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 代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 js 代码解读复制代码 axios ({ url : "/user/getInfo" , method : 'post' , data : {}, withCredentials : true , headers : { "Content-Type" : "application/x-www-form-urlencoded" } }). then (function (response ) { console .log (res); }). catch (function (error ) { return alert ("异常:" + JSON .stringify (error)); })
此时在后端,我们应该添加以下响应头:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 java 代码解读复制代码 @Configuration public class SaTokenConfigure implements WebMvcConfigurer { @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 -> { SaRequest request = SaHolder.getRequest(); String origin = request.getHeader("Origin" ); if (origin == null ) { origin = request.getHeader("Referer" ); } SaHolder.getResponse() .setHeader("Access-Control-Allow-Credentials" , "true" ) .setHeader("Access-Control-Allow-Origin" , origin) .setHeader("Access-Control-Allow-Methods" , "POST, GET, OPTIONS, DELETE" ) .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(); }) ; } }