cors跨域之简朴要求与预检要求(发送要求头带令牌token)

引子

自从从JAVA伪全栈转前端以来,进修的路上就充满了波折(奇葩题目),而触及前后端星散这个题目,对cors的运用不停增添,暴露出的题目也接二连三。
这两天着手实践基于Token的WEB背景认证机制,看过诸多理论(较好一篇引荐),正所谓虑一千次,不如去做一次。 犹疑一万次,不如实践一次,所以就有了下文,关于token的天生,别的一篇文章会细讲,本篇主要议论在发送ajax请求,头部带上自定义token考证考证,暴露出的跨域题目。

先说说定义

CORS:跨泉源资本共享(CORS)是一份阅读器手艺的范例,供应了 Web 效劳从差别网域传来沙盒剧本的要领,以避开阅读器的同源战略,是 JSONP 形式的现代版。与 JSONP 差别,CORS 除了 GET 请求要领以外也支撑其他的 HTTP 请求。用 CORS 能够让网页设计师用平常的 XMLHttpRequest,这类体式格局的毛病处理比JSONP要来的好,JSONP关于 RESTful 的 API 来讲,发送 POST/PUT/DELET 请求将成为题目,不利于接口的一致。但另一方面,JSONP 能够在不支撑 CORS 的老旧阅读器上运作。不过现代的阅读器(IE10以上)基础都支撑 CORS。
预检请求(option):在 CORS 中,能够运用 OPTIONS 要领提议一个预检请求(平常都是阅读检测到请求跨域时,会自动提议),以检测现实请求是不是能够被效劳器所接收。预检请求报文中的 Access-Control-Request-Method 首部字段示知效劳器现实请求所运用的 HTTP 要领;Access-Control-Request-Headers 首部字段示知效劳器现实请求所照顾的自定义首部字段。效劳器基于从预检请求取得的信息来推断,是不是接收接下来的现实请求。

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

效劳器所返回的 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");
            })                 
        }
后端相干设置:
        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");    //身份认证(预检)后,xxS之内发送请求不在须要预检,既能够直接跳过预检,举行请求(前面只是照猫画虎,背面才明白)

