我之前就写过有关AngularJS+Spring Boot在有关跨域方面的博客,但那主要是针对解决问题,而没有去分析跨域。后来我在看了多篇博客之后,在这里整理总结下我对CORS的认识。
造成跨域的主要原因是由于请求方的1、域名 2、端口与被请求方不一致。而CORS可以提供跨域的资源分享(比如一个网站需要访问另一个网站的资源,这个资源可以是一张图片,或者是一个API接口),它需要前后端共同去实现。
支持CORS的浏览器有
1、Chrome 3+
2、Firefox 3.5+
3、Opera 12+
4、Safari 4+
5、IE 8+
CORS的请求方式分两种:简单请求和非简单请求。
简单请求满足下列两点:
1、HTTP Method为下列三种:HEAD、GET、POST
2、HTTP Headers中主要包括Accept、Accept-Language、Content-Language、Last-Event-ID以及Content-Type(Content-Type只能取1application/x-www-form-urlencoded、2multipart/form-data、3text/plain这三种)。
其他情况都属于非简单请求。
像目前比较常用的JSON-P来进行GET方法的跨域,用的就是简单请求。但是如果用JSON,那肯定就是非简单请求了。
下面我们来看简单请求:
首先用Javascript发送请求时与平时一样,但是Request的内容会发生改变,下图是我引用https://www.html5rocks.com/en/tutorials/cors/中的图片。
红色框框的部分是浏览器自动给我们加上的,Origin中的值主要有协议、域名、端口这几种。注意,CORS请求肯定有Origin这个属性,但是有这个属性并不一定是CORS请求。
当服务器接受到这个请求之后,它会返回给我们一个请求,这个Response请求中会包含下面几个属性:
Access-Control-Allow-Origin属性为服务器允许跨域访问的域名、
Access-Control-Allow-Credentials为是否允许写入Cookies。这个属性大家需要注意,它是一个可选属性,但是如果你要用到Session必须要打开,,所有的Cookie都由你请求的站点来控制,你是无法通过JavaScript来管理的。
Access-Control-Expose-Headers主要携带特殊Response中Header的信息。
下面来看看非简单请求:
当我们要发送PUT、DELETE请求,或者使用Json,那我我们会发送非简单请求,下面看张图。
从这张图中我们可以看出,非简单请求在发送真正的请求前会发送一次Preflight Request,接收一个Preflight Response。(这也是Preflight恶心的地方)。
下面我们来看一个Preflight Request:
这个Preflight Request不会携带Cookie和你要传递的参数,它携带
1、Origin 告诉服务器我的域名是多少
2、Access-Control-Request-Method 我真实请求的方法是什么
3、Access-Control-Request-Headers 真实请求携带的Header中的信息
然后服务器端给我们返回一个Preflight Response
主要到这个Response返回给我们的Methods包含了GET,POST,DELETE,PUT这个4个方法,而不是我们请求的GET,那是因为PreflightResponse可以缓存一段时间,在这段时间内,非简单请求就以这个Response为参考,符合这些条件就不必再发Preflight,而是直接开始真实的请求。
然后浏览器开始发送真实的请求:
上图为真实的请求。大家注意到这个请求携带了Cookie,这是因为我在发出请求时,就设置了.withCredentials=true(如果不知道如何设置,大家可以百度)
然后服务器会给我返回相应的Response,这个Response与简单请求的差不多。
以上就是CORS请求,浏览器与服务器交互的过程,这上面的坑主要是Preflight,如果我们的后台用了安全管理框架(比如Spring Security),并且没有对Preflight这个请求做出相应的处理,那么这个请求会导致权限管控失败(比如无法登录)。因为Preflight不携带Cookie,即不携带JSESSIONID,因此Spring Security拦截器会认为你没有登录。
在用Spring Security作为安全框架的情况下,处理这种问题也是非常简单的,下面给上代码
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()//就是这一行啦
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/teacher/**").hasRole("TEACHER")
.antMatchers("/student/**").hasRole("STUDENT")
.anyRequest().authenticated()
.and().formLogin().permitAll()
.and().formLogin().successHandler(authenticationSuccessionHandler)
.and().formLogin().failureUrl("http://localhost:63342/yjsy-ui/build/login/login.html")
.and().csrf().disable();
}
requestMatchers(CorsUtils::isPreFlightRequest).permitAll()的作用是将PreflightRequest不做拦截。