Skip to content

Latest commit

 

History

History
206 lines (164 loc) · 7.23 KB

跨域.md

File metadata and controls

206 lines (164 loc) · 7.23 KB

跨域

跨域请求在前后端分离的项目中也算常规操作了

跨域踩坑经验总结(内涵:跨域知识科普) - 凝雨 - Yun (ningyu1.github.io)

跨域请求

Spring Boot实现跨域请求_Macky_He的博客-CSDN博客

CORS 是一个W3C标准,全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。 由于浏览器同源策略(同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。),凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。

Tip

src 属性不受同源策略约束,如img标签,JSONP 就是利用此原理

@CrossOrigin

@Target({ElementType.METHOD, ElementType.TYPE})//可作用于方法,类上 @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CrossOrigin { ... @AliasFor("origins")//允许可访问的域列表 String[] value() default {}; ... long maxAge() default -1L;//准备响应前的缓存持续的最大时间(以秒为单位)。 }

在控制器类或方法上加上@CrossOrigin注解

@CrossOrigin(origins = "*",maxAge = 3600)
@CrossOrigin(origins = "http://localhost:8080",maxAge = 3600)

全局配置

/**
 * @author Macky
 * @Title class WebMvConfig
 * @Description: TODO
 * @date 2019/8/2 13:52
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
                .maxAge(3600)
                .allowCredentials(true);
    }
}

自定义请求头跨域

当时使用拦截器校验自定义请求头时,以上配置依然会报错

浏览器会在发送真正请求之前,先发送一个方法为OPTIONS的预检请求 Preflighted requests 这个请求是用来验证本次请求是否安全的,但是并不是所有请求都会发送,需要符合以下条件:

  • 请求方法不是GET/HEAD/POST
  • POST请求的Content-Type并非application/x-www-form-urlencoded, multipart/form-data, 或text/plain
  • 请求设置了自定义的header字段

对于管理端的接口,我有对接口进行权限校验,每次请求需要在header中携带自定义的字段(token),所以浏览器会多发送一个OPTIONS请求去验证此次请求的安全性。

为何OPTIONS请求是500呢?

OPTIONS请求只会携带自定义的字段,并不会将相应的值带入进去,而后台校验token字段时 token为NULL,所以验证不通过,抛出了一个异常。

Springboot如何优雅的解决ajax+自定义headers的跨域请求 - Java知音号 - 博客园 (cnblogs.com)

解决办法:拦截器放行OPTIONS方法

package com.config;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.common.utils.JwtUserUtils;
import com.common.utils.JwtUtils;

@Component
public class JwtInterceptor implements HandlerInterceptor {
	
	@Autowired
	RedisTemplate<String, String> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {

        // 如果是OPTIONS请求则结束
        if (HttpMethod.OPTIONS.toString().equals(httpServletRequest.getMethod())) {
            return true;
        }
    	
		...
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

    }
}

Note

由于此情形下跨域请求会发送OPTIONS请求,自然整个项目不能禁用OPTIONS请求方式

web.xml

<security-constraint>
 <web-resource-collection>
     <url-pattern>/*</url-pattern>
     <http-method>PUT</http-method>
     <http-method>DELETE</http-method>
     <http-method>HEAD</http-method>
     <http-method>TRACE</http-method>
 </web-resource-collection>
 <auth-constraint/>
</security-constraint>
<security-constraint>
 <web-resource-collection>
     <web-resource-name>securedapp</web-resource-name>
     <url-pattern>/*</url-pattern>
 </web-resource-collection>
 <user-data-constraint>
     <transport-guarantee>CONFIDENTIAL</transport-guarantee>
 </user-data-constraint>
</security-constraint>

application.yml

spring:
mvc:
 dispatch-options-request: true 

客户端配置

有时后端已配置跨域,且在控制台访问测试正常,但是项目中的请求报错

The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.

原因是:请求的 origin 与后端配置的 origin 不一致。

当前端需要带 cookie 请求时,即配置了withCredentials: true

// xhr
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://192.168.3.105/api/notification/list?page=1&limit=2');
// xhr.setRequestHeader("J-Token", "eyJhbGciOiJIUzUxMiJ9.eyJzdW...f4Mm18VPvrztQzTO3yopgdQoKPkh28g");
xhr.withCredentials = true;
xhr.send();

// jquery
$.ajax({
	url : 'url',
	xhrFields: {
	    withCredentials: true
	},
});

// axios
Axios.defaults.withCredentials = true

后端Access-Control-Allow-Credentials必须为true,但是需要注意当Access-Control-Allow-Credentials=true时,Access-Control-Allow-Origin就不能为” * “ ,必须是明确的域名,当然可以多个域名使用 “,” 分割

一般的接口请求或前后端分离项目是不需要带 cookie 请求的,所以一般不配置withCredentials,保持它的默认值false

Access-Control-Allow-Origin - HTTP | MDN (mozilla.org)

跨域踩坑经验总结(内涵:跨域知识科普) - 凝雨 - Yun (ningyu1.github.io)

动态设置?不知有无必要

跨域通配符与include报错,The value of the 'Access-Control-Allow-Origin' header ''_海贼8023的博客-CSDN博客