关于上面一段代码,是我的用户初次登录认证,天生token令牌,保存在sessionStorage中,供背面挪用;须要申明的是,前端效劳器地点是:localhost:80,后端效劳器地点:localhost:8089,所以前后端触及到跨域,本身在后端做了相应的跨域设置:response.setHeader(“Access-Control-Allow-Origin”, “http://localhost”); 所以登录认证,平安的完成了跨域信息认证,后端相应发送回来了相应的token信息。
但获取到token后,想在须要的时刻,在请求的头部照顾上这个令牌,来做相应的身份认证,所以本身在请求中做了这些修改(有标注),后端没修改,源码:

        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.细致想想,彷佛,好像这个题目遇到过,还提干涉,确切提过,链接在这里。但这次的设置和上次一样,就在header里多加了一个自定义token,但却报了和上次没有设置headers: {‘Content-Type’: ‘application/x-www-form-urlencoded’}一样的毛病信息,因而,手足无措,算了,重头再来,好好百度,研究一下cors跨域。

理论进修

命运运限不错,找到了一篇好文,文章讲的很细,也找到本身题目的地点:触发 CORS 预检请求。援用原文的话加以本身总结:跨域资本共享规范新增了一组 HTTP 首部字段,许可效劳器声明哪些源站有权限接见哪些资本。别的,范例请求,对那些可能对效劳器数据发生副作用的 HTTP 请求要领(特别是 GET 以外的 HTTP 请求,或许搭配某些 MIME 范例的 POST 请求),阅读器必需起首运用 OPTIONS 要领提议一个预检请求(preflight request:素昧平生有无?诶,对,上面谁人毛病信息中,就有一个如许生疏的辞汇),从而获知效劳端是不是许可该跨域请求。效劳器确认许可以后,才提议现实的 HTTP 请求。在预检请求的返回中,效劳器端也能够关照客户端,是不是须要照顾身份凭据(包含 Cookies 和 HTTP 认证相干数据)。所以跨域请求分两种:简朴请乞降预检请求。一次完全的请求不须要效劳端预检,直接相应的,归为简朴请求;而相应前须要预检的,称为预检请求,只要预检请求经由过程,才有接下来的简朴请求。关于那些是简朴请求,那些会触发预检请求,文章做了细致的总结,这里列出触发预检请求的前提(不晓得头脑为啥会想到那些会触发BFC的前提),不要跑题,原文是如许总结的:

当请求满足下述任一前提时,即应起首发送预检请求:
运用了下面任一 HTTP 要领:
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
工资设置了对 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

题目剖析

所以,再来看本身两次出错(第一次是没有设置:headers: {‘Content-Type’: ‘application/x-www-form-urlencoded’}, 第二次是设置自定义header,headers:{‘token’:token}。很巧,有无,一次少,一次多,都点燃了导火索),实在都是触发了预检请求。关于第一次的毛病,很好处理,增添headers: {‘Content-Type’: ‘application/x-www-form-urlencoded’},就处理了,关于Conten-Type的几种取值,你须要晓得的。但关于第二个毛病,彷佛没法向第一种那样,将预检请求转变为简朴请求,所以,只要寻觅要领怎样在后端完成相应的预检请求,来返回一个状况码2xx,通知阅读器此次跨域请求能够继承。所以注意力转向后端。
关于JAVA完成预检请求,基础都是采纳过滤器,不要问我为何不是监听器或许拦截器(我就是个伪全栈,就不要互相为难了,本身百度之),自定义(copy)了一个filter,并在web.xml中举行了设置。源码:

Filter接口完成部份:
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 接口的自定义完成
    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);//经由过程打印,能够看到一次非简朴请求,会被过滤两次,即请求两次,第一次请求确认是不是相符跨域请求(预检),这一次是不带headers的自定义信息,第二次请求会照顾自定义信息。
        if ("OPTIONS".equals(request.getMethod())){//这里经由过程推断请求的要领,推断此次是不是是预检请求,如果是,马上返回一个204状况吗,标示,许可跨域;预检后,正式请求,这个要领参数就是我们设置的post了
          response.setStatus(HttpStatus.SC_NO_CONTENT); //HttpStatus.SC_NO_CONTENT = 204
          response.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, OPTIONS, DELETE");//当判定为预检请求后,设定许可请求的要领
          response.setHeader("Access-Control-Allow-Headers", "Content-Type, x-requested-with, Token"); //当判定为预检请求后,设定许可请求的头部范例
          response.addHeader("Access-Control-Max-Age", "1");  // 预检有用坚持时候                       
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
    @Override
    public void destroy() {     
    }
}
web.xml设置部份
<filter>
<filter-name>cors</filter-name>
<filter-class>stock.model.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>   

关于Access-Control-Max-Age

近来又最先写java,再回来看这个,发明当时Access-Control-Max-Age设置了1.实在如许写有很大题目,由于每一个庞杂请求都会发两次。明显如许是现代所不能接收的,所以Max-Age的值合适设的大一些,详细多大很营业需求相干。别的Access-Control-Max-Age不是针对请求域名有用的,是请求的完成途径有用的,比方第一次发出www.exanple.com/api/corsGet,会发生一次options请乞降一次post请求,然后我再请求一次,这时候没有预检请求了,只要post请求。但再发送一次www.exanple.com/api/corsSave请求,会发明又发生了一次options请乞降一次post请求,所以Access-Control-Max-Age不是针对雷同的origin有用,而是针对雷同的requestUrl有用。很主要哦。

结论

当在后端完成增加上面的源码后,大快人心,题目得以处理,补上失利和胜利,本身截下的两张请求相应图。《cors跨域之简朴要求与预检要求(发送要求头带令牌token)》细致看请求相应失利提议相应那张图,在General的数据集合,能够看到要领是options,而非代码指定的post请求,所以这是一次阅读器发出的一次预检请求,让效劳器确认此IP是不是有接见的权限,如果有,效劳器须要返回一个2xx的状况码给阅读器。紧接着再提议一次简朴请求。以下面在devtools中的截取图片(为了对照消灭,我把两次离别截取,做了拼接,由于不会做动态图)。能够看到同一个post请求,现实上发生了两次网络连接。《cors跨域之简朴要求与预检要求(发送要求头带令牌token)》
但关于cors,要去探究的,另有许多许多,所以遵照反动语录:实践(偶然也能够是时候)是磨练真谛的唯一规范,是没有错的。后续有新的收成,再补充。

    原文作者:Denzel
    原文地址: https://segmentfault.com/a/1190000009971254
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