成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

cors跨域之簡單請求與預(yù)檢請求(發(fā)送請求頭帶令牌token)

zsy888 / 2362人閱讀

摘要:所以跨域請求分兩種簡單請求和預(yù)檢請求。但對于第二個錯誤,好像沒法向第一種那樣,將預(yù)檢請求轉(zhuǎn)變?yōu)楹唵握埱?,所以,只有尋找方法怎么在后端實現(xiàn)相應(yīng)的預(yù)檢請求,來返回一個狀態(tài)碼,告訴瀏覽器此次跨域請求可以繼續(xù)。

引子

自從從JAVA偽全棧轉(zhuǎn)前端以來,學(xué)習(xí)的路上就充滿了荊棘(奇葩問題),而涉及前后端分離這個問題,對cors的應(yīng)用不斷增多,暴露出的問題也接踵而至。
這兩天動手實踐基于Token的WEB后臺認證機制,看過諸多理論(較好一篇推薦),正所謂慮一千次,不如去做一次。 猶豫一萬次,不如實踐一次,所以就有了下文,關(guān)于token的生成,另外一篇文章會細講,本篇主要討論在發(fā)送ajax請求,頭部帶上自定義token驗證驗證,暴露出的跨域問題。

先說說定義

CORS:跨來源資源共享(CORS)是一份瀏覽器技術(shù)的規(guī)范,提供了 Web 服務(wù)從不同網(wǎng)域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,是 JSONP 模式的現(xiàn)代版。與 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。用 CORS 可以讓網(wǎng)頁設(shè)計師用一般的 XMLHttpRequest,這種方式的錯誤處理比JSONP要來的好,JSONP對于 RESTful 的 API 來說,發(fā)送 POST/PUT/DELET 請求將成為問題,不利于接口的統(tǒng)一。但另一方面,JSONP 可以在不支持 CORS 的老舊瀏覽器上運作。不過現(xiàn)代的瀏覽器(IE10以上)基本都支持 CORS。
預(yù)檢請求(option):在 CORS 中,可以使用 OPTIONS 方法發(fā)起一個預(yù)檢請求(一般都是瀏覽檢測到請求跨域時,會自動發(fā)起),以檢測實際請求是否可以被服務(wù)器所接受。預(yù)檢請求報文中的 Access-Control-Request-Method 首部字段告知服務(wù)器實際請求所使用的 HTTP 方法;Access-Control-Request-Headers 首部字段告知服務(wù)器實際請求所攜帶的自定義首部字段。服務(wù)器基于從預(yù)檢請求獲得的信息來判斷,是否接受接下來的實際請求。

OPTIONS /resources/post-here/ HTTP/1.1 
Host: bar.other 
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 
Accept-Language: en-us,en;q=0.5 
Accept-Encoding: gzip,deflate 
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 
Connection: keep-alive 
Origin: http://foo.example 
Access-Control-Request-Method: POST 
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

服務(wù)器所返回的 Access-Control-Allow-Methods 首部字段將所有允許的請求方法告知客戶端。該首部字段與 Allow 類似,但只能用于涉及到 CORS 的場景中。

問題描述

話不多說,先上代碼:

前端(ajax庫:vue-resource)
        userLogin:function(){
            this.$http({
                method:"post",
                url:"http://localhost:8089/StockAnalyse/LoginServlet",
                params:{"flag":"ajaxlogin","loginName":this.userInfo.id,"loginPwd":this.userInfo.psd}, 
                headers: {"Content-Type": "application/x-www-form-urlencoded"}, 
                credientials:false, 
                emulateJSON: true                    
            }).then(function(response){
                sessionStorage.setItem("token",response.data);
                this.isActive =false;
                document.querySelector("#showInfo").classList.toggle("isLogin");
            })                 
        }
后端相關(guān)配置:
        response.setHeader("Access-Control-Allow-Origin", "http://localhost"); //允許來之域名為http://localhost的請求        
    response.setHeader("Access-Control-Allow-Headers", "Origin,No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With, userId, token");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); //請求允許的方法
    response.setHeader("Access-Control-Max-Age", "3600");    //身份認證(預(yù)檢)后,xxS以內(nèi)發(fā)送請求不在需要預(yù)檢,既可以直接跳過預(yù)檢,進行請求(前面只是照貓畫虎,后面才理解)

