Java请求跨域处理
Access to XMLHttpRequest at ‘http://localhost:88/api/sys/login’ from origin ‘http://localhost:8001’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
跨域:浏览器对JavaScript施加的安全限制,默认不让js获取远程网站的数据
XMLHttpRequest对象发送AJAX请求
同源策略:指协议、域名、端口都要相同,其中有一个不同都会产生跨域
限制:
- 域名及其对应IP,发送跨域
- 主域相同,子域不同,跨域
- 同一域名,不同二级域名(有无www),跨域
跨域流程:
登录不属于简单请求,由于登录的content-type属于application/json所以数据非简单请求,此时先发送一个options 预检请求
服务器可对origin请求头做校验,仅允许部分请求源跨域访问数据
解决跨域:
- 使用NGINX 部署为同一域 NGINX拦在gateway前方
前后端请求都走nginx 通过动静分离 代理到不同实际地址,但请求的origin(请求域:协议+域名+端口)就一致了都是nginx的地址
- 跨域流程处理
1、添加响应头
• Access-Control-Allow-Origin:支持哪些来源的请求跨域
• Access-Control-Allow-Methods:支持哪些方法跨域
• Access-Control-Allow-Credentials:跨域请求默认不包含cookie,设置为true可以包含
cookie
• Access-Control-Expose-Headers:跨域请求暴露的字段
• CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如
果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
• Access-Control-Max-Age:表明该响应的有效时间为多少秒。在有效时间内,浏览器无
须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果
该首部字段的值超过了最大有效时间,将不会生效。
-
在网关中添加跨域filter
springboot 自带CorsWebFilter 来解决跨域问题,通过spring boot自带的跨域解决方式来处理,若不允许跨域返回403,若允许返回允许响应头信息
package priv.yuzuki.gulimall.gateway.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; /** * CorsConfiguration * * @author yuzuki * @date 2021/3/28 17:33 * @since 1.0.0 */ @Configuration public class MyCorsConfiguration { @Bean public CorsWebFilter corsWebFilter(){ // CorsConfigurationSource corsConfigurationSource = new CorsConfigurationSource() { // @Override // public org.springframework.web.cors.CorsConfiguration getCorsConfiguration(javax.servlet.http.HttpServletRequest request) { // return null; // } // }; // 注意使用 reactive包下的类,因为spring cloud 都是响应式编程所以要都使用reactive包下的类 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration = new CorsConfiguration(); // 配置跨域 corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); // 测试效果,如果只允许GET请求,看POST请求会被如何拒绝 // corsConfiguration.addAllowedMethod(HttpMethod.GET); corsConfiguration.addAllowedOrigin("*"); // 是否允许携带cookie跨域 corsConfiguration.setAllowCredentials(true); source.registerCorsConfiguration("/**",corsConfiguration); return new CorsWebFilter(source); } }
-
出错
:8001/#/login:1 Access to XMLHttpRequest at ‘http://localhost:88/api/sys/login’ from origin ‘http://localhost:8001’ has been blocked by CORS policy: The ‘Access-Control-Allow-Origin’ header contains multiple values ‘http://localhost:8001, http://localhost:8001’, but only one is allowed.
若多出配置跨域会返回多个相同响应头导致出错
spring boot 响应式编程跨域请求源码分析
核心是org.springframework.web.cors{.reactive}.CorsWebFilter
用户只需给出自定义的跨域配置,由spring boot自动扫描CorsWebFilter注入IOC容器并产生作用
跨域过滤的核心方法为CorsWebFilter#Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain)
@Override
public boolean process(@Nullable CorsConfiguration config, ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().addAll(HttpHeaders.VARY, VARY_HEADERS);
// 根据请求头中是否携带origin请求头或origin代表的资源域与请求域是否同源(协议+域名+端口不一致)判断是否跨域,跨域就往下判断是否允许,否则请求不规范,未携带origin请求头或origin的内容与请求源地址不同
if (!CorsUtils.isCorsRequest(request)) {
return true;
}
if (response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) != null) {
logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
return true;
}
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
if (config == null) {
if (preFlightRequest) {
rejectRequest(response);
return false;
}
else {
return true;
}
}
return handleInternal(exchange, config, preFlightRequest);
}
最终处理方法:
protected boolean handleInternal(ServerWebExchange exchange,
CorsConfiguration config, boolean preFlightRequest) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
HttpHeaders responseHeaders = response.getHeaders();
String requestOrigin = request.getHeaders().getOrigin();
// 检测请求origin是否在允许范围内(由之前通过@Configuration+@Bean给出的自定义配置判断
String allowOrigin = checkOrigin(config, requestOrigin);
if (allowOrigin == null) {
logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
rejectRequest(response);
return false;
}
HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
// 检测请求方法是否在指定范围内
List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
if (allowMethods == null) {
logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
rejectRequest(response);
return false;
}
List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
// 检测请求头是否在指定范围内
List<String> allowHeaders = checkHeaders(config, requestHeaders);
if (preFlightRequest && allowHeaders == null) {
logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
rejectRequest(response);
return false;
}
// 设置允许请求origin的响应头
responseHeaders.setAccessControlAllowOrigin(allowOrigin);
// 预检请求才设置允许请求方法的响应头
if (preFlightRequest) {
responseHeaders.setAccessControlAllowMethods(allowMethods);
}
// 预检请求且允许请求头非空才设置允许请求头的响应头
if (preFlightRequest && !allowHeaders.isEmpty()) {
responseHeaders.setAccessControlAllowHeaders(allowHeaders);
}
if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
}
// 判断是否允许携带cookie
if (Boolean.TRUE.equals(config.getAllowCredentials())) {
responseHeaders.setAccessControlAllowCredentials(true);
}
// 判断是否设置允许响应时间
if (preFlightRequest && config.getMaxAge() != null) {
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
}
return true;
}
小结
origin请求头是浏览器自带的,无需用户干预,如果不带,是否表明没有发生跨域
不带/带了但是请求同源都不会发生跨域处理
同源策略:
如果两个 URL 的 protocol、port (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。(“元组” 是指一组项目构成的整体,双重/三重/四重/五重/等的通用形式)。
如何避免引导正常用户引发跨站请求伪造?(CSRF,Cross-site request forgery)(通过使用网站返回的cookie以非本意的方式发送请求,通过恶意网站B,引诱用户在B网站做任何操作从而B网站获取用户cookie并给给已登录的A网站发送下单、转账等请求)通过浏览器自带的origin同源策略可在一定程度上防止该操作
如果非正常用户去更改origin来避免浏览器的同源机制,除非获取正常用户的电脑,否则无法完成操作
浏览器通过同源策略在一定程度上阻止恶意请求(CSRF),但业务上产生跨域资源共享(CORS)场景则通过跨域策略来允许访问非同源请求获取资源