日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

跨域問題的由來

相信很多人都或多或少了解過跨域問題,尤其在現(xiàn)如今前后端分離大行其道的時候。

你在本地開發(fā)一個前端項目,這個項目是通過 node 運行的,端口是9528,而服務端是通過 spring boot 提供的,端口號是7001。

當你調(diào)用一個服務端接口時,很可能得到類似下面這樣的一個錯誤:

跨域問題是怎樣造成的?

 

然后你在發(fā)送請求的地方debug,在出現(xiàn)異常的地方你將得到這樣的結(jié)果:

跨域問題是怎樣造成的?

 

異常對象很詭異,返回的 response 是 undefined 的,并且 message 消息中只有一個"Network Error"。

看到這里你應該要知道,你遇到跨域問題了。

但是你需要明確的一點是,這個請求已經(jīng)發(fā)出去了,服務端也接收到并處理了,但是返回的響應結(jié)果不是瀏覽器想要的結(jié)果,所以瀏覽器將這個響應的結(jié)果給攔截了,這就是為什么你看到的response是undefined。

瀏覽器的同源策略

那瀏覽器為什么會將服務端返回的結(jié)果攔截掉呢?

這就需要我們了解瀏覽器基于安全方面的考慮,而引入的 同源策略(same-origin policy) 了。

早在1995年,Netscape 公司就在瀏覽器中引入了“同源策略”。

最初的 “同源策略”,主要是限制Cookie的訪問,A網(wǎng)頁設置的 Cookie,B網(wǎng)頁無法訪問,除非B網(wǎng)頁和A網(wǎng)頁是“同源”的。

那么怎么確定兩個網(wǎng)頁是不是“同源”呢,所謂“同源”就是指"協(xié)議+域名+端口"三者相同,即便兩個不同的域名指向同一個ip地址,也非同源。

跨域問題是怎樣造成的?

 

沒有同源策略的保護

那么為什么要做這個同源的限制呢?因為如果沒有同源策略的保護,瀏覽器將沒有任何安全可言。

老李是一個釣魚愛好者,經(jīng)常在 我要買(51mai.com) 的網(wǎng)站上買各種釣魚的工具,并且通過 銀行(yinhang.com) 以賬號密碼的方式直接支付。

這天老李又在 51mai.com 上買了一根魚竿,輸入銀行賬號密碼支付成功后,在支付成功頁看到一個叫 釣魚(diaoyu.com) 的網(wǎng)站投放的一個"免費領取魚餌"的廣告。

老李什么都沒想就點擊了這個廣告,跳轉(zhuǎn)到了釣魚的網(wǎng)站,殊不知這真是一個 “釣魚” 網(wǎng)站,老李銀行賬戶里面錢全部被轉(zhuǎn)走了。

跨域問題是怎樣造成的?

 

以上就是老李的錢被盜走的過程:

1.老李購買魚竿,并登錄了銀行的網(wǎng)站輸入賬號密碼進行了支付,瀏覽器在本地緩存了銀行的Cookie

2.老李點擊釣魚網(wǎng)站,釣魚網(wǎng)站使用老李登錄銀行之后的Cookie,偽造成自己是老李進行了轉(zhuǎn)賬操作。

這個過程就是著名的CSRF(Cross Site Request Forgery),跨站請求偽造,正是由于可能存在的偽造請求,導致了瀏覽器的不安全。

那么如何防止CSRF攻擊呢,可以參考這篇文章:如何防止CSRF攻擊?

同源策略限制哪些行為

上面說了 **同源策略 **是一個安全機制,他本質(zhì)是限制了從一個源加載的文檔或腳本如何與來自另一個源的資源進行交互,這是一個用于隔離潛在惡意文件的重要安全機制。

隨著互聯(lián)網(wǎng)的發(fā)展,"同源策略"越來越嚴格,不僅限于Cookie的讀取。目前,如果非同源,共有三種行為受到限制。

(1) Cookie、LocalStorage 和 IndexDB 無法讀取。

(2) DOM 無法獲得。

(3) 請求的響應被攔截。

雖然這些限制是必要的,但是有時很不方便,合理的用途也會受到影響,所以為了能夠獲取非“同源”的資源,就有了跨域資源共享。

跨域資源共享

看到這里你應該明白,為什么文章開頭的請求會被攔截了,原因就是請求的源和服務端的源不是“同源”,而服務端又沒有設置允許的跨域資源共享,所以請求的響應被瀏覽器給攔截掉了。

CORS 是一個 W3C 標準,全稱是"跨域資源共享"(Cross Origin Resource Sharing),它允許瀏覽器向跨源服務器,發(fā)出 XMLHttpRequest 請求,從而克服了只能發(fā)送同源請求的限制。