關(guān)于上面一段代碼,是我的用戶首次登錄認證,生成token令牌,保存在sessionStorage中,供后面調(diào)用;需要說明的是,前端服務(wù)器地址是:localhost:80,后端服務(wù)器地址:localhost:8089,所以前后端涉及到跨域,自己在后端做了相應(yīng)的跨域設(shè)置:response.setHeader("Access-Control-Allow-Origin", "http://localhost"); 所以登錄認證,安全的實現(xiàn)了跨域信息認證,后端相應(yīng)發(fā)送回來了相應(yīng)的token信息。
但獲取到token后,想在需要的時候,在請求的頭部攜帶上這個令牌,來做相應(yīng)的身份認證,所以自己在請求中做了這些改動(有標注),后端沒改動,源碼:

        checkIdentity:function(){
            let token =sessionStorage.getItem("token");
            this.$http({
                method:"post",
                url:"http://localhost:8089/StockAnalyse/LoginServlet",
                params:{"flag":"checklogin","isLogin":true,"token":token}, 
                headers: {"Content-Type": "application/x-www-form-urlencoded"}, 
                headers:{"token":token},        //header中攜帶令牌信息            
                credientials:false, 
                emulateJSON: true                    
            }).then(function(response){
                console.log(response.data);
            })                 
        }   

但實際上在devtools打印了如下錯誤信息:Response to preflight request doesn"t pass access control check: No "Access-Control-Allow-Origin" header is present on the requested resource. Origin "http://localhost" is therefore not allowed access.仔細想一想,好像,似乎這個問題遇到過,還提過問,確實提過,鏈接在這里。但這次的設(shè)置和上次一樣,就在header里多加了一個自定義token,但卻報了和上次沒有設(shè)置headers: {"Content-Type": "application/x-www-form-urlencoded"}一樣的錯誤信息,于是,不知所措,算了,重頭再來,好好百度,研究一下cors跨域。

理論學(xué)習(xí)

運氣不錯,找到了一篇好文,文章講的很細,也找到自己問題的所在:觸發(fā) CORS 預(yù)檢請求。引用原文的話加以自己總結(jié):跨域資源共享標準新增了一組 HTTP 首部字段,允許服務(wù)器聲明哪些源站有權(quán)限訪問哪些資源。另外,規(guī)范要求,對那些可能對服務(wù)器數(shù)據(jù)產(chǎn)生副作用的 HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發(fā)起一個預(yù)檢請求(preflight request:似曾相識有沒有?誒,對,上面那個錯誤信息中,就有一個這樣陌生的詞匯),從而獲知服務(wù)端是否允許該跨域請求。服務(wù)器確認允許之后,才發(fā)起實際的 HTTP 請求。在預(yù)檢請求的返回中,服務(wù)器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關(guān)數(shù)據(jù))。所以跨域請求分兩種:簡單請求和預(yù)檢請求。一次完整的請求不需要服務(wù)端預(yù)檢,直接響應(yīng)的,歸為簡單請求;而響應(yīng)前需要預(yù)檢的,稱為預(yù)檢請求,只有預(yù)檢請求通過,才有接下來的簡單請求。對于那些是簡單請求,那些會觸發(fā)預(yù)檢請求,文章做了詳細的總結(jié),這里列出觸發(fā)預(yù)檢請求的條件(不知道腦子為啥會想到那些會觸發(fā)BFC的條件),不要跑題,原文是這樣總結(jié)的:

當請求滿足下述任一條件時,即應(yīng)首先發(fā)送預(yù)檢請求:
使用了下面任一 HTTP 方法:
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
人為設(shè)置了對 CORS 安全的首部字段集合之外的其他首部字段。該集合為:
Accept
Accept-Language
Content-Language
Content-Type (but note the additional requirements below)
DPR
Downlink
Save-Data
Viewport-Width
Width
 Content-Type 的值不屬于下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
