记一次SpringBoot中跨域的小问题
记一次SpringBoot中跨域的小问题
问题
前阵子,有个学长在跨域的时候遇到一个问题,我们两个人互相讨论了一番,得到了问题的答案。问题如下:
如果按照上图的方式配置跨域类,那么就会出现报403的问题,我把我的配置类发给学长后,果然是没问题了,那么看来问题就出在两个配置类里不一样的地方了
解决方式
对比两张图中的配置,可以发现出在了allowedOrigin
和.allowedOriginPatterns
这两个方法上,于是乎我到官方文档上查阅了这两个方法的详细介绍,得到了如下内容:
现在终于恍然大悟了,文档里都介绍的很清楚了。但是知道了解决方式以后,我去看了一下源码,了解一下到底为什么可以这么做.
源码+相关内容
config变量是CorsConfiguration
的一个实例化对象。那么看来这两个方法的区别要更深一层,同样是点进去看一下,先看第一个。
入参是可以为空的字符串列表,判空之后对非空情况进行一个常见的处理,使用的是函数式编程,其中有一个名为trimTrailingSlash
的方法,这个看名字应该是和MySQL里的trim差不多的,点进去看一下
果不其然,作用是去掉传入URL最后的一个/。到这里这个方法就全部结束了,看着也太简单了,果然是被抛弃了的一个方法
上图是第二个方法的内容,同样入参一个可以为空的字符串列表,关键在于这个addAllowedOriginPattern
方法,继续点进去看
入参为字符串类型的originPattern
,这里就体现出来一个setAllowedOriginPatterns
的好处了,如果我们传入了多个需要跨域的格式,那么MVC会帮我们挨个进行处理,调用trimTrailingSlash
方法进行/的剔除。看到这里,你可能会有些疑惑,这样看来两个方法不是没什么区别吗?是的,如果看到这里,那确实没啥区别,但是真正的区别其实是下面这个:
allowedOrigins
是给setAllowedOrigin
用的,可以看到列表中的元素类型为String类,那么下面这个allowedOriginPatterns
是给setAllowedOriginPatterns
用的,列表内容是OriginPattern类,这个类从来没听过,而且一看名字就知道是Spring自己写的,所以我们继续点进去看。
类里有一个字符串,两个正则,那肯定两个正则是处理这个字符串的。构造方法OriginPattern
,初始化正则格式。这个正则还是很好看懂的,\E和\Q转义出来其实就是要匹配*这个字符,这也就是为什么setAllowedOriginPatterns
在官方文档中介绍说可以在传入跨域格式的字符串中填写*号,而不用是已经完全确定的一个格式。
关于CORS中Access-Control-Allow-Origin
这个header,规定我们只能设置为如下三种情况:
-
- 单域名
- none
同时如果我们设置为了*,那么Access-Control-Allow-Credentials
就不能设置为true了。跨源资源共享(CORS) - HTTP | MDN (mozilla.org)
基于以上的规则,我们总结一下:如果我们需要发送Cookie,那么前端需要设置withCredentials
为true,且同时后端也要设置Access-Control-Allow-Credentials
为true,并且Access-Control-Allow-Credentials
不能设置为*,只能为单域名
但是这时候就又出现一个问题,我的跨域配置类中,withCredentials传参是*啊,而且也不是单域名,为什么跨域还是成功了呢?学了这么长时间的Spring,我多少有个猜测——肯定也是有个很牛逼的东西帮我处理了这个问题
@Nullable
public String checkOrigin(@Nullable String origin) {
if (!StringUtils.hasText(origin)) {
return null;
} else {
String originToCheck = this.trimTrailingSlash(origin);
Iterator var3;
if (!ObjectUtils.isEmpty(this.allowedOrigins)) {
if (this.allowedOrigins.contains("*")) {
this.validateAllowCredentials();
return "*";
}
var3 = this.allowedOrigins.iterator();
while(var3.hasNext()) {
String allowedOrigin = (String)var3.next();
if (originToCheck.equalsIgnoreCase(allowedOrigin)) {
return origin;
}
}
}
if (!ObjectUtils.isEmpty(this.allowedOriginPatterns)) {
var3 = this.allowedOriginPatterns.iterator();
while(var3.hasNext()) {
OriginPattern p = (OriginPattern)var3.next();
if (p.getDeclaredPattern().equals("*") || p.getPattern().matcher(originToCheck).matches()) {
return origin;
}
}
}
return null;
}
}
果不其然,我找到了这个名为checkOrigin
的方法,内容中明确写道。如果我们同时将credentials设置为true且allowedPatterns为*,那么这个方法就会把Access-Control=Allow-Origin
设置为当前请求过来的origin,也就解决了这个问题。