一、什么情况下需要跨域

跨域问题源于浏览器的同源策略,所谓同源(即指在同一个域)就是两个页面具有相同的协议(http/https),主机(host)和端口号(port)。

同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

那么跨域,说的就是:当一个请求url的协议域名端口三者之间任意一个与当前页面url不同即为跨域

工作中我们经常需要获取其他服务的资源信息,这种时候就需要考虑跨域的问题。

二、跨域常见的两种方式

1. JSONP

  • JSONP是“JSON with padding”的简写,我将其翻译为“被包裹的JSON”。JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,兼容性好(兼容低版本IE)

原理
首先,我们应该清楚的认识到,浏览器的“同源策略”只是阻止了通过AJAX技术跨域获取资源,而并没有禁止跨域获取资源这件事本身,正因如此,我们可以通过<link>标签,<img>标签以及<script>标签中的href属性或src属性获取异域的CSS,JS资源和图片(虽然我们其实并不能读取这些资源的内容)。

核心思想:网页通过添加一个<script>元素,向服务器请求 JSON 数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。

JSONP的本质是绕过AJAX获取资源的机制,使用原始的src属性获取异域资源;

  1. 原生实现
<script src="http://test.com/data.php?callback=dosomething"></script>
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
 
// 处理服务器返回回调函数的数据
<script type="text/javascript">
    function dosomething(res){
        // 处理获得的数据
        console.log(res.data)
    }
</script>
  1. jQuery ajax:
$.ajax({
    url: 'http://www.test.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 请求方式为jsonp
    jsonpCallback: "handleCallback",    // 自定义回调函数名
    data: {}
});
  1. Vue.js
this.$http.jsonp('http://www.domain2.com:8080/login', {
    params: {},
    jsonp: 'handleCallback'
}).then((res) => {
    console.log(res); 
})

JSONP的缺点

  • 无法发送POST请求,也就是说JSONP技术只能用于请求异域资源,无法上传数据或修改异域数据;
  • 无法监测JSONP请求是否失败;
  • 可能存在安全隐患:别忘了,JSONP之所以能成功获取异域服务器资源,靠的是服务器动态生成了回调函数,并在页面中执行,那么如果服务器在原有的回调函数下再添加些别的恶意JavaScript代码会怎样?当然也会被执行!所以在使用JSONP技术时,一定要确保请求资源的服务器是值得信赖的;

2. 官方推荐:CORS

  • CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。

Spring Boot 通过 CORS 实现跨域

1、全局配置
可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。

// 请求跨域
@Configuration
public class CorsConfig implements WebMvcConfigurer {
       
    static final String ORIGINS[] = new String[] { "GET", "POST", "PUT", "DELETE" };
 
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 所有的当前站点的请求地址,都支持跨域访问。
                .allowedOrigins("*") // 所有的外部域都可跨域访问。 如果是localhost则很难配置,因为在跨域请求的时候,外部域的解析可能是localhost、127.0.0.1、主机名
                .allowCredentials(true) // 是否支持跨域用户凭证
                .allowedMethods(ORIGINS) // 当前站点支持的跨域请求类型是什么
                .maxAge(3600); // 超时时长设置为1小时。 时间单位是秒。
    }
 
}

2.使用 @CrossOrigin 注解
Controller层在需要跨域的类或者方法上加上该注解即可。

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
@CrossOrigin(origins = "*",maxAge = 3600)
public class UserController {
	final UserService userService;
	
	@GetMapping("/getOne/{id}")
	public User getOne(@PathVariable("id") Integer id) {
		return userService.getById(id);
	}
}

3、自定义跨域过滤器
1).编写过滤器

// 跨域过滤器
@Component
public class CORSFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		//*号表示对所有请求都允许跨域访问
        HttpServletResponse res = (HttpServletResponse) response;
        res.addHeader("Access-Control-Allow-Credentials", "true");
        res.addHeader("Access-Control-Allow-Origin", "*");
        res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
        res.addHeader("Access-Control-Allow-Headers", "Content-Type,X-CAF-Authorization-Token,sessionToken,X-TOKEN");
        if (((HttpServletRequest) request).getMethod().equals("OPTIONS")) {
            response.getWriter().println("Success");
            return;
        }
        chain.doFilter(request, response);
    }
 
    @Override
    public void destroy() {
 
    }
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
 
    }
}

2).注册过滤器

@Configuration
public class CorsConfig {
 
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }
 
}