問題分析

所以,再來看自己兩次犯錯(第一次是沒有設(shè)置:headers: {"Content-Type": "application/x-www-form-urlencoded"}, 第二次是設(shè)置自定義header,headers:{"token":token}。很巧,有沒有,一次少,一次多,都點燃了導(dǎo)火索),其實都是觸發(fā)了預(yù)檢請求。對于第一次的錯誤,很好解決,增加headers: {"Content-Type": "application/x-www-form-urlencoded"},就解決了,關(guān)于Conten-Type的幾種取值,你需要知道的。但對于第二個錯誤,好像沒法向第一種那樣,將預(yù)檢請求轉(zhuǎn)變?yōu)楹唵握埱?,所以,只有尋找方法怎么在后端實現(xiàn)相應(yīng)的預(yù)檢請求,來返回一個狀態(tài)碼2xx,告訴瀏覽器此次跨域請求可以繼續(xù)。所以注意力轉(zhuǎn)向后端。
關(guān)于JAVA實現(xiàn)預(yù)檢請求,基本都是采用過濾器,不要問我為什么不是監(jiān)聽器或者攔截器(我就是個偽全棧,就不要相互為難了,自己百度之),自定義(copy)了一個filter,并在web.xml中進行了設(shè)置。源碼:

Filter接口實現(xiàn)部分:
package stock.model;
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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;    
import org.apache.commons.httpclient.HttpStatus;   //這里需要添加commons-httpclient-3.1.jar
public class CorsFilter implements Filter {     //filter 接口的自定義實現(xiàn)
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        response.setHeader("Access-Control-Allow-Origin", "*");           
        String token = request.getHeader("token");
        System.out.println("filter origin:"+token);//通過打印,可以看到一次非簡單請求,會被過濾兩次,即請求兩次,第一次請求確認是否符合跨域要求(預(yù)檢),這一次是不帶headers的自定義信息,第二次請求會攜帶自定義信息。
        if ("OPTIONS".equals(request.getMethod())){//這里通過判斷請求的方法,判斷此次是否是預(yù)檢請求,如果是,立即返回一個204狀態(tài)嗎,標示,允許跨域;預(yù)檢后,正式請求,這個方法參數(shù)就是我們設(shè)置的post了
          response.setStatus(HttpStatus.SC_NO_CONTENT); //HttpStatus.SC_NO_CONTENT = 204
          response.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, OPTIONS, DELETE");//當判定為預(yù)檢請求后,設(shè)定允許請求的方法
          response.setHeader("Access-Control-Allow-Headers", "Content-Type, x-requested-with, Token"); //當判定為預(yù)檢請求后,設(shè)定允許請求的頭部類型
          response.addHeader("Access-Control-Max-Age", "1");  // 預(yù)檢有效保持時間                       
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
    @Override
    public void destroy() {     
    }
}
web.xml配置部分

cors
stock.model.CorsFilter


cors
/*
   
關(guān)于Access-Control-Max-Age

最近又開始寫java,再回來看這個,發(fā)現(xiàn)當時Access-Control-Max-Age設(shè)置了1.其實這樣寫有很大問題,因為每個復(fù)雜請求都會發(fā)兩次。顯然這樣是當代所不能接受的,所以Max-Age的值適合設(shè)的大一些,具體多大很業(yè)務(wù)需求相關(guān)。另外Access-Control-Max-Age不是針對請求域名有效的,是請求的完成路徑有效的,比如第一次發(fā)出www.exanple.com/api/corsGet,會產(chǎn)生一次options請求和一次post請求,然后我再請求一次,這時沒有預(yù)檢請求了,只有post請求。但再發(fā)送一次www.exanple.com/api/corsSave請求,會發(fā)現(xiàn)又產(chǎn)生了一次options請求和一次post請求,所以Access-Control-Max-Age不是針對相同的origin有效,而是針對相同的requestUrl有效。很重要哦。

結(jié)論

當在后端實現(xiàn)添加上面的源碼后,皆大歡喜,問題得以解決,補上失敗和成功,自己截下的兩張請求響應(yīng)圖。仔細看請求響應(yīng)失敗發(fā)起響應(yīng)那張圖,在General的數(shù)據(jù)集中,可以看到方法是options,而非代碼指定的post請求,所以這是一次瀏覽器發(fā)出的一次預(yù)檢請求,讓服務(wù)器確認此IP是否有訪問的權(quán)限,如果有,服務(wù)器需要返回一個2xx的狀態(tài)碼給瀏覽器。緊接著再發(fā)起一次簡單請求。如下面在devtools中的截取圖片(為了對比清除,我把兩次分別截取,做了拼接,因為不會做動態(tài)圖)??梢钥吹酵粋€post請求,實際上產(chǎn)生了兩次網(wǎng)絡(luò)連接。
但關(guān)于cors,要去探索的,還有很多很多,所以遵循革命語錄:實踐(有時也可以是時間)是檢驗真理的唯一標準,是沒有錯的。后續(xù)有新的收獲,再補充。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/83724.html

相關(guān)文章

  • cors域之簡單請求預(yù)檢請求發(fā)送請求頭帶令牌token

    摘要:所以跨域請求分兩種簡單請求和預(yù)檢請求。但對于第二個錯誤,好像沒法向第一種那樣,將預(yù)檢請求轉(zhuǎn)變?yōu)楹唵握埱?,所以,只有尋找方法怎么在后端實現(xiàn)相應(yīng)的預(yù)檢請求,來返回一個狀態(tài)碼,告訴瀏覽器此次跨域請求可以繼續(xù)。 引子 自從從JAVA偽全棧轉(zhuǎn)前端以來,學(xué)習(xí)的路上就充滿了荊棘(奇葩問題),而涉及前后端分離這個問題,對cors的應(yīng)用不斷增多,暴露出的問題也接踵而至。這兩天動手實踐基于Token的WE...

    RaoMeng 評論0 收藏0
  • 前端域之原因&&方案&&原理

    摘要:于是乎同源策略應(yīng)運而生主要限制在于和無法讀取。怎么繞過同源策略首先一般來說協(xié)議和端口造成的跨域問題大部分方法是沒有辦法繞過的。二級域名是寄存在主域名之下的域名。當主域名受到懲罰二級域名也會連帶懲罰。 前言 這是一道前端跨不過躲不掉面試必備的知識,掙扎多年沒能做到刻骨銘心深入脊髓,只能好好寫篇博文記錄起來了; 什么是跨域? 廣義來說,A域執(zhí)行的文檔腳本試圖去請求B域下的資源是不被允許的,...

    Zack 評論0 收藏0
  • CORS原理及@koa/cors源碼解析

    摘要:服務(wù)端根據(jù)這個值,決定是否同意本次請求。預(yù)檢請求預(yù)檢請求用的請求方法是,表示這個請求是用來詢問的。該字段也可以設(shè)為星號,表示同意任意跨源請求。這是為了避免多次預(yù)檢請求。 首發(fā)于個人博客 目錄 跨域 簡單請求和復(fù)雜請求 服務(wù)端如何設(shè)置CORS @koa/cors是怎么實現(xiàn)的 跨域 為什么會有跨域問題? 這是瀏覽器的同源策略所造成的,同源策略限制了從同一個源加載的文檔或腳本如何與來自...

    loostudy 評論0 收藏0
  • 你不知道的CORS跨域資源共享

    摘要:同源策略禁止使用對象向不同源的服務(wù)器地址發(fā)起請求。借助于決解同源策略決解同源策略,新方案跨域資源共享這里講的重點跨域資源共享提供的標準跨域解決方案,是一個由瀏覽器共同遵循的一套控制策略,通過的來進行交互主要通過后端來設(shè)置配置項。 了解下同源策略 源(origin)*:就是協(xié)議、域名和端口號; 同源: 就是源相同,即協(xié)議、域名和端口完全相同; 同源策略:同源策略是瀏覽器的一個安全...

    Gu_Yan 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<