同源策略是瀏覽器的一個安全功能,不一樣源的客戶端腳本在沒有明確受權的狀況下,不能讀寫對方資源。 同源策略是瀏覽器安全的基石。javascript
例如辦公內外網環境,當咱們訪問外網一個惡意網站的時候,惡意網站就會利用咱們的主機向內網發送 ajax 請求,破壞或獲取隱私數據。css
Content-Type 是指 http/https 發送信息至服務端時的內容編碼類型,在 HTTP 協議消息頭中,使用 Content-Type 來表示請求和響應中的媒體類型信息。它用來告訴服務端如何處理請求的數據,以及告訴客戶端(通常是瀏覽器)如何解析響應的數據,好比顯示圖片,解析並展現 html 等等。html
並非請求或響應獨有的參數前端
以 JQuery 爲例,發送 ajax 請求的時候,設置dataType:"jsonp"
,將使用 JSONP 方式調用函數,函數的 url 變爲myurl?callback=e5bbttt
的形式,e5bbttt 就是一個臨時方法名,後端會根據callback
的值返回一個 js 腳本,如java
<script> e5bbttt({"a":"aaa","b":"bbb"}); </script>
JQuery 會提早根據 ajax 中 success 的內容生成一個臨時函數,名字就是 xxxreact
$.ajax({ // 其餘省略 dataType:"jsonp", success:function(data){ console.log(data.a); console.log(data.b); }, jsonp:"e5bbttt" }) // JQuery 生成的臨時函數 function e5bbttt(data){ ajaxObject.success(data); }
服務端返回給客戶端的e5bbttt({"a":"aaa","b":"bbb"});
,至關於調用當即(?)調用了 JQuery 生成的e5bbttt
函數,用完這個函數就銷燬了(?)nginx
JSONP 也算是一個約定俗成的「協議」,callback 是約定俗成的做爲定義臨時函數名的參數。若是想自定義這個參數名,須要在 ajax 中用 jsonp 屬性定義。web
跟用戶數據有關的就是動態請求,沒有數據的是靜態請求,好比 css js,so,HTTP 服務器(Apache、Nginx 等)至少作了兩個做用ajax
在服務器端解決跨域有2種解決思路spring
依據瀏覽器同源策略,非同源腳本不可操做其餘源下面的對象。想要操做其餘源下的對象就須要跨域。綜上所述,在同源策略的限制下,非同源的網站之間不能發送 ajax 請求。若有須要,可經過降域或其餘技術實現。
爲了解決瀏覽器跨域問題,W3C 提出了跨源資源共享方案,即 CORS(Cross-Origin Resource Sharing)。
CORS 能夠在不破壞即有規則的狀況下,經過後端服務器實現 CORS 接口,就能夠實現跨域通訊。
CORS 將請求分爲兩類:簡單請求和非簡單請求,分別對跨域通訊提供了支持。
一個簡單請求:
GET /test HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate, sdch, br Origin: http://www.test.com Host: www.test.com
對於簡單請求,CORS 的策略是請求時在請求頭中增長一個 Origin 字段,表示請求發出的域。服務器收到請求後,根據該字段判斷是否容許該請求訪問。
除了上面提到的 Access-Control-Allow-Origin,還有幾個字段用於描述 CORS 返回結果
通常是發送 JSON 格式的 ajax 請求,或帶有自定義頭的請求
對於非簡單請求的跨源請求,瀏覽器會在真實請求發出前,增長一次 OPTION 請求,稱爲預檢請求(preflightrequest)。預檢請求將真實請求的信息,包括請求方法、自定義頭字段、源信息添加到 HTTP 頭信息字段中,詢問服務器是否容許這樣的操做
例如一個 GET 請求的預檢請求,包含一個自定義參數 X-Custom-Header
OPTIONS /test HTTP/1.1 Origin: http://www.test.com Access-Control-Request-Method: GET // 請求使用的 HTTP 方法 Access-Control-Request-Headers: X-Custom-Header // 請求中包含的自定義頭字段 Host: www.test.com
服務器收到請求時,須要分別對 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 進行驗證,驗證經過後,會在返回 HTTP 頭信息中添加:
HTTP/1.1 200 OK Access-Control-Allow-Origin: http://www.test.com // 容許的域 Access-Control-Allow-Methods: GET, POST, PUT, DELETE // 容許的方法 Access-Control-Allow-Headers: X-Custom-Header // 容許的自定義字段 Access-Control-Allow-Credentials: true // 是否容許用戶發送、處理 cookie Access-Control-Max-Age: 172800 // 預檢請求的有效期,單位爲秒。有效期內,不須要發送預檢請求,ps 48小時
當預檢請求經過後,瀏覽器纔會發送真實請求到服務器。這樣就實現了跨域資源的請求訪問。
因此後端處理其實處理的就是此次預檢請求
==注意:==
==在 Chrome 和 Firefox 中,若是 Access-Control-Allow-Methods 中並未容許 GET/POST/HEAD 請求,但容許跨域了,瀏覽器仍是會容許 GET/POST/HEAD 這些簡單請求訪問,這時就必須在後臺用其餘辦法禁掉這些 Method==
這種方法不會用到 Spring,對 Servlet 也可使用
在 web.xml 中配置
<!-- 跨域 --> <filter> <filter-name>webFliter</filter-name> <filter-class>com.n031.filter.WebFliter</filter-class> </filter> <filter-mapping> <filter-name>webFliter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
編寫 java 類
import javax.servlet.*; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class WebFliter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest)request; HttpServletResponse res = (HttpServletResponse) response; // 容許跨域的域名,設置*表示容許全部域名 String origin = req.getHeader("Origin"); if ("abcdefg".contains(origin)) { // 知足指定的條件 res.addHeader("Access-Control-Allow-Origin", origin); } res.addHeader("Access-Control-Allow-Origin", "http://www.test.com"); // 容許跨域的方法,可設置*表示全部 res.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); // 容許的自定義字段 String headers = req.getHeader("Access-Control-Request-Headers"); // 獲取 request 發來的自定義字段 res.addHeader("Access-Control-Allow-Headers", headers); // 或者 // res.addHeader("Access-Control-Allow-Headers", "X-Custom-Header"); // 預檢請求的有效期,單位爲秒。有效期內,不須要發送預檢請求,ps 48小時 res.addHeader("Access-Control-Max-Age", "172800"); // 還能夠有其餘配置... chain.doFilter(request, response); } @Override public void destroy() { } }
Spring 解決跨域的方法不少,感受就和茴字有五種寫法同樣。這裏列舉的並不全。
先看下原理。說實話雖然搞不懂爲何這麼作,但看了下這個類的源碼確實是這麼寫的。
本質都是構造CorsConfiguration
而後委託給DefaultCorsProcessor
實現(責任鏈模式,要學的東西好多啊...)
public class CorsConfiguration { private List<String> allowedOrigins; private List<String> allowedMethods; private List<String> allowedHeaders; private List<String> exposedHeaders; private Boolean allowCredentials; private Long maxAge; }
DefaultCorsProcessor
的processRequest
處理步驟以下(spring-web 5.1.8-RELEASE
)
CorsConfiguration
)決定是否放行這種方式適合只有一兩個 rest 接口須要跨域或者沒有網關的狀況下,這種處理方式就很是簡單,適合在原來基代碼基礎上修改,影響比較小。
@CrossOrigin(allowCredentials = "true", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE}, origins = "*") @PostMapping("/abc") public String handler(@RequestBody String json) { return "abc"; }
@Configuration public class CorsConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**")// 容許跨域的訪問路徑 .allowedOrigins("*")// 容許跨域訪問的源 .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")// 容許請求方法 .maxAge(172800)// 預檢間隔時間 .allowCredentials(true);// 是否容許發送 cookie } }
注意因爲 Java8 開始支持 default method,這個類從 spring 5.0 開始已通過期,將來這個方法將轉移到WebMvcConfigurer
接口中
default void addCorsMappings(CorsRegistry registry){}
其實和方法2相似,都是構造CorsConfiguration
@Configuration public class CorsConfig { @Bean public FilterRegistrationBean<CorsFilter> corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); // 是否發送cookie config.setAllowCredentials(true); // 容許的網站域名,全容許則設爲 * config.addAllowedOrigin("http://localhost:8088"); // 容許 HEADER 或 METHOD , * 爲所有 config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source)); // 這個順序很重要,爲避免麻煩請設置在最前 bean.setOrder(0); return bean; } }
以上這種方案若是微服務多的話,須要在每一個服務的主類上都加上這麼段代碼,增長了維護量。
這三種方案都是在 SpringBoot 的基礎上實現的解決方案,在模塊較多或者接口較多的狀況下不易維護。
既然 Spring Cloud 自帶 Gateway,下面就講講使用 Gateway 的跨域解決方案。(Gateway 是取代不斷跳票的 Zuul 的新一代網關)
==4 5 方法未驗證==
這種方案跟方案三有些相似,只不過是放到了 Gateway 端,對於有多個微服務模塊的狀況下,就大大減小了 SpringBoot 模塊端的代碼量,讓各個模塊更集中精力作業務邏輯實現。這個方案只須要在 Gateway 裏添加 Filter 代碼類便可。
import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.cors.CorsUtils; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; import javax.servlet.http.HttpServletRequest; @Configuration public class CorsWebFilter implements WebFilter { private static final String ALL = "*"; private static final String MAX_AGE = "18000"; @Override public Mono<Void> filter(ServerWebExchange ctx, WebFilterChain chain) { ServerHttpRequest request = ctx.getRequest(); String path = request.getPath().value(); ServerHttpResponse response = ctx.getResponse(); if ("/favicon.ico".equals(path)) { response.setStatusCode(HttpStatus.OK); return Mono.empty(); } if (!CorsUtils.isCorsRequest((HttpServletRequest) request)) { return chain.filter(ctx); } HttpHeaders requestHeaders = request.getHeaders(); HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod(); HttpHeaders headers = response.getHeaders(); headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin()); headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders()); if (requestMethod != null) { headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name()); } headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL); headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE); if (request.getMethod() == HttpMethod.OPTIONS) { response.setStatusCode(HttpStatus.OK); return Mono.empty(); } return chain.filter(ctx); } }
在仔細閱讀過 Gateway 的文檔你就會發現,原來 CorsFilter 早已經在 Gateway 裏了,不須要本身寫代碼實現,並且更靈活,修改配置文件便可,結合配置中心使用,能夠實現動態修改。
spring: cloud: gateway: globalcors: corsConfigurations: '[/**]': allowedOrigins: "docs.spring.io" allowedMethods: - GET
這裏的 Nginx 盡作反向代理功能,瀏覽器訪問頁面在 a.com 的 Nginx 上,ajax 請求接口是 b.com,因此瀏覽器認爲是跨域
Nginx 在 nginx.conf 上配(vhost 是約定作法,這樣作不修改主文件)
include vhost/*.config;
建立 cors.conf
server{ listen 80; // 監聽80端口 server_name b.com; // 監聽向 b.com 發送的請求 location /{ proxy_pass http://ser432ver.53253bb.com:8080; // 轉發到哪裏 // Filter實現的功能在Nginx上再實現一遍 add_header Access-Control-Allow-Origin $http_origin; // $http_ 能夠獲取請求中相應的 header 參數 add_header Access-Control-Allow-Method *; add_header Access-Control-Allow-Headers X-Custom-Header; // 或者 // add_header Access-Control-Allow-Headers $http_access_control_request_headers; add_header Access-Control-Allow-Credentials true; add_header Access-Max-age 172800; // 直接處理預檢命令,if 後要帶空格 if ($request_method = OPTIONS) { return 200; } } }
但其實大部分狀況下,咱們會把前端應用和請求轉發放在同一臺 Nginx 上
server{ listen 80; // 監聽80端口 server_name a.com; // 監聽向 a.com 發送的請求 location / { root html; index index.html index.htm; } locltion /ajaxserver { proxy_pass http://ser432ver.53253bb.com:8080; // 後端地址 } }
這樣實質是隱藏跨域,讓瀏覽器認爲沒有訪問其餘域。前端代碼須要每一個 ajax 請求前都要加上/ajaxserver
ajax跨域徹底講解
https://www.imooc.com/learn/947
SpringBoot使用CORS解決跨域請求問題
http://www.javashuo.com/article/p-nuxbqjzg-gy.html
Spring MVC之@RequestParam @RequestBody @RequestHeader 等詳解
https://blog.csdn.net/summerSunStart/article/details/78676781
你不知道的「跨域 CORS」
https://www.jianshu.com/p/abb5f6bf92c3
關於跨域問題和安全性的一點理解
https://blog.csdn.net/jaytalent/article/details/52213576
淺談跨域威脅與安全
https://www.freebuf.com/articles/web/208672.html
cors跨域中關於access-control-allow-headers致使的錯誤
https://www.jianshu.com/p/cecb73b26a11
什麼是跨域?跨域解決方法
https://blog.csdn.net/qq_38128179/article/details/84956552
Spring Cloud配置跨域訪問的五種方案?你用的是哪種呢?
http://www.javashuo.com/article/p-mnpcciss-bv.html
servlet跨域請求
https://blog.csdn.net/qq_34135615/article/details/82900786
跨域(CORS) 解決方案中,爲何 Access-Control-Allow-Methods 不起做用?
https://segmentfault.com/q/1010000005067552/a-1020000005067822