最近在学习vue, 因为前后端分离, 所以前端请求后端的时候会出现跨域问题, 记录一下
场景:
前端js: localhost:8080
let send = function() {
let req = new XMLHttpRequest()
req.onreadystatechange = function() {
if (req.readyState === 4 && req.status === 200) {
console.log(req.responseText)
}
}
// 发送请求:
req.open('GET', 'http://localhost:9999/hello');
req.send();
}
let _main = function() {
send()
}
_main()
后端java: localhost:9999
@RequestMapping("/hello")
public String hello()
{
return "你好";
}
于是产生问题
大致意思是说CORS策略已阻止从源“ *”访问“ *”处的XMLHttpRequest:请求的资源上不存在“ Access-Control-Allow-Origin”标头。
解决方案1: jsonp
script标签的src属性是可以跨域的, 比如经常引用各种cdn
js:
// 全局回调执行函数
window.handleCallback = function(res) {
console.log(JSON.stringify(res))
}
let jsonp = function() {
let script = document.createElement('script')
script.type = 'text/javascript'
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://localhost:9999/jsonp?callback=handleCallback'
document.head.appendChild(script)
console.log(script)
}
let _main = function() {
// send()
jsonp()
}
_main()
java:
@RequestMapping("/jsonp")
public String jsonp(String callback)
{
String json = "{\"name\":\"Louise\", \"age\":16}";
return callback + "(" + json + ")";
}
结果:
解决方案2: cors
报错指明被请求的资源上不存在Access-Control-Allow-Origin的header, 那么在响应被请求资源的时候加上指定的header即可
这里请求被分为简单请求和复杂请求
简单请求:
- (1)请求方法是以下三种方法之一: HEAD, GET, POST
- (2)HTTP的头信息不超出以下几种字段:Accept, Accept-Language, Content-Language, Last-Event-ID
- (3)Content-Type:只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
对于简单请求, 服务端在响应时携带Access-Control-Allow-Origin: *即可
@RequestMapping(value = "/hello")
public String hello(HttpServletResponse resp)
{
// 可接受的域
resp.setHeader("Access-Control-Allow-Origin", "*");
return "你好";
}
let send = function() {
let req = new XMLHttpRequest()
req.onreadystatechange = function() { // 状态发生变化时,函数被回调
if (req.readyState === 4 && req.status === 200) {
console.log(req.responseText)
console.log(req.response)
}
}
// 发送请求:
req.open('GET', 'http://localhost:9999/hello');
req.send();
}
let _main = function() {
send()
}
_main()
复杂请求:
不符合简单请求的条件,会被浏览器判定为复杂请求, 例如DELETE请求
对于复杂请求, 浏览器会先发送一次域检请求, 请求方式为OPTIONS, 若服务器给出正确响应后, 才会发送真正的请求
响应域检请求时会多携带一些头信息, 具体见下方java代码
let send = function() {
let req = new XMLHttpRequest()
req.onreadystatechange = function() { // 状态发生变化时,函数被回调
if (req.readyState === 4 && req.status === 200) {
console.log(req.responseText)
console.log(req.response)
}
}
// 发送请求:
req.open('DELETE', 'http://localhost:9999/hello');
req.send();
}
let _main = function() {
send()
}
_main()
编写拦截器, 既能处理简单请求, 又能处理复杂请求
@Component
public class CORSInterceptor implements HandlerInterceptor
{
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler)
{
String origin = req.getHeader("Origin");
if (origin == null)
{
resp.addHeader("Access-Control-Allow-Origin", "*");
}
else
{
resp.addHeader("Access-Control-Allow-Origin", origin);
}
resp.addHeader("Access-Control-Allow-Headers", "Origin, x-requested-with, Content-Type, Accept,X-Cookie");
resp.addHeader("Access-Control-Allow-Credentials", "true");
resp.addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS,DELETE");
if (req.getMethod().equals("OPTIONS"))
{
// 若是域检请求就不放行了
resp.setStatus(HttpServletResponse.SC_OK);
return false;
}
return true;
}
}
复杂请求的例子没有直接在controller中处理, 一是因为这个需求放在拦截器中正好合适, 二是因为SpringMvc处理类似delete, put之类的请求很诡异, 所以在请求到达controller之前就处理, 以免引入新的问题
当然, SpringMvc已经提供了跨域的过滤器CorsFilter
解决方案3: CorsFilter
// 统一处理跨域问题
@Configuration
public class CorsConfig
{
@Bean
public CorsFilter corsFilter()
{
//1.添加CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//1) 允许的域,不要写*,否则cookie就无法使用了
config.addAllowedOrigin("http://localhost:8080");
//2) 是否发送Cookie信息
config.setAllowCredentials(true);
//3) 允许的请求方式
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
// 4)允许的头信息
config.addAllowedHeader("*");
//2.添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
//3.返回新的CorsFilter.
return new CorsFilter(configSource);
}
}