背景
Spring Cloud 微服務試點改造,目前在嘗試前后端分離。
前台A應用(本機8080端口),通過網管(本機8769端口)調用后台應用B(本機8082端口)、應用C發布的http服務。。
A的js代碼如下:
$.ajax({ type: "POST", async: "true", url: "http://127.0.0.1:8769/service-B/getResInfo", data:{resTypeId:201} dataType:"json",
xhrFields: {
withCredentials: true
},
error: function (XMLHttpRequest, textStatus, errorThrown) { alert(textStatus + "," + errorThrown); }, beforeSend: function (XMLHttpRequest) { }, success: function (data) { ... } });
運行后報錯:
XMLHttpRequest cannot load http://127.0.0.1:8769/service-B/getResInfo. No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:8080' is therefore not allowed access.
問題原因
A的前台訪問B應用,導致了跨域。
跨域訪問違反了同源策略,同源策略規定:瀏覽器的ajax只能訪問跟它的前台頁面同源(相同域名或IP)的資源。
也就是說,如果A的前台訪問A的后台,則不會跨域。。
解決方案
嘗試了兩種解決辦法,最終采用了方案二。。
方案一:
在被調用的類或方法上增加@CrossOrigin注解來聲明自己支持跨域訪問
origins=*表示允許所有來源都支持,也可以定義特定的來源,比如http://domain1.com
allowCredentials=true 表示response里會增加標示Access-Control-Allow-Credentials=true
@RequestMapping(value="/getResInfo",method = {RequestMethod.POST}) @CrossOrigin(allowCredentials="true", allowedHeaders="*", methods={RequestMethod.POST}, origins="*") public List<Map<String,Object>> getResInfo(@RequestParam(name = "resTypeId", required = false) String resTypeId){ 。。。 }
如果只是針對某個服務需要被跨域訪問,用此方案可行。。
但由於我們進行了前后端分離,前台調用的都是跨域的服務,此方案需要對幾乎所有的B、C應用的服務對應的方法或者類上增加注解,不太合適。
而且,如果B、C服務都開放了跨域訪問,則可能存在安全隱患,因為其他未知應用也可以訪問這些服務。。
方案二:
在網管zuul里增加CorsFilter過濾器,比如下圖直接在啟動類里增加紅色部分代碼
@SpringBootApplication @EnableZuulProxy public class DemoZuulApplication { public static void main(String[] args) { SpringApplication.run(DemoZuulApplication.class, args); } @Bean public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); final CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 允許cookies跨域 config.addAllowedOrigin("*");// 允許向該服務器提交請求的URI,*表示全部允許。。這里盡量限制來源域,比如http://xxxx:8080 ,以降低安全風險。。 config.addAllowedHeader("*");// 允許訪問的頭信息,*表示全部 config.setMaxAge(18000L);// 預檢請求的緩存時間(秒),即在這個時間段里,對於相同的跨域請求不會再預檢了 config.addAllowedMethod("*");// 允許提交請求的方法,*表示全部允許,也可以單獨設置GET、PUT等 /* config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET");// 允許Get的請求方法 config.addAllowedMethod("PUT"); config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE"); config.addAllowedMethod("PATCH");*/ source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }
后來zuul上又增加了其他的過濾器,而此跨域過濾器必須在其他過濾器之前,所以寫法改成如下的方式,即借助FilterRegistrationBean將此過濾器的order設置為0,即最先加載的過濾器。。order值越大加載越晚。。
@Bean public FilterRegistrationBean corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); final CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 允許cookies跨域 config.addAllowedOrigin("*");// #允許向該服務器提交請求的URI,*表示全部允許,在SpringMVC中,如果設成*,會自動轉成當前請求頭中的Origin config.addAllowedHeader("*");// #允許訪問的頭信息,*表示全部 config.setMaxAge(1800L);// 預檢請求的緩存時間(秒),即在這個時間段里,對於相同的跨域請求不會再預檢了 config.addAllowedMethod("*");// 允許提交請求的方法,*表示全部允許 /* config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET");// 允許Get的請求方法 config.addAllowedMethod("PUT"); config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE"); config.addAllowedMethod("PATCH");*/ source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new org.springframework.web.filter.CorsFilter(source)); bean.setOrder(0); return bean; }
由於此方案是增加到網管上的,對B、C應用的代碼都無任何改造。。
且因為B、C未直接開放跨域訪問,所以其他應用無法跨越訪問B、C服務,比如A不經過網關直接訪問B、C應用會訪問失敗。。
后續會對網管應用裝配上SSO進行單點登錄校驗,來更好的保障服務安全。。
'Access-Control-Allow-Origin' header contains multiple values的報錯的解決方案
今天遇到一個跨域的報錯:f12谷歌瀏覽器發現:
Failed to load http://127.0.0.1:8769/device-service/routeService/queryViewPathString: The 'Access-Control-Allow-Origin' header contains multiple values 'http://127.0.0.1:8080, http://127.0.0.1:8080', but only one is allowed. Origin 'http://127.0.0.1:8080' is therefore not allowed access.
這個問題是因為重復配置了跨域過濾器。。比如zuul里已經配置了CorsConfiguration,在業務應用里又配置了一次CorsConfiguration。。則會出現此問題。。
解決方案:去掉業務應用里的配置,統一在網關進行配置即可。
前端ajax跨域訪問后端時,如果需要cookie一並發送,則需要做增加withCredentials參數。。否則會引起后台獲取的session每次都變化的問題。
xhrFields: {
withCredentials: true
},
本文參考:http://blog.csdn.net/liuchuanhong1/article/details/62237705