CORS實現(xiàn)機制

那跨域資源共享機制是怎樣實現(xiàn)的呢?

當一個資源(origin)通過腳本向另一個資源(host)發(fā)起請求,而被請求的資源(host)和請求源(origin)是不同的源時(協(xié)議、域名、端口不全部相同),瀏覽器就會發(fā)起一個 跨域 HTTP 請求 ,并且瀏覽器會自動將當前資源的域添加在請求頭中一個叫 Origin 的 Header 中。

當然了,有三個標簽本身就是允許跨域加載資源的:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

比如某個網(wǎng)站的首頁 http://domain-a.com/index.html 通過 <img src="http://domain-b.com/image.jpg" /> 來加載其他域上的圖片,除此之外還有諸如通過 CDN 節(jié)點引入css和js文件的方式。

出于安全原因,瀏覽器限制從腳本內(nèi)發(fā)起的跨域 HTTP 請求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。 也就是說使用這些 API 的 Web 應用程序只能從加載應用程序的同一個域請求 HTTP 資源,除非響應報文中包含了正確 CORS 響應頭。

通過在響應報文中設置額外的 HTTP 響應頭來告訴瀏覽器,運行在某個 origin 上的 Web 應用被準許訪問來自不同源服務器上的資源,此時瀏覽器就不會將該響應攔截掉了。

那這些額外的 HTTP 響應頭是什么呢?

響應頭是否必須含義Access-Control-Allow-Origin是該字段表示,服務端接收哪些來源的域的請求Access-Control-Allow-Credentials否是否可以向服務端發(fā)送Cookie,默認是 falseAccess-Control-Expose-Headers否可以向請求額外暴露的響應頭

其中只有 Access-Control-Allow-Origin 是必須的,該響應頭的值可以是請求的 Origin 的值,也可以是 * ,表示服務端接收所有來源的請求。

當瀏覽器發(fā)起 CORS 請求時,默認只能獲得6個響應頭的值:

Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma

如果還需要返回其他的響應頭給前端,則可以通過在 Access-Control-Expose-Headers 中指定。

CORS的兩種請求類型

CORS有兩種類型的請求,分別是:簡單請求(simple request)和非簡單請求(not-so-simple request)

只要同時滿足以下兩大條件,就屬于簡單請求。

(1) 請求方法是以下三種方法之一:

HEADGETPOST

(2) HTTP的頭信息不超出以下幾種字段:

AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:只限于三個值 Application/x-www-form-urlencoded 、multipart/form-data、text/plain

凡是不同時滿足上面兩個條件,就屬于非簡單請求,瀏覽器對這兩種請求的處理,是不一樣的。

為什么會有兩種不同類型的請求呢?

CORS 規(guī)范要求,對那些可能對服務器數(shù)據(jù)產(chǎn)生副作用的 HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發(fā)起一個預檢請求(preflight request),從而獲知服務端是否允許該跨域請求。

服務器確認允許之后,瀏覽器才能發(fā)起實際的 HTTP 請求。在預檢請求的返回中,服務器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關(guān)的數(shù)據(jù))。

非簡單請求就要求瀏覽器先發(fā)送一個預檢請求,預檢通過后再發(fā)送實際的請求。

怎樣實現(xiàn)CORS

知道了CORS的實現(xiàn)機制之后,我們就可以解決遇到的CORS的問題了。

1.通過JSONP

利用 <script> 標簽沒有跨域限制的漏洞,網(wǎng)頁可以得到從其他來源動態(tài)產(chǎn)生的 JSON 數(shù)據(jù)。JSONP請求一定需要對方的服務器做支持才可以。

JSONP 和 AJAX 相同,都是客戶端向服務器端發(fā)送請求,從服務器端獲取數(shù)據(jù)的方式。但 AJAX 屬于同源策略,JSONP 屬于非同源策略(支持跨域請求)。JSONP優(yōu)點是簡單兼容性好,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題。缺點是僅支持 GET 方法具有局限性,不安全可能會遭受XSS攻擊。

2.利用反向代理服務器

同源策略是瀏覽器需要遵循的標準,而如果是服務器向服務器請求就無需遵循同源策略

所以通過反向代理服務器可以有效的解決跨域問題,代理服務器需要做以下幾個步驟:

1.接受客戶端的請求

2.將請求轉(zhuǎn)發(fā)給實際的服務器

3.將服務器的響應結(jié)果返回給客戶端

Nginx就是類似的反向代理服務器,可以通過配置Nginx代理來解決跨域問題。

3.服務端支持CORS

最安全的還是服務端來設置允許哪些來源的請求,即服務端在接收到請求之后,對允許的請求源設置 Access-Control-Allow-Origin 的響應頭。

通過@CrossOrigin注解

這里以 Spring Boot 為例,可以通過 @CrossOrigin 注解來指定哪些類或者方法支持跨越,如下列代碼所示:

/**
 * 在類上加注解
 */
@CrossOrigin({"http://127.0.0.1:9528", "http://localhost:9528"})
@RestController
public class UserController {
    
}
@RestController
public class UserController {
    @Resource
    private UserFacade userFacade;
    /**
     * 在方法上加注解
     */
    @GetMapping(ApiConstant.Urls.GET_USER_INFO)
    @CrossOrigin({"http://127.0.0.1:9528", "http://localhost:9528"})
    public PojoResult<UserDTO> getUserInfo() {
        return userFacade.getUserInfo();
    }
}

通過CorsRegistry設置全局跨域配置

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**")
            .allowedOrigins("http://127.0.0.1:9528", "http://localhost:9528");
	}
}

如果你使用的是 Spring Boot,推薦的做法是只定義一個 WebMvcConfigurer 的Bean:

@Configuration
public class MyConfiguration {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                    .allowedOrigins("http://127.0.0.1:9528", "http://localhost:9528");
            }
        };
    }
}

以上兩種方式在沒有定義攔截器(Interceptor)的時候,使用一切正常,但是如果你有一個全局的攔截器用來檢測用戶的登錄態(tài),例如下面的簡易代碼:

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        // 從 http 請求頭中取出 token
        String token = httpServletRequest.getHeader("token");
        // 檢查是否登錄
        if (token == null) {
            throw new InvalidTokenException(ResultCode.INVALID_TOKEN.getCode(), "登錄態(tài)失效,請重新登錄");
        }
        return true;
    }
}

當自定義攔截器返回true時,一切正常,但是當攔截器拋出異常(或者返回false)時,后續(xù)的CORS設置將不會生效。

為什么攔截器拋出異常時,CORS不生效呢?可以看下這個issue:

when interceptor preHandler throw exception, the cors is broken

有個人提交了一個issue,說明如果在自定義攔截器的preHandler方法中拋出異常的話,通過 CorsRegistry 設置的全局 CORS 配置就失效了,但是Spring Boot 的成員不認為這是一個Bug。

然后提交者舉了個具體的例子:

他先定義了CorsRegistry,并添加了一個自定義的攔截器,攔截器中拋出了異常

跨域問題是怎樣造成的?

 

然后他發(fā)現(xiàn)AbstractHandlerMapping在添加CorsInterceptor的時候,是將 Cors 的攔截器加在攔截器鏈的最后:

跨域問題是怎樣造成的?

 

那就會造成上面說的問題,在自定義攔截器中拋出異常之后,CorsInterceptor 攔截器就沒有機會執(zhí)行向 response 中設置 CORS 相關(guān)響應頭了。

issue的提交者也給出了解決的方案,就是將用來處理 Cors 的攔截器 CorsInterceptor 加在攔截器鏈的第一個位置:

跨域問題是怎樣造成的?

 

這樣的話請求來了之后,第一個就會為 response 設置相應的 CORS 響應頭,后續(xù)如果其他自定義攔截器拋出異常,也不會有影響了。

感覺是一個可行的解決方案,但是 Spring Boot 的成員認為這不是 Spring Boot 的Bug,而是 Spring Framework 的 Bug,所以將這個issue關(guān)閉了。

通過CorsFilter設置全局跨域配置

既然通過攔截器設置全局跨域配置會有問題,那我們還有另外一種方案,通過過濾器 CorsFilter 的方式來設置,代碼如下:

@Configuration
public class MyConfiguration {
	@Bean
	public FilterRegistrationBean corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		CorsConfiguration config = new CorsConfiguration();
		config.setAllowCredentials(true);
		config.addAllowedOrigin("http://127.0.0.1:9528");
        config.addAllowedOrigin("http://localhost:9528");
		config.addAllowedHeader("*");
		config.addAllowedMethod("*");
		source.registerCorsConfiguration("/**", config);
		FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
		bean.setOrder(0);
		return bean;
	}
}

為什么過濾器可以而攔截器不行呢?

因為過濾器依賴于 Servlet 容器,基于函數(shù)回調(diào),它可以對幾乎所有請求進行過濾。而攔截器是依賴于 Web 框架(如Spring MVC框架),基于反射通過AOP的方式實現(xiàn)的。

在觸發(fā)順序上如下圖所示:

跨域問題是怎樣造成的?

 

因為過濾器在觸發(fā)上是先于攔截器的,但是如果有多個過濾器的話,也需要將 CorsFilter 設置為第一個過濾器才行。

原文:https://my.oschina.net/u/3216837/blog/3196454

分享到:
標簽:跨域
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定