对于前后端分离的项目来说,如果前端项目与后端项目部署在两个不同的域下,那么势必会引起跨域问题的出现。
跨域指的是从一个域名去请求另外一个域名的资源。即跨域名请求,跨域时,浏览器不能执行其他域名网站的脚本,是由浏览器的 “同源策略” 造成的,是浏览器施加的安全限制。跨域的严格一点来说就是只要协议,域名,端口有任何一个的不同,就被当作是跨域。
下面举个例子:
判断下面 URL 是否和 http://www.baidu.com/a/a.html 同源
综上所述,在同源策略的限制下,非同源的网站之间不能发送 AJAX 请求。如有需要,可通过降域或其他技术实现。
下面实现一个案例来验证一下
package com.example.demo.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("server")
public class TestController {
@RequestMapping("test")
public String test(HttpServletResponse httpResponse,HttpSession session){
return "阿三";
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<div id="msg">
</div>
<script type="text/javascript">
$(document).ready(function(){
$.ajax({
url:'http://localhost:8090/server/test',
type:'get',
dataType:'json',
success:function(res){
console.log(res);
$("#msg").html(res.msg);
},
error:function(){
console.log("error");
}
})
});
</script>
</body>
</html>
那么如何解决跨域问题?,跨域问题解决方案有很多,本文主要讲通过 CORS 协议解决跨域问题。
CORS 是一个 W3C 标准,全称是 "跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。CORS 需要浏览器和服务器同时支持。它的通信过程,都是浏览器自动完成,不需要用户参与。
对于开发者来说,CORS 通信与同源的 AJAX/Fetch 通信没有差别,代码完全一样。浏览器一旦发现请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
在 CORS 出现前,发送 HTTP 请求时在头信息中不能包含任何自定义字段,且 HTTP 头信息不超过以下几个字段:
Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(值限于 3 个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)
这里给出一个例子:
GET /baidu HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, sdch, br
Origin: http://www.baidu.com
Host: www.baidu.com
对于简单请求,CORS 的策略是请求时在请求头中增加一个 Origin 字段,服务器收到请求后,根据该字段判断是否允许该请求访问。
对于非简单请求的跨源请求,浏览器会在真实请求发出前,增加一次 OPTION 请求,称为预检请求 (preflight request) 。预检请求将真实请求的信息,包括请求方法、自定义头字段、源信息添加到 HTTP 头信息字段中,询问服务器是否允许这样的操作。
下面的请求会触发预检请求:
以下是一个发起预检请求的例子:
发起请求的 origin 与请求的服务器的 host 不同,而且根据上面的条件判断,触发了预检
当预检请求通过后,浏览器才会发送真实请求到服务器。这样就实现了跨域资源的请求访问。
spring mvc 4.2 版本增加了对 CORS 的支持,通过 Spring Boot 可以非常简单的实现跨域访问。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
这种方式是全局配置的,但是很多都是基于旧的 Spring 版本,比如 WebMvcConfigurerAdapter 在 Spring5.0 已经被标记为 Deprecated,我们通过看源码就可以知道:
/**
* An implementation of {@link WebMvcConfigurer} with empty methods allowing
* subclasses to override only the methods they're interested in.
*
* @author Rossen Stoyanchev
* @since 3.1
* @deprecated as of 5.0 {@link WebMvcConfigurer} has default methods (made
* possible by a Java 8 baseline) and can be implemented directly without the
* need for this adapter
*/
@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {}
通过实现 Fiter 接口在请求中添加一些 Header 来解决跨域的问题
import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter(filterName = "CorsFilter ")
@Configuration
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}
在 Controller 上使用 @CrossOrigin 注解,该类下的所有接口都可以通过跨域访问。
@RestController
@RequestMapping("/test")
//@CrossOrigin //所有域名均可访问该类下所有接口
@CrossOrigin("https://www.baidu.com") // 只有指定域名可以访问该类下所有接口
public class CorsTestController {
@GetMapping("/test2")
public String sayHello() {
return "hello world";
}
}
这里指定当前的 CorsTestController 中所有的方法可以处理 https://www.baidu.com 域上的请求。
我们点击 CrossOrigin 进去源码看一下:
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
}
从注解 @Target 可以看出,注解可以放在 method、class 等上面,类似 RequestMapping,也就是说,整个Controller 下面的方法可以都受控制,也可以单个方法受控制。
这里不管是通过哪种方式配置 CORS,都是在构造 CorsConfiguration,一个 CORS 配置用一个 CorsConfiguration类来表示,如下所示:
Spring 中对 CORS 规则的校验,通过委托给 DefaultCorsProcessor实现。
处理流程如下:
校验就是根据 CorsConfiguration 这个类的配置进行判断:
源码地址
本文主要介绍了 CORS 的知识,以及如何在 Spring Boot 中配置 CORS。希望对小伙伴的学习有所帮助。