日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

cors 前后端分离跨域问题_前后端分离之CORS跨域访问踩坑总结

發(fā)布時間:2023/12/10 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 cors 前后端分离跨域问题_前后端分离之CORS跨域访问踩坑总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

前后端分離的開發(fā)模式越來越流行,目前絕大多數的公司與項目都采取這種方式來開發(fā),它的好處是前端可以只專注于頁面實現,而后端則主要負責接口開發(fā),前后端分工明確,彼此職責分離,不再高度耦合,但是由于這種開發(fā)模式將前后端項目分開來獨立部署,所以將必不可免的會碰到跨域問題.

什么是跨域

跨域指的是瀏覽器不能執(zhí)行其他網站的腳本.它是由瀏覽器的同源策略造成的,是瀏覽器對javascript施加的安全限制.目前所有的瀏覽器都實行同源策略,所謂的同源指的是

協(xié)議相同

域名相同

端口相同

如何解決

常見的解決方案有JSONP和CORS等多種方式,這里我們采用了CORS的方式來統(tǒng)一處理項目中的跨域問題.

cors是一種w3c標準,全稱是"跨域資源共享(Cross-origin resource sharing)".它允許瀏覽器向跨源服務器發(fā)出XMLHttpRequest請求,從而克服了同源使用的限制.下面重點來看下我們在SpringBoot項目中使用cors處理跨域時所遇到的問題.

首先創(chuàng)建一個WebMvcConfig類繼承自WebMvcConfigurerAdapter類并覆寫其中的addCorsMappings方法

/**

* 允許跨域訪問

*

* @param registry

*/

@Override

public void addCorsMappings(CorsRegistry registry) {

registry.addMapping("/**").allowedOrigins("*")

.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")

.allowedHeaders("Accept", "Origin", "X-Requested-With", "Content-Type",

"Last-Modified", "device", "token")

.exposedHeaders("Set-Cookie")

.allowCredentials(true).maxAge(3600);

}

接著我們在之前搭建好的Vue項目環(huán)境中創(chuàng)建一個vue文件,用來向springboot項目發(fā)起跨域請求.

Message is: {{ message }}

export default {

data () {

return {

message: 'This is my from'

}

},

methods:{

get:function(){

$.ajax({

url:'http://localhost:8080/win/api/test/cors',//測試接口

type:'post',

beforeSend:(xhr) => {

xhr.setRequestHeader("token", "web_session_key-082ba2a3-7d8e-407c-8bd0-2fbc430b0dbf");

xhr.setRequestHeader("device","APP");

},

success:function(data){

console.log(data);

}

});

}

}

}

然后啟動前端vue項目

npm run dev

打開瀏覽器輸入http://localhost:8081/點擊頁面中的測試按鈕發(fā)起接口調用,注意vue工程的端口為8081,springboot工程的端口為8080,不符合同源規(guī)則所以訪問后端接口時會出現跨域.此時打開chrome控制臺會發(fā)現

image.png

通過chrome控制臺查看接口請求的headers信息是這樣的

image.png

可以看到這個接口的Request Method為OPTIONS,而不是我們在ajax代碼中所設置的POST,仔細看控制臺報的異常Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.可以發(fā)現其中的關鍵字preflight(預檢請求),這個其實就是問題的所在了,再次google了一下cors的相關知識,才知道原來瀏覽器將cors請求分為兩類即簡單請求和非簡單請求.

簡單請求

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

請求方法是以下三種方法之一:GET POST HEAD

Http的頭信息不超出以下幾種字段:Accept Accept-Language Content-Language Last-Event-ID Content-Type 只限于三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

非簡單請求

其他的請求皆屬于非簡單請求.

注意在cors定義中,如果頭信息中的Content-Type不設置,則默認值為json/application,如果Content-Type值不為application/x-www-form-urlencoded,multipart/form-data或text/plain,都被視為非簡單請求,即預檢請求.

主要問題描述

瀏覽器對這兩種請求的處理是不一樣的.

如果是簡單請求的話,一次完整的請求過程是不需要服務端預檢的,直接響應回客戶端,而非簡單請求則瀏覽器會在發(fā)送真正請求之前先用OPTIONS發(fā)送一次預檢請求preflight request,從而獲知服務端是否允許該跨域請求,當服務器確認允許之后,才會發(fā)起真正的請求.那么前面所出現的異常很明顯就是由這個preflight request導致的了,回頭看代碼可發(fā)現我們在發(fā)起ajax調用時往請求頭里面塞了兩個自定義的header參數token和device,所以此次調用屬于非簡單請求,并觸發(fā)了一次預檢請求,由于我們服務端使用了shiro權限認證框架,通過攔截用戶請求頭中傳過來的token信息來判定客戶端是否為非法調用,而Preflight請求過程中并不會攜帶我們自定義的token信息到服務器,這樣服務器校驗就永遠也無法通過了,就算是合法的登錄用戶也會被攔截.

解決的辦法

既然已經知道原因了,那么自然解決這個問題的辦法就是交給后端了,在后端檢測到該請求為預檢請求時,不讓它繼續(xù)往下走(也可以返回一個2xx的狀態(tài)碼),直接告訴瀏覽器此次跨域請求可以繼續(xù),很明顯過濾器符合我們的要求,我們來把之前的addCorsMappings方法去掉,重寫這塊代碼,在網上查了很久,嘗試了很多種方法,得出來的結論大致有如下兩種方式:

第一種方案采用過濾器的機制

@Bean

public FilterRegistrationBean corsFilter() {

return new FilterRegistrationBean(new Filter() {

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

String method = request.getMethod();

// this origin value could just as easily have come from a database

response.setHeader("Access-Control-Allow-Origin", "*");

response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, OPTIONS");

response.setHeader("Access-Control-Max-Age", "3600");

response.setHeader("Access-Control-Allow-Credentials", "true");

response.setHeader("Access-Control-Allow-Headers", "Accept, Origin, X-Requested-With, Content-Type,Last-Modified,device,token");

if ("OPTIONS".equals(method)) {//檢測是options方法則直接返回200

response.setStatus(HttpStatus.OK.value());

} else {

chain.doFilter(req, res);

}

}

public void init(FilterConfig filterConfig) {

}

public void destroy() {

}

});

}

第二種方案

1.創(chuàng)建一個類MyCorsRegistration繼承自CorsRegistration

public class MyCorsRegistration extends CorsRegistration {

public MyCorsRegistration(String pathPattern) {

super(pathPattern);

}

@Override

public CorsConfiguration getCorsConfiguration() {

return super.getCorsConfiguration();

}

}

2.然后在WebMvcConfig類中增加一個方法里來處理跨域問題.

@Bean

public FilterRegistrationBean filterRegistrationBean() {

// 對響應頭進行CORS授權

MyCorsRegistration corsRegistration = new MyCorsRegistration("/**");

corsRegistration.allowedOrigins("*")

.allowedMethods(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name(),

HttpMethod.PUT.name(), HttpMethod.OPTIONS.name())

.allowedHeaders("Accept", "Origin", "X-Requested-With", "Content-Type",

"Last-Modified", "device", "token")

.exposedHeaders(HttpHeaders.SET_COOKIE)

.allowCredentials(true)

.maxAge(3600);

// 注冊CORS過濾器

UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();

configurationSource.registerCorsConfiguration("/**", corsRegistration.getCorsConfiguration());

CorsFilter corsFilter = new CorsFilter(configurationSource);

return new FilterRegistrationBean(corsFilter);

}

其實第二種方案本質上也是過濾器的機制,看源碼可知CorsFilter繼承自spring的過濾器OncePerRequestFilter,來看看它的doFilterInternal方法

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

if (CorsUtils.isCorsRequest(request)) {

CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);

if (corsConfiguration != null) {

boolean isValid = this.processor.processRequest(corsConfiguration, request, response);

if (!isValid || CorsUtils.isPreFlightRequest(request)) {

return;

}

}

}

filterChain.doFilter(request, response);

}

代碼大致意思是先判斷是否為cors請求再判斷是否預檢請求,符合條件則直接return了.

我們來看看最后的請求結果,控制臺再也沒有報錯了,瀏覽器一共發(fā)送了兩次接口請求,第一次是OPTIONS請求,第二次是POST請求.

OPTIONS請求:

image.png

POST請求:

image.png

從上面第一個圖中可以看到這個預檢請求主要攜帶的請求header信息如下(并沒有攜帶我們自定義的header信息,第二個圖中可以清楚的看到我們自定義的header信息了):

Access-Control-Request-Headers:device,token 真實請求攜帶的Header中的信息

Access-Control-Request-Method:POST 我真實請求的方法是什么

Origin:http://localhost:8081 告訴服務器我的域名

然后是服務器端給我們返回的Preflight Response:

Access-Control-Allow-Credentials:true

Access-Control-Allow-Headers:Accept, Origin, X-Requested-With, Content-Type,Last-Modified,device,token

Access-Control-Allow-Methods:GET, HEAD, POST, PUT, DELETE, OPTIONS

Access-Control-Allow-Origin:*

Access-Control-Max-Age:3600

值得注意的是上面的Access-Control-Max-Age這個header,它告訴了服務器在多少秒內,不需要再發(fā)送預檢請求,可以緩存該結果,即只發(fā)送真正的請求,不同瀏覽器有不同的上限.在Firefox中,上限是24h(即86400秒),而在Chrome中則是10min(即600秒).Chrome同時規(guī)定了一個默認值5秒.如果值為-1,則表示禁用緩存,每一次請求都需要提供預檢請求,即用OPTIONS請求進行檢測.它對完全一樣的url的緩存設置生效,多一個參數也視為不同url.也就是說,如果設置了10分鐘的緩存,在10分鐘內,所有請求第一次會產生options請求,第二次以及第二次以后就只發(fā)送真正的請求了,這也是一個很有效的性能優(yōu)化手段(另外注意下在chrome瀏覽器中如果開啟了Disable cache,則表示本地不緩存,會導致每次請求都發(fā)一次預檢測).

總結

以上是生活随笔為你收集整理的cors 前后端分离跨域问题_前后端分离之CORS跨域访问踩坑总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。