後端接口和前端分離的時候,很多情況下會遇到跨域問題。這是瀏覽器的同源策略導致的,同源策略是爲了Web安全提出的,說的是兩個不同源的網址默認是不能請求對方的接口的。不同源包含:協議(http|https)、ip/域名、端口之一不同就是不同源。不同源的網頁請求接口都要遵循瀏覽器的同源策略。
解決跨域問題有兩種方案,都需要服務端支持纔可以。
一種是JSONP,基本意思是通過在網頁上動態添加一個script標籤,在這個標籤中去請求接口,帶上一個函數名,在網頁存在,這個是不用遵循同源策略的,這個接口返回中用函數名包裹,頁面就可以回調此函數得到真正的數據。JSONP只支持GET方法。
另外一種是CORS(Cross Origin Resource Share),跨域資源共享。這是跨域問題的終極解決方案。支持所有的方法。CORS對於簡單請求和非簡單請求採取不同的處理方案。簡單請求是滿足以下條件的請求,不滿足的都是非簡單請求。
(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
對於簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,增加一個Origin
字段。
下面是一個例子,瀏覽器發現這次跨源AJAX請求是簡單請求,就自動在頭信息之中,添加一個Origin
字段。
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
上面的頭信息中,Origin
字段用來說明,本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否同意這次請求。如果Origin
指定的源,不在許可範圍內,服務器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭信息沒有包含Access-Control-Allow-Origin
字段,就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest
的onerror
回調函數捕獲。注意,這種錯誤無法通過狀態碼識別,因爲HTTP迴應的狀態碼有可能是200。
如果Origin
指定的域名在許可範圍內,服務器返回的響應,會多出幾個頭信息字段。
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
對於非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱爲"預檢"請求(preflight)。
瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答覆,瀏覽器纔會發出正式的XMLHttpRequest
請求,否則就報錯。
下面針對Nginx和Java代碼作出服務端的CORS配置:
Nginx:
if ( $request_method = 'OPTIONS' ) {
add_header Access-Control-Allow-Origin 'http://somedomain:port';
add_header Access-Control-Allow-Origin '$http_origin';
add_header Access-Control-Allow-Methods 'POST,GET,PUT,OPTIONS,DELETE';
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Headers 'Origin,X-Requested-With,Content-Type,Accept,Authorization,sourcetype,token';
add_header Access-Control-Allow-Credentials true;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 200;
}
add_header Access-Control-Allow-Origin 'http://somedomain:port';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type,*';
add_header Access-Control-Allow-Origin '$http_origin';//這樣就能全部了,*是不行的
靜態資源設置允許跨域,否則canvas使用不行
img.crossOrigin = 'anonymous'; //可選值:anonymous,*
服務端nginx
server {
listen 80;
add_header 'Access-Control-Allow-Origin' '*';
location /Roboto/ {
root /home/images;
autoindex on;
}
}
在SpringBoot中可以通過設置Filter或者WebMvcConfigurerAdapter#addCorsMappings(CorsRegistry registry)做配置
先定義CORS每個配置項
@ConfigurationProperties(prefix = "cors")
public class CorsProperties {
private String[] allowedOrigins;
private String[] allowedMethods;
private String[] allowedHeaders;
private String[] exposedHeaders;
private long maxAge;
private boolean allowCredentials;
public String[] getAllowedOrigins() {
return allowedOrigins;
}
public void setAllowedOrigins(String[] allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
public String[] getAllowedMethods() {
return allowedMethods;
}
public void setAllowedMethods(String[] allowedMethods) {
this.allowedMethods = allowedMethods;
}
public String[] getAllowedHeaders() {
return allowedHeaders;
}
public void setAllowedHeaders(String[] allowedHeaders) {
this.allowedHeaders = allowedHeaders;
}
public String[] getExposedHeaders() {
return exposedHeaders;
}
public void setExposedHeaders(String[] exposedHeaders) {
this.exposedHeaders = exposedHeaders;
}
public long getMaxAge() {
return maxAge;
}
public void setMaxAge(long maxAge) {
this.maxAge = maxAge;
}
public boolean isAllowCredentials() {
return allowCredentials;
}
public void setAllowCredentials(boolean allowCredentials) {
this.allowCredentials = allowCredentials;
}
}
配置文件配置:
#cors.allowedOrigins=http://113.204.233.35:21128
cors.allowedOrigins=*
cors.allowedMethods=POST,GET,PUT,DELETE,OPTIONS,HEAD
#cors.allowedHeaders=Origin,X-Requested-With,Content-Type,Accept,Authorization,token,userId,userCode,userName,user
cors.allowedHeaders=*
cors.maxAge=3600
cors.allowCredentials=true
通過CorsFilter配置:
@Configuration
public class CorsConfig {
@Autowired
private CorsProperties corsProperties;
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
for (String origin : corsProperties.getAllowedOrigins()) {
corsConfiguration.addAllowedOrigin(origin);
}
for (String method : corsProperties.getAllowedMethods()) {
corsConfiguration.addAllowedMethod(method);
}
for (String header : corsProperties.getAllowedHeaders()) {
corsConfiguration.addAllowedHeader(header);
}
if(ArrayUtil.isNotEmpty(corsProperties.getExposedHeaders())){
for (String header : corsProperties.getExposedHeaders()) {
corsConfiguration.addExposedHeader(header);
}
}
corsConfiguration.setMaxAge(corsProperties.getMaxAge());
corsConfiguration.setAllowCredentials(corsProperties.isAllowCredentials());
return corsConfiguration;
}
}
參考文件:
http://www.ruanyifeng.com/blog/2016/04/cors.html