前言
相信剛開始接觸從xml配置文件轉到java類實現配置功能的過程中,些許都會有些疑惑,配置文件到底該怎么寫,我要實現多少接口,new多少對象,完全不知道怎么入手。
今天,在學習了跨域的相關配置后,學到了一點點自定義配置的思路。在此做下記錄,也為學習討論。本文主體分為兩部分,第一部分介紹跨域問題,第二部分介紹如何編寫配置類
聊聊跨域
跨域的本質是瀏覽器針對ajax請求,規定當前頁面下發送的ajax請求的域名應該和當前頁面的域名一致,達到阻止跨站攻擊的目的。通常該跨域問題只針對ajax,而不會針對靜態文件(圖片等)
而以下四種情況會導致跨域問題:
條件 | 示例 |
---|---|
域名不同 | www.a.com與www.b.com |
域名相同端口不同 | www.a.com:80與www.a.com:81 |
二級域名不同 | x.a.com與y.a.com |
協議不同 | http與https |
解決跨域的方法
解決跨域的方法有以下三種
-
Jsonp
-
實現方法:通過
<script>
標簽的src發送請求路徑獲取偽裝成js腳本的json數據 -
缺點:只能發送GET請求,添加額外標簽繁瑣
-
-
nginx反向代理
- 實現方法:修改nginx配置,添加二級域名到server的location路徑下
- 缺點:沒解決本質問題,只是將跨域改為不跨越
-
CORS(推薦)
- 實現方法:服務器端通知瀏覽器哪些域名可以跨域
- 缺點:產生額外請求(預檢請求)
什么是CORS?
CORS全稱Cross-origin resource sharing(跨域資源共享)為W3C的一個標准,它允許瀏覽器向跨源服務器,發出XMLHttpRequest(ajax底層對象)請求,從而克服了AJAX只能同源使用的限制。
瀏覽器處理Ajax請求
瀏覽器處理ajax請求分為簡單請求和特殊請求
簡單請求
只要同時滿足以下兩大條件,就屬於簡單請求。:
(1) 請求方法是以下三種方法之一:
- HEAD
- GET
- POST
(2)HTTP的頭信息不超出以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限於三個值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
簡單請求重點:
當瀏覽器發現發起的ajax請求是簡單請求時,會在請求頭中攜帶一個字段:Origin
簡單請求過程解析:
瀏覽器會在請求頭信息攜帶Origin字段(例:Origin:http://a.baidu.com{協議+域名+端口}),服務器根據該Origin值決定是否允許跨域
響應信息:
Access-Control-Allow-Origin: http://a.baidu.com
Access-Control-Allow-Credentials: true
Content-Type: text/html; charset=utf-8
- Access-Control-Allow-Origin:可接受的域,是一個具體域名或者*(代表任意域名)
- Access-Control-Allow-Credentials:是否允許攜帶cookie,默認情況下,cors不會攜帶cookie,除非這個值是true
注意事項:
如果要使用cookie除了設置Access-Control-Allow-Credentials: true
外,Access-Control-Allow-Origin: http://a.baidu.com
必須為具體的域名!!!
特殊請求
除了簡單請求之外,都為特殊請求
特殊請求重點:
特殊請求會在正式通信之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。該預檢請求會在請求頭攜帶一個OPTIONS
字段
特殊請求過程解析(有關判斷使用三元運算符?:)
發送預檢請求==》服務器檢查該域名是否在許可名單中?答復瀏覽,瀏覽器發送正式XMLHttpRequest
請求:報錯
預檢請求模板:
OPTIONS /cors HTTP/1.1
Origin: http://a.baidu.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.leyou.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
與簡單請求相比,除了Origin以外,多了兩個頭:
- Access-Control-Request-Method:接下來會用到的請求方式,比如PUT
- Access-Control-Request-Headers:會額外用到的頭信息
答復瀏覽器模板:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://a.baidu.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 1728000
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
除了Access-Control-Allow-Origin
和Access-Control-Allow-Credentials
以外,這里又額外多出3個頭:
- Access-Control-Allow-Methods:允許訪問的方式
- Access-Control-Allow-Headers:允許攜帶的頭
- Access-Control-Max-Age:本次許可的有效時長,單位是秒,過期之前的ajax請求就無需再次進行預檢了
通過CorsFilter解決跨域
終於到了激動人心的擼碼環節了,以下開始介紹如何編寫配置類,以解決跨域問題
@Configuration注解
作用於類上,聲明當前類為配置類,相當於xml文件
@Bean
作用於方法上,將當前方法的返回值對象注入spring容器上下文(contex)中
1. 明確需要返回的對象
因為本次為了解決跨域問題,所以需要使用CorsFilter對象。因此直接return new CorsFilter();
2. 查看該對象需要哪些參數
點進CorsFilter發現該構造方法需要一個CorsConfigurationSource對象作為參數
public CorsFilter(CorsConfigurationSource configSource) {
Assert.notNull(configSource, "CorsConfigurationSource must not be null");
this.configSource = configSource;
}
所以需要new出一個CorsConfigurationSource的對象,但是點進CorsConfigurationSource發現該類為一個接口,因此應該找一個實現類進行實例化
3. 尋找接口實現類
點擊該CorsConfigurationSource接口發現有一個實現類(UrlBasedCorsConfigurationSource)實現了該接口
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource
接着通過new該實現類(UrlBasedCorsConfigurationSource),通過閱讀源碼的注釋了解到,該類下的registerCorsConfiguration可以注冊CORS配置信息
/**
* Register a {@link CorsConfiguration} for the specified path pattern.
*/
public void registerCorsConfiguration(String path, CorsConfiguration config) {
this.corsConfigurations.put(path, config);
}
根據該信息發現該方法需要兩個參數,一個是需要攔截的路徑,一個是配置類對象
4. 實例化配置類對象並注入相關信息
如法炮制,通過查看CorsConfiguration源碼,發現該類可以直接new,並且有無參構造方法,所以,直接new對象,並對該對象屬性賦初始值,完成該配置類的構造。
最后,將所有初始化后的對象賦值到方法的參數列表中