序言
目前项目开发流行前后端分离,前后分离势必会出现CORS问题了。CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。本文主要讨论前后端分离之后对于Cors问题的解决方案。需要说明的是本文章前端使用Angular9,后端使用的是SpringBoot 2.1.3.RELEASE。
一、场景
前端发起请求主要关注JSON形式和JSONP形式。
1.1 JSON数据请求场景
前端请求代码实现形式,这里使用的是HttpClientModule。
首先在app.module.ts中引入要使用的模块
import { HttpClientModule, HttpClientJsonpModule } from '@angular/common/http';
然后实现方法doPost()
import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpHeaders} from '@angular/common/http';
@Component({
selector: 'app-httprequest',
templateUrl: './httprequest.component.html',
styleUrls: ['./httprequest.component.css']
})
export class HttprequestComponent implements OnInit {
constructor(public httpClient: HttpClient) { }
ngOnInit() {
}
doPost() {
const url = `http://localhost:8080/interface/testPost`;
const httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
this.httpClient.post(url,{"username":"张三丰","age":45}, httpOptions).subscribe( (response) => {
console.log(response);
this.masterInfo = response;
});
}
}
后端SpringBoot应用解决Json数据请求的跨域问题有多种:
1、使用@CrossOrigin注解来实现跨域,使用注解可以以最细粒度控制可跨域范围,此注解可添加在Controller类上和方法上,Spring Framework 4.2开始支持此注解。
2、CORS全局配置一个CorsConfigure类实现WebMvcConfigure接口或者继承WebMvcConfigurerAdapter类,形式都是一样,都是给CorsRegistry 设置跨域允许的请求源:
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.maxAge(3600)
.allowCredentials(true);
}
}
3、实现Filter接口自定义一个允许跨域的过滤器
@WebFilter(urlPatterns = "/*")
public class CorsFilter implements Filter {
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException,
ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin","*");
filterChain.doFilter(request, servletResponse);
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
1.2 JSONP数据请求场景
这需要说明一下浏览器的特性,浏览器不允许页面中的脚本程序跨域读取数据,但却允许HTML引用跨域的资源,如图片,CSS和脚本程序。对于脚本程序的引用比较特殊,它被浏览器解析以后,就和本地的脚本程序别无二致且可立即进行解释并执行。
前端JSONP请求需要用到的依赖HttpClientJsonpModule。请求实现方式是使用HttpClient发起jsonp请求。
import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpHeaders} from '@angular/common/http';
@Component({
selector: 'app-httprequest',
templateUrl: './httprequest.component.html',
styleUrls: ['./httprequest.component.css']
})
export class HttprequestComponent implements OnInit {
constructor(public httpClient: HttpClient) { }
ngOnInit() {
}
getJSONP() {
/* jsonp请求,需要服务器支持才行 */
const url = `http://localhost:8080/interface/testJSONP`;
this.httpClient.jsonp(url, "CallBack").subscribe((response: any) => {
console.log(response);
console.log(response.master);
});
}
}
服务器端接口对应的实现方式,这里使用的JSONObject是阿里的fastjson
@RequestMapping("/testJSONP")
@ResponseBody
public String testJSONP(@RequestParam("CallBack") String callback) {
//加上返回参数
JSONObject jsonObject = new JSONObject();
jsonObject.put("result","200");
jsonObject.put("master","charberming");
jsonObject.put("appname","app1");
System.out.println(callback+":"+jsonObject.toString());
/* 必须和前端请求 url 链接中带的回调函数名一致 如 http://localhost:8080/interface/testJSONP?CallBack=callbac ,要求返回json字符串形式*/
return callback+"("+jsonObject+")"; /* Only JSON Object Or JSON String is OK {"result":"200","appname":"app1","master":"charberming"} */
}
这里返回的数据必须是由前端请求url上带着的callback参数拼接的json字符串格式,否则前端会报错。在这里由于我们在前端请求参数中设置的回调参数为CallBack,所以在服务端程序中,我们要拿到的参数也是CallBack,保持一致。
注:主要是在学习angular过程中,出现跨域问题,网上找的办法主要是在前端设置,比如说angular设置代理文件启动,还有说使用nginx反向代理,前者说是能解决跨域问题,但是后者就太扯淡了,但还是都去验证了下,没成功。因此在这里做一些总结,以节省道友时间。