目录
SpringSecurity配置对OPTIONS请求并没有放开登录认证
浏览器的同源策略
浏览器出于网站安全性的考虑,限制不同源之间的资源相互访问的一种政策。只有当协议+域名+端口这三个部分完全相同的才认为是同源。
同源策略的限制
值得一提的是,有些请求是不受到跨域限制。例如:WebSocket,script、img、iframe、video、audio标签的
src
属性等。
-
AJAX 请求不能发送。
-
无法获取DOM元素并进行操作。
-
无法读取Cookie、LocalStorage 和 IndexDB
什么是跨域问题
当一个资源去访问另一个不同域名或者同域名不同端口的资源时,就会发出跨域请求。如果此时另一个资源不允许其进行跨域资源访问,那么访问的那个资源就会遇到跨域问题。或者是两个资源的传输协议不一致(如一个是http,一个是https)时,也会出现跨域问题。
跨域请求解决方案
代理模式(proxy)
-
将请求转发给后端进行处理(有点像重定向)
const express = require('express');
const router = express.Router();
const request = require('request');
router.get('*', (req, res, next) => {
let path = req.path.replace(/^\/proxy/, '');
request.get(`http://127.0.0.1:3000${path}`, (err, response) => {
res.json(JSON.parse(response.body));
});
});
module.exports = router;
这样,我们在前端访问/proxy/info/normal
后,就会自动转发到http://127.0.0.1:3000/proxy/info/normal
。
前端ajax部分如下:
document.getElementById('btn-1').addEventListener('click', function() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
alert(xhr.responseText);
}
}
xhr.open('get', '/proxy/info/normal');
xhr.send(null);
});
该方法的优点很明显:不需要第三方服务http://127.0.0.1:3000/info/normal
进行任何改造。
当然,该方法也有一些缺点:
-
首先,需要你有一个自己的后端服务能够接收并转发请求。如果你进行本地的纯静态页面开发,则需要一些浏览器插件或自动化工具中集成的本地服务器来实现。
-
此外,如果请求包含一些特殊的请求头(例如cookie等等),需要在转发时特殊处理。
CORS(本项目采用的方案)
同源策略往往过于严格了,为了解决浏览器的这个问题,w3c提出了CORS(Cross-Origin Resource Sharing)标准。CORS通过相应的请求头与响应头来实现跨域资源访问。
如果我们打开控制台,可以在请求头中发现一个叫origin
的头信息,它表明了请求的来源。这是浏览器自动添加的。
Referer: http://127.0.0.1:8085/
Origin: http://127.0.0.1:8085 <============ origin
Accept: */*
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8
Pragma: no-cache
与之对应的,服务器端的响应头中一个头信息为Access-Control-Allow-Origin
,表明接受的跨域请求来源。显而易见,这两个信息如果一致,则这个请求就会被接受。
router.get('/cors', (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8085');
res.json(data);
});
如果将Access-Control-Allow-Origin
的值设置为*
,则会接受所有域的请求。这时的客户端不需要任何配置即可进行跨域访问。
然而,还有一个问题,CORS默认是不会发送cookie,但是如果我希望这次的请求也能够带上对方服务所需的cookie怎么办?那就需要再进行一定的改造。
与Access-Control-Allow-Origin
相配套的,还有一个叫Access-Control-Allow-Credentials
的响应头,如果设置为true
则表明服务器允许该请求内包含cookie信息
router.get('/cors', (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8085');
res.setHeader('Access-Control-Allow-Credentials', true);
res.json(data);
});
以上前端参数的设置,我们可以在后端设置一个跨域的过滤器配置来实现,配置文件如下
添加GlobalCorsConfig配置文件来允许跨域访问。
package com.macro.mall.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 全局跨域配置
* Created by macro on 2019/7/27.
*/
@Configuration
public class GlobalCorsConfig {
/**
* 允许跨域调用的过滤器
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//允许所有域名进行跨域调用
config.addAllowedOrigin("*");
//允许跨越发送cookie
config.setAllowCredentials(true);
//放行全部原始头信息
config.addAllowedHeader("*");
//允许所有请求方法跨域调用
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
缺点是:
-
需要对跨域的服务接口进行一定的改造。如果该服务因为某些原因无法改造,则无法实现。但这种改造还是相对较小的。
-
不兼容一些“古董”浏览器。
复杂跨域请求引起的预检问题
参考文章ajax跨域简单请求与复杂请求
SpringSecurity配置对OPTIONS请求并没有放开登录认证
由于复杂的跨越请求需要先进行一次OPTIONS请求进行预检,我们的应用整合了SpringSecurity,对OPTIONS请求并没有放开登录认证。因此我们要修改Security的配置文件,允许Security放开options请求
在SecurityConfig类的configure(HttpSecurity httpSecurity)方法中添加如下代码。
.antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求
.permitAll()