b. 对于非简单请求,比如POST、PUT、DELETE等,浏览器会分两次应答。第一次preflight(method: OPTIONS),主要验证来源是否合法,并返回允许的Header等。第二次才是真正的HTTP应答。所以服务器必须处理OPTIONS应答。
这里是一个nginx启用CORS的参考配置示例。代码:
# # A CORS (Cross-Origin Resouce Sharing) config for nginx # # == Purpose # # This nginx configuration enables CORS requests in the following way: # - enables CORS just for origins on a whitelist specified by a regular expression # - CORS preflight request (OPTIONS) are responded immediately # - Access-Control-Allow-Credentials=true for GET and POST requests # - Access-Control-Max-Age=20days, to minimize repetitive OPTIONS requests # - various superluous settings to accommodate nonconformant browsers # # == Comment on echoing Access-Control-Allow-Origin # # How do you allow CORS requests only from certain domains? The last # published W3C candidate recommendation states that the # Access-Control-Allow-Origin header can include a list of origins. # (See: #access-control-allow-origin-response-header ) # However, browsers do not support this well and it likely will be # dropped from the spec (see, ?rfc=6454&eid=3249 ). # # The usual workaround is for the server to keep a whitelist of # acceptable origins (as a regular expression), match the request's # Origin header against the list, and echo back the matched value. # # (Yes you can use '*' to accept all origins but this is too open and # prevents using 'Access-Control-Allow-Credentials: true', which is # needed for HTTP Basic Access authentication.) # # == Comment on spec # # Comments below are all based on my reading of the CORS spec as of # 2013-Jan-29 ( ), the # XMLHttpRequest spec ( # ), and # experimentation with latest versions of Firefox, Chrome, Safari at # that point in time. # # == Changelog # # shared at: https://gist.github.com/algal/5480916 # based on: https://gist.github.com/alexjs/4165271 # location / { # if the request included an Origin: header with an origin on the whitelist, # then it is some kind of CORS request. # specifically, this example allow CORS requests from # scheme : http or https # authority : any authority ending in ".mckinsey.com" # port : nothing, or : if ($http_origin ~* (https?://[^/]*\.mckinsey\.com(:[0-9]+)?)$) { set $cors "true"; } # Nginx doesn't support nested If statements, so we use string # concatenation to create a flag for compound conditions # OPTIONS indicates a CORS pre-flight request if ($request_method = 'OPTIONS') { set $cors "${cors}options"; } # non-OPTIONS indicates a normal CORS request if ($request_method = 'GET') { set $cors "${cors}get"; } if ($request_method = 'POST') { set $cors "${cors}post"; } # if it's a GET or POST, set the standard CORS responses header if ($cors = "trueget") { # Tells the browser this origin may make cross-origin requests # (Here, we echo the requesting origin, which matched the whitelist.) add_header 'Access-Control-Allow-Origin' "$http_origin"; # Tells the browser it may show the response, when XmlHttpRequest.withCredentials=true. add_header 'Access-Control-Allow-Credentials' 'true'; # # Tell the browser which response headers the JS can see, besides the "simple response headers" # add_header 'Access-Control-Expose-Headers' 'myresponseheader'; } if ($cors = "truepost") { # Tells the browser this origin may make cross-origin requests # (Here, we echo the requesting origin, which matched the whitelist.) add_header 'Access-Control-Allow-Origin' "$http_origin"; # Tells the browser it may show the response, when XmlHttpRequest.withCredentials=true. add_header 'Access-Control-Allow-Credentials' 'true'; # # Tell the browser which response headers the JS can see, besides the "simple response headers" # add_header 'Access-Control-Expose-Headers' 'myresponseheader'; } # if it's OPTIONS, then it's a CORS preflight request so respond immediately with no response body if ($cors = "trueoptions") { # Tells the browser this origin may make cross-origin requests # (Here, we echo the requesting origin, which matched the whitelist.) add_header 'Access-Control-Allow-Origin' "$http_origin"; # in a preflight response, tells browser the subsequent actual request can include user credentials (e.g., cookies) add_header 'Access-Control-Allow-Credentials' 'true'; # # Return special preflight info # # Tell browser to cache this pre-flight info for 20 days add_header 'Access-Control-Max-Age' 1728000; # Tell browser we respond to GET,POST,OPTIONS in normal CORS requests. # # Not officially needed but still included to help non-conforming browsers. # # OPTIONS should not be needed here, since the field is used # to indicate methods allowed for "actual request" not the # preflight request. # # GET,POST also should not be needed, since the "simple # methods" GET,POST,HEAD are included by default. # # We should only need this header for non-simple requests # methods (e.g., DELETE), or custom request methods (e.g., XMODIFY) add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; # Tell browser we accept these headers in the actual request # # A dynamic, wide-open config would just echo back all the headers # listed in the preflight request's # Access-Control-Request-Headers. # # A dynamic, restrictive config, would just echo back the # subset of Access-Control-Request-Headers headers which are # allowed for this resource. # # This static, fairly open config just returns a hardcoded set of # headers that covers many cases, including some headers that # are officially unnecessary but actually needed to support # non-conforming browsers # # Comment on some particular headers below: # # Authorization -- practically and officially needed to support # requests using HTTP Basic Access authentication. Browser JS # can use HTTP BA authentication with an XmlHttpRequest object # req by calling # # req.withCredentials=true, and # req.setRequestHeader('Authorization','Basic ' + window.btoa(theusername + ':' + thepassword)) # # Counterintuitively, the username and password fields on # XmlHttpRequest#open cannot be used to set the authorization # field automatically for CORS requests. # # Content-Type -- this is a "simple header" only when it's # value is either application/x-www-form-urlencoded, # multipart/form-data, or text/plain; and in that case it does # not officially need to be included. But, if your browser # code sets the content type as application/json, for example, # then that makes the header non-simple, and then your server # must declare that it allows the Content-Type header. # # Accept,Accept-Language,Content-Language -- these are the # "simple headers" and they are officially never # required. Practically, possibly required. # # Origin -- logically, should not need to be explicitly # required, since it's implicitly required by all of # CORS. officially, it is unclear if it is required or # forbidden! practically, probably required by existing # browsers (Gecko does not request it but WebKit does, so # WebKit might choke if it's not returned back). # # User-Agent,DNT -- officially, should not be required, as # they cannot be set as "author request headers". practically, # may be required. # # My Comment: # # The specs are contradictory, or else just confusing to me, # in how they describe certain headers as required by CORS but # forbidden by XmlHttpRequest. The CORS spec says the browser # is supposed to set Access-Control-Request-Headers to include # only "author request headers" (section 7.1.5). And then the # server is supposed to use Access-Control-Allow-Headers to # echo back the subset of those which is allowed, telling the # browser that it should not continue and perform the actual # request if it includes additional headers (section 7.1.5, # step 8). So this implies the browser client code must take # care to include all necessary headers as author request # headers. # # However, the spec for XmlHttpRequest#setRequestHeader # (section 4.6.2) provides a long list of headers which the # the browser client code is forbidden to set, including for # instance Origin, DNT (do not track), User-Agent, etc.. This # is understandable: these are all headers that we want the # browser itself to control, so that malicious browser client # code cannot spoof them and for instance pretend to be from a # different origin, etc.. # # But if XmlHttpRequest forbids the browser client code from # setting these (as per the XmlHttpRequest spec), then they # are not author request headers. And if they are not author # request headers, then the browser should not include them in # the preflight request's Access-Control-Request-Headers. And # if they are not included in Access-Control-Request-Headers, # then they should not be echoed by # Access-Control-Allow-Headers. And if they are not echoed by # Access-Control-Allow-Headers, then the browser should not # continue and execute actual request. So this seems to imply # that the CORS and XmlHttpRequest specs forbid certain # widely-used fields in CORS requests, including the Origin # field, which they also require for CORS requests. # # The bottom line: it seems there are headers needed for the # web and CORS to work, which at the moment you should # hard-code into Access-Control-Allow-Headers, although # official specs imply this should not be necessary. # add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since'; # build entire response to the preflight request # no body in this response add_header 'Content-Length' 0; # (should not be necessary, but included for non-conforming browsers) add_header 'Content-Type' 'text/plain charset=UTF-8'; # indicate successful return with no content return 204; } # --PUT YOUR REGULAR NGINX CODE HERE-- }服务器解析流程如下:
a.首先查看http头部有无origin字段;
b.如果没有,或者不允许,直接当成普通请求处理,结束;
c.如果有并且是允许的,那么再看是否是preflight(method=OPTIONS);
d.如果是preflight,就返回Allow-Headers、Allow-Methods等,内容为空;
e.如果不是preflight,就返回Allow-Origin、Allow-Credentials等,并返回正常内容。
若服务器为nginx,可以
location / { add_header Access-Control-Allow-Origin *; }