1.發(fā)現(xiàn)問題
在開發(fā)基于WEB應(yīng)用的時(shí)候爽篷,一般情況下前端負(fù)責(zé)界面展示,后端負(fù)責(zé)業(yè)務(wù)邏輯慢睡,尤其是當(dāng)前分布式逐工、微服務(wù)铡溪、前后端分離架構(gòu)的廣泛使用,前端調(diào)用后端獲取數(shù)據(jù)泪喊,難免會(huì)出現(xiàn)數(shù)據(jù)訪問跨域現(xiàn)象:
當(dāng)瀏覽器console出現(xiàn)下面的提示棕硫,說明出現(xiàn)了跨域問題。
Access to XMLHttpRequest at 'http://www.float.com/goods/findPage?page=1&size=5' from origin 'www.integer.net.cn' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
關(guān)鍵詞:
origin:域袒啼、源
CORS:全稱是"跨域資源共享"(Cross-Origin Resource Sharing)
blocked:阻止哈扮,阻塞
Access-Control-Allow-Origin:HTTP協(xié)議中與跨域有關(guān)的請求頭信息。
CORS policy:CORS策略
接下來有個(gè)問題蚓再,域是什么鬼滑肉?
2.什么是域
當(dāng)一個(gè)請求url的協(xié)議、域名摘仅、端口三者之間任意一個(gè)與當(dāng)前頁面url不同即為不同的域
3.為什么會(huì)出現(xiàn)跨域問題
出于安全考慮靶庙,瀏覽器使用同源策略(SameOriginPolicy)進(jìn)行訪問的限制。
所謂同源(或同一個(gè)域)就是兩個(gè)請求具有相同的協(xié)議(protocol)娃属,主機(jī)(host)和端口號(port)六荒,這三者均相同則表示這兩個(gè)請求同源。
同源策略防止當(dāng)前域的javascript腳本和另外一個(gè)域的內(nèi)容進(jìn)行交互矾端,這就是為什么出現(xiàn)跨域訪問被阻止的原因掏击。
同源策略是Web應(yīng)用程序安全模型中的一個(gè)重要概念,它由 Netscape 公司在1995年引入瀏覽器。該策略最初是為保護(hù)DOM的訪問而設(shè)計(jì)的秩铆,Web瀏覽器允許第一個(gè)Web頁面中包含的腳本訪問第二個(gè)Web頁面中的數(shù)據(jù)砚亭,但前提是兩個(gè)Web頁面具有相同的源(origin)。后來進(jìn)行了擴(kuò)展殴玛,此策略可防止一個(gè)頁面上的惡意腳本(ajax請求)訪問另一個(gè)網(wǎng)頁上的敏感數(shù)據(jù)钠惩。
惡意腳本訪問敏感數(shù)據(jù)
目前Web應(yīng)用程序廣泛依賴HTTP cookie來維護(hù)經(jīng)過身份驗(yàn)證的用戶會(huì)話,服務(wù)器基于cookie信息來顯示或者獲取敏感信息族阅。客戶端必須保證無關(guān)站點(diǎn)與當(dāng)前站點(diǎn)嚴(yán)格分離膝捞,以防止數(shù)據(jù)機(jī)密性或完整性丟失坦刀。
同源策略主要防止一個(gè)域的javascript腳本和另外一個(gè)域的內(nèi)容進(jìn)行交互。
主要限制包括:
不同源的頁面之間Cookie蔬咬、LocalStorage 和 IndexDB無法共享
不同源AJAX 請求不能發(fā)送
通過相應(yīng)的HTML標(biāo)記嵌入的資源不受限制鲤遥,如圖像,CSS和腳本
圖像: <img src="http://abc.com/1.png"/>
CSS: <link rel="stylesheet" type="text/css" />
腳本: <script type="text/javascript" src="http://abc.com/a.js"></script>
(1)當(dāng)訪問http://www.integer.net.cn的時(shí)候林艘,瀏覽器發(fā)送GET 請求獲取主頁盖奈,這時(shí)候當(dāng)前頁面的域指定為http://www.integer.net.cn
(2)通過<img src="http://www.integer.net.cn/1.png">訪問1.png圖片,沒有跨域狐援,允許訪問钢坦,通過<img src="http://www.double.net.cn/2.png">訪問2.png圖片究孕,雖然跨域,但訪問的是HTML嵌入資源爹凹,不受同源策略影響厨诸,依然可以訪問。
(3)通過<link />訪問a.css禾酱,沒有跨域微酬,允許訪問,通過< link >訪問b.css颤陶,雖然跨域颗管,但訪問的是HTML嵌入資源,不受同源策略影響滓走。
(4)使用ajax異步請求http://www.integer.net.cn域下的數(shù)據(jù)垦江,沒有跨域,允許訪問
(5)使用ajax異步請求http://www.float.com域下的數(shù)據(jù)闲坎,此時(shí)跨域疫粥,同源策略阻止繼續(xù)訪問,是否允許訪問由CORS策略控制腰懂。
從以上分析可以看出梗逮,只有當(dāng)ajax異步請求跨域時(shí),同源策略才會(huì)起作用绣溜,跨域請求就一定不能訪問呢慷彤?當(dāng)然不是,上面已經(jīng)提及怖喻,是否能訪問跨域資源由CORS策略決定的底哗,那么什么是CORS策略呢?
4.CORS跨域解決方案(服務(wù)端)
CORS策略锚沸,它允許瀏覽器向跨域服務(wù)器發(fā)出XMLHttpRequest(AJAX)請求跋选,從而克服了AJAX只能同源訪問的限制。
整個(gè)CORS通信過程哗蜈,前端都是瀏覽器自動(dòng)完成前标,不需要用戶參與。對于開發(fā)者來說距潘,CORS通信與同源的AJAX通信沒有差別炼列,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請求跨域音比,就會(huì)自動(dòng)添加一些附加的頭信息(針對簡單請求)俭尖,有時(shí)還會(huì)多出一次附加的請求(針對復(fù)雜請求),但用戶不會(huì)有感覺。因此稽犁,實(shí)現(xiàn)CORS通信的關(guān)鍵是服務(wù)器焰望。只要服務(wù)器實(shí)現(xiàn)了CORS接口,就可以跨域通信缭付。
上面這段話柿估,翻譯成大白話就是瀏覽器發(fā)現(xiàn)AJAX請求跨域,添加附加信息(簡單請求)或者附加請求(復(fù)雜請求)發(fā)送到服務(wù)器陷猫,如果服務(wù)器端允許當(dāng)前訪問秫舌,則繼續(xù)訪問返回響應(yīng)結(jié)果,如果服務(wù)器不允許該來源訪問绣檬,則會(huì)出現(xiàn)跨域訪問現(xiàn)象足陨。所以,主動(dòng)權(quán)掌握在服務(wù)器手里娇未。這就是CORS策略墨缘。
就像你想要約隔壁班的女生看電影,人家和你不是一個(gè)班根本不認(rèn)識你零抬,去不去由人家決定镊讼。當(dāng)然,附加的信息就是你表明自己身份去邀請人家平夜,這里還有一個(gè)問題蝶棋,如果只是看電影這種簡單請求,你拿著票表達(dá)誠意就行忽妒。但是對于其他復(fù)雜請求玩裙,問一次肯定不行,先要進(jìn)行一次預(yù)請求段直,試探一下吃溅,試探成功后再進(jìn)行復(fù)雜請求。
那么哪些請求是簡單請求鸯檬,哪些是復(fù)雜請求呢决侈?
簡單請求需要滿足以下幾點(diǎn):
請求是GET、POST喧务、HEAD三種
HTTP的頭信息不超出以下幾種字段 Accept颜及、Accept-Language、Content-Language蹂楣、Content-Type
Content-Type 的值僅限于下列三者之一:text/plain、multipart/form-data讯蒲、application/x-www-form-urlencoded
什么是復(fù)雜請求呢痊土,除了簡單請求都是復(fù)雜請求。
比如說你需要發(fā)送PUT墨林、DELETE等HTTP請求赁酝,或者發(fā)送Content-Type: application/json的內(nèi)容犯祠,這些就都是復(fù)雜請求。
那么簡單請求中附加的信息是什么呢酌呆?
跨域請求HTTP頭當(dāng)中要求包含一個(gè)域(Origin)的信息衡载。該域包含協(xié)議名、地址以及一個(gè)可選的端口隙袁。不過這一項(xiàng)實(shí)際上由瀏覽器代為發(fā)送痰娱,開發(fā)者不需要操作,這就是簡單請求附加的信息菩收。
服務(wù)器端對簡單請求進(jìn)行響應(yīng)梨睁,部分響應(yīng)頭及解釋如下:
Access-Control-Allow-Origin(必含) - 不可省略,否則請求按失敗處理娜饵。該項(xiàng)控制數(shù)據(jù)的可見范圍坡贺,如果希望數(shù)據(jù)對任何人都可見,可以填寫"*"箱舞。
Access-Control-Allow-Credentials(可選) - 該項(xiàng)標(biāo)志著請求當(dāng)中是否包含cookies信息遍坟,只有一個(gè)可選值:true(必為小寫)。如果不包含cookies晴股,請略去該項(xiàng)愿伴,而不是填寫false。這一項(xiàng)與XmlHttpRequest對象(ajax請求)當(dāng)中的withCredentials屬性應(yīng)保持一致队魏,即withCredentials為true時(shí)該項(xiàng)也為true公般;withCredentials為false時(shí),省略該項(xiàng)不寫胡桨。反之則導(dǎo)致請求失敗官帘。
簡單請求
任何不滿足上述簡單請求要求的請求,都屬于復(fù)雜請求昧谊。
復(fù)雜請求會(huì)在正式通信之前刽虹,增加一次HTTP查詢請求,稱為"預(yù)檢"請求(preflight)呢诬。預(yù)檢請求為OPTIONS請求涌哲,用于向服務(wù)器請求權(quán)限信息的。預(yù)檢請求被成功響應(yīng)后尚镰,才會(huì)發(fā)出真實(shí)請求阀圾,攜帶真實(shí)數(shù)據(jù)。
復(fù)雜請求
預(yù)請求以O(shè)PTIONS形式發(fā)送狗唉,當(dāng)中同樣包含域初烘,并且還包含了兩項(xiàng)CORS特有的內(nèi)容:
Access-Control-Request-Method – 該項(xiàng)內(nèi)容是實(shí)際請求的種類,可以是GET、POST之類的簡單請求肾筐,也可以是PUT哆料、DELETE等等。
Access-Control-Request-Headers – 該項(xiàng)是一個(gè)以逗號分隔的列表吗铐,當(dāng)中是復(fù)雜請求所使用的頭部东亦。
顯而易見,這個(gè)預(yù)請求實(shí)際上就是在為之后的實(shí)際請求發(fā)送一個(gè)權(quán)限請求唬渗,在預(yù)回應(yīng)返回的內(nèi)容當(dāng)中典阵,服務(wù)端應(yīng)當(dāng)對這兩項(xiàng)進(jìn)行回復(fù),以讓瀏覽器確定請求是否能夠成功完成谣妻。
復(fù)雜請求的部分響應(yīng)頭及解釋如下:
Access-Control-Allow-Origin(必含) – 和簡單請求一樣的萄喳,必須包含一個(gè)域。
Access-Control-Allow-Methods(必含) – 這是對預(yù)請求當(dāng)中Access-Control-Request-Method的回復(fù)蹋半,這一回復(fù)將是一個(gè)以逗號分隔的列表他巨。盡管客戶端或許只請求某一方法,但服務(wù)端仍然可以返回所有允許的方法减江,以便客戶端將其緩存染突。
Access-Control-Allow-Headers(當(dāng)預(yù)請求中包含Access-Control-Request-Headers時(shí)必須包含) – 這是對預(yù)請求當(dāng)中Access-Control-Request-Headers的回復(fù),和上面一樣是以逗號分隔的列表辈灼,可以返回所有支持的頭部份企。這里在實(shí)際使用中有遇到,所有支持的頭部一時(shí)可能不能完全寫出來巡莹,而又不想在這一層做過多的判斷司志,沒關(guān)系,事實(shí)上通過request的header可以直接取到Access-Control-Request-Headers降宅,直接把對應(yīng)的value設(shè)置到Access-Control-Allow-Headers即可骂远。
Access-Control-Allow-Credentials(可選) – 和簡單請求當(dāng)中作用相同。
Access-Control-Max-Age(可選) – 以秒為單位的緩存時(shí)間腰根。預(yù)請求的的發(fā)送并非免費(fèi)午餐激才,允許時(shí)應(yīng)當(dāng)盡可能緩存。
一旦預(yù)回應(yīng)如期而至额嘿,所請求的權(quán)限也都已滿足瘸恼,則實(shí)際請求開始發(fā)送。
5.Spring CORS –@CrossOrigin注解
Spring MVC 提供@CrossOrigin 注解. 該注解可以標(biāo)注在類或者方法上
5.1. Spring CORS 配置
@CrossOrigin?默認(rèn)將允許所有的源册养,所有頭信息东帅,@RequestMapping標(biāo)注的方法進(jìn)行跨域訪問,maxAge 為30分鐘球拦。
CrossOrigin 配置參數(shù)
5.2. @CrossOrigin 注解標(biāo)注在Controller類
@CrossOrigin(origins ="*", allowedHeaders ="*")
@Controller
publicclassHomeController{
????@GetMapping(path="/")
????public String homeInit(Model model) {
????????return"home";
????}
}
表明該類的所有方法靠闭,均實(shí)現(xiàn)CORS策略邓夕。
5.3. @CrossOrigin 注解標(biāo)注在方法
@Controller
publicclassHomeController{
????@CrossOrigin(origins ="*", allowedHeaders ="*")
????@GetMapping(path="/")
????public String homeInit(Model model) {
????????return"home";
????}
}
表明該方法實(shí)現(xiàn)CORS策略
5.4. @CrossOrigin 在方法級覆蓋類級注解
@Controller
@CrossOrigin(origins ="*", allowedHeaders ="*")
public class HomeController{
????@CrossOrigin(origins ="http://example.com")
????@GetMapping(path="/")
????publicString homeInit(Model model) {
????????return"home";
????}
}
homeInit() 方法只能被域 http://example.com訪問,HomeController 的其他方法可以被所有的域
6.Spring CORS – 全局配置
6.1. 實(shí)現(xiàn)WebMvcConfigurer
為了讓整個(gè)應(yīng)該所有的請求均實(shí)現(xiàn)CORS跨域方案, 可以使用WebMvcConfigurer 并添加到 CorsRegistry中阎毅。
import org.springframework.context.annotation.Configuration;
import rg.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
publicclass CorsConfiguration implements WebMvcConfigurer{
@Override
public void addCorsMappings(CorsRegistry registry) {
????????????registry.addMapping("/**").allowedMethods("GET", "POST");
? ? }
}
6.2. WebMvcConfigurer Bean
spring boot應(yīng)用中, 推薦聲明一個(gè) WebMvcConfigurer bean.
@Configuration
publicclassCorsConfiguration{
@Bean
publicWebMvcConfigurercorsConfigurer() {
????????return new WebMvcConfigurer() {
????????@Override
????????public void addCorsMappings(CorsRegistry registry){
? ?????????????registry.addMapping("/**").allowedOrigins("http://localhost:8080");
????}? ? ? ?
?};? ??
}
}
6.3 CORS 與Spring Security
為了能夠使CORS 支持 Spring security, 配置CorsConfigurationSource bean 并且使用 HttpSecurity.cors() 進(jìn)行設(shè)置。
@EnableWebSecurity
publicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http)throwsException{
????????http.cors().and()//other config
? ? }
? ? @Bean
? ? CorsConfigurationSource corsConfigurationSource() {
? ? ? ? CorsConfiguration configuration = new CorsConfiguration();
? ? ? ? configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
? ? ? ? configuration.setAllowedMethods(Arrays.asList("GET","POST"));
? ? ? ? UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
? ? ? ? source.registerCorsConfiguration("/**", configuration);
? ? ? ? return source;
? ? }
}
7.Java CORS過濾器
如果應(yīng)用中沒有集成Spring或者SpringBoot框架点弯,可以直接使用過濾器來進(jìn)行配置扇调,基本原理就是對Http 頭信息就行設(shè)置。
7.1Response Headers
·?Access-Control-Allow-Origin?: 指示允許哪些域進(jìn)行跨域訪問. “*” 代表沒有限制.
·?Access-Control-Allow-Credentials?: 指示跨域訪問是否支持用戶身份抢肛,即cookie.
·?Access-Control-Expose-Headers?: 指示哪些頭信息暴露出來.
·?Access-Control-Max-Age?: 指示預(yù)請求響應(yīng)在客戶端緩存的時(shí)間.
·?Access-Control-Allow-Methods?: 指示允許哪些方法可以訪問資源
·?Access-Control-Allow-Headers?:指示在實(shí)際請求中哪些頭信息被允許.
7.2. Request Headers
·?Origin?: 指示跨域請求或者預(yù)請求的來源.
·?Access-Control-Request-Method?: 在預(yù)請求中使用狼钮,告訴服務(wù)器實(shí)際請求講使用該方法
·?Access-Control-Request-Headers?:在預(yù)請求中使用,告訴服務(wù)器實(shí)際請求中將帶的頭信息.
7.3. Java CORS 過濾器demo
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet Filter implementation class CORSFilter
*/
// Enable it for Servlet 3.x implementations
/* @ WebFilter(asyncSupported = true, urlPatterns = { "/*" }) */
public class CORSFilter implements Filter {
? ? /**
? ? * Default constructor.
? ? */
? ? public CORSFilter() {
? ? ? ? // TODO Auto-generated constructor stub
? ? }
? ? /**
? ? * @see Filter#destroy()
? ? */
? ? public void destroy() {
? ? ? ? // TODO Auto-generated method stub
? ? }
? ? /**
? ? * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
? ? */
? ? public void doFilter(ServletRequest servletRequest,ServletResponse servletResponse, FilterChain chain)
? ? ? ? ? ? throws IOException, ServletException {
? ? ? ? HttpServletRequest request = (HttpServletRequest) servletRequest;
? ? ? ? System.out.println("CORSFilter HTTP Request: " + request.getMethod());
? ? ? ? // Authorize (allow) all domains to consume the content
? ? ? ? ((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Origin", "*");
? ? ? ? ((HttpServletResponse) servletResponse)
????????????????????.addHeader("Access-Control-Allow-Methods","GET, OPTIONS, HEAD, PUT, POST");
? ? ? ? HttpServletResponse resp = (HttpServletResponse) servletResponse;
? ? ? ? // For HTTP OPTIONS verb/method reply with ACCEPTED status code -- per CORS handshake
? ? ? ? if (request.getMethod().equals("OPTIONS")) {
? ? ? ? ? ? resp.setStatus(HttpServletResponse.SC_ACCEPTED);
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? // pass the request along the filter chain
? ? ? ? chain.doFilter(request, servletResponse);
? ? }
? ? /**
? ? * @see Filter#init(FilterConfig)
? ? */
? ? public void init(FilterConfig fConfig) throws ServletException {
? ? ? ? // TODO Auto-generated method stub
? ? }
}
web.xml配置
<filter>
????<filter-name>CorsFilter</filter-name>
????<filter-class>com.howtodoinjava.examples.cors.CORSFilter</filter-class>
</filter>
<filter-mapping>
????<filter-name>CorsFilter</filter-name>
????<url-pattern>/*</url-pattern>
</filter-mapping>