記一次 CORS 跨域請(qǐng)求出現(xiàn) OPTIONS 請(qǐng)求的問題及解決方法

今天前后端在聯(lián)調(diào)接口的時(shí)候,發(fā)生了跨域請(qǐng)求資源獲取不到的問題。
首先說明下跨域問題的由來。引自HTTP 訪問控制 的一段話:

當(dāng) Web 資源請(qǐng)求由其它域名或端口提供的資源時(shí),會(huì)發(fā)起跨域 HTTP 請(qǐng)求(cross-origin HTTP request)。
比如,站點(diǎn) http://domain-a.com 的某 HTML 頁面通過 <img> 的 src 請(qǐng)求 http://domain-b.com/image.jpg。網(wǎng)絡(luò)上,很多頁面從其他站點(diǎn)加載各類資源(包括 CSS、圖片、JavaScript 腳本)。
出于安全考慮,瀏覽器會(huì)限制腳本中發(fā)起的跨域請(qǐng)求。比如,使用 XMLHttpRequest 和 Fetch 發(fā)起的 HTTP 請(qǐng)求必須遵循同源策略。因此,Web 應(yīng)用通過 XMLHttpRequest 對(duì)象或 Fetch 僅能向同域資源發(fā)起 HTTP 請(qǐng)求。 

既然知道了導(dǎo)致問題的原因,就開始解決吧。
筆者使用的是 Django 框架。github 上面已經(jīng)有人分享了解決辦法,django-cors-headers。我們直接

pip install django-cors-headers 

安裝好了以后,需要我們?nèi)?settings 文件中去配置一下。常見的配置如下:
1) 先在 INSTALLED_APPS 中引入 corsheaders:

INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

2)接著,在 MIDDLEWARE_CLASSES 里面加入 CorsMiddleware 中間件:

MIDDLEWARE_CLASSES = [
    ...
    'corsheaders.middleware.CorsMiddleware',  # cors
    'django.middleware.common.CommonMiddleware',
    ...

3)配置下一些基本參數(shù):

CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True

一些文章還有配置 CORS_ORIGIN_WHITELIST 參數(shù)。

筆者也是看了別人的解決方法,之前也是實(shí)踐過了。配置好這三個(gè)參數(shù)就OK了。本來也以為大工告成了。沒想到,居然沒解決!?。?br> 怎么回事?
通過追蹤請(qǐng)求日志,發(fā)現(xiàn)每次客戶端請(qǐng)求接口的時(shí)候,都會(huì)有一個(gè) OPTIONS 請(qǐng)求。

為什么會(huì)有 OPTIONS 請(qǐng)求?
原來,產(chǎn)生 OPTIOINS 請(qǐng)求的原因是:自定義 Headers 頭信息導(dǎo)致的。為了限制接口的訪問,我在 request 中間件里面加了一層過濾,通過判斷 headers 中是否有約定好的字段及其對(duì)應(yīng)的值(比如:key為 aaa, value為 bbb),如果有,就默認(rèn)可以請(qǐng)求。設(shè)置完自定義 header 字段后,問題就出現(xiàn)了:原來的簡(jiǎn)單請(qǐng)求會(huì)變成預(yù)檢請(qǐng)求。

XHR對(duì)象對(duì)于HTTP跨域請(qǐng)求有三種:簡(jiǎn)單請(qǐng)求、Preflighted 請(qǐng)求、Preflighted 認(rèn)證請(qǐng)求。簡(jiǎn)單請(qǐng)求不需要發(fā)送OPTIONS嗅探請(qǐng)求,但只能按發(fā)送簡(jiǎn)單的GET、HEAD或POST請(qǐng)求,且不能自定義HTTP Headers。Preflighted 請(qǐng)求和認(rèn)證請(qǐng)求,XHR會(huì)首先發(fā)送一個(gè)OPTIONS嗅探請(qǐng)求,然后XHR會(huì)根據(jù)OPTIONS請(qǐng)求返回的Access-Control-*等頭信息判斷是否有對(duì)指定站點(diǎn)的訪問權(quán)限,并最終決定是否發(fā)送實(shí)際請(qǐng)求信息。

瀏覽器會(huì)去向 Server 端發(fā)送一個(gè) OPTIONS 請(qǐng)求,看 Server 返回的 "Access-Control-Allow-Headers" 是否有自定義的 header 字段。因?yàn)槲抑皼]有返回自定義的字段,所以,默認(rèn)是不允許的,造成了客戶端沒辦法拿到數(shù)據(jù)。
既然已經(jīng)知道了原因,且知道了解決思路,就動(dòng)手干吧。通過閱讀 django-cors-headers 的源碼后,發(fā)現(xiàn) **corsheaders/middleware.py ** 里面已經(jīng)有實(shí)現(xiàn)了,那就不再重復(fù)造輪子了。

def process_response(self, request, response):
        """
        Add the respective CORS headers
        """
        origin = request.META.get('HTTP_ORIGIN')
        if not origin:
            return response

        enabled = getattr(request, '_cors_enabled', None)
        if enabled is None:
            enabled = self.is_enabled(request)

        if not enabled:
            return response

        # todo: check hostname from db instead
        url = urlparse(origin)

        if conf.CORS_ALLOW_CREDENTIALS:
            response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = 'true'

        if (
            not conf.CORS_ORIGIN_ALLOW_ALL and
            not self.origin_found_in_white_lists(origin, url) and
            not self.origin_found_in_model(url) and
            not self.check_signal(request)
        ):
            return response

        if conf.CORS_ORIGIN_ALLOW_ALL and not conf.CORS_ALLOW_CREDENTIALS:
            response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
        else:
            response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
            patch_vary_headers(response, ['Origin'])

        if len(conf.CORS_EXPOSE_HEADERS):
            response[ACCESS_CONTROL_EXPOSE_HEADERS] = ', '.join(conf.CORS_EXPOSE_HEADERS)

        if request.method == 'OPTIONS':
            response[ACCESS_CONTROL_ALLOW_HEADERS] = ', '.join(conf.CORS_ALLOW_HEADERS)
            response[ACCESS_CONTROL_ALLOW_METHODS] = ', '.join(conf.CORS_ALLOW_METHODS)
            if conf.CORS_PREFLIGHT_MAX_AGE:
                response[ACCESS_CONTROL_MAX_AGE] = conf.CORS_PREFLIGHT_MAX_AGE

        return response

看完后,發(fā)現(xiàn)只要配置下 CORS_ALLOW_HEADERS 就好。

from corsheaders.defaults import default_headers

CORS_ALLOW_HEADERS = default_headers + (
    'aaa'
)

至此,問題就算解決了。

參考鏈接:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,537評(píng)論 19 139
  • 在項(xiàng)目開發(fā)過程中,遇到了Ajax跨域訪問資源的問題,提示出現(xiàn)了類似于下面這樣的錯(cuò)誤: Cross-Origin R...
    vito1994閱讀 3,909評(píng)論 5 14
  • 1. 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對(duì)JavaScri...
    cbw100閱讀 6,485評(píng)論 2 86
  • 題目1.什么是同源策略? 同源策略(Same origin Policy): 瀏覽器出于安全方面的考慮,只允許與本...
    FLYSASA閱讀 1,885評(píng)論 0 6
  • 最近倍感焦慮,拼了命地想多學(xué)些東西,想多看些書,覺得時(shí)間不夠用,而要做的事情太多。 今天是兒子的五歲生日,他在睡之...
    柳絮紛飛啊閱讀 490評(píng)論 7 10

友情鏈接更多精彩內(nèi)容