在 web 开发中,跨域绝对是比较折磨新同学的一个问题,本文将讲解三种常见的跨域情形,并讲解如何使用 Sa-Token 框架解决跨域问题。
简单理解,就是你在 A 域名下的页面,去调用 B 域名的接口,浏览器感觉你这次调用可能是不安全的请求行为,于是它需要用 cors 安全策略来确认一下这个请求是由用户真实的意愿发出的,而不是被 csrf 伪造请求攻击偷偷发送的。(这么说只是为了方便大家理解,不是特别严谨,实际上同域名下部分情形也会出现跨域问题)
请仔细理解上面这段话,因为它说明了两点:
请一定要记住上面跨域的本质,明白了症状和原因,我们才能对症下药。
一般情况下,我们会碰到三种跨域场景:
有些公司项目的开发方式为:
这种情况下比较好解决,在代码层面我们无需任何更改,只在前端客户端做出一定的更改就行了。比如说:在前端配置一个代理服务器,或者修改一下 Chrome 客户端使其去除跨域限制。
具体的方案有很多,大家可参考这篇博客:手把手教你解决web前端跨域问题
上面是说的普通前后端分离开发,而在APP、小程序 开发中,其天然就是个没有跨域限制的客户端,我们什么都不用做就能解决跨域问题。
当你使用 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
即可,其它保持不变。
这是最古老的方案,目前新版浏览器对此方案限制越来越严格,非必要不选择此方案,如果对此方案不是很熟悉就贸然使用也容易出现安全问题。
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
即可,其它保持不变。