關(guān)于SpringBoot中的跨域問(wèn)題
一、同源策略
同源策略是由Netscape提出的一個(gè)著名的安全策略,它是瀏覽器最核心也最基本的安全功能,現(xiàn)在所有支持JavaScript的瀏覽器都會(huì)使用這個(gè)策略。
所謂同源是指 協(xié)議 、 域名 以及 端口 要相同。
同源策略是基于安全方面的考慮提出來(lái)的,這個(gè)策略本身沒(méi)問(wèn)題,但是我們?cè)趯?shí)際開(kāi)發(fā)中,由于各種原因又經(jīng)常有跨域的需求,傳統(tǒng)的跨域方案是JSONP,JSONP雖然能解決跨域但是有一個(gè)很大的局限性,那就是只支持GET請(qǐng)求,不支持其他類型的請(qǐng)求
而今天我們說(shuō)的CORS(跨域源資源共享)(CORS,Cross-origin resource sharing)是一個(gè)W3C標(biāo)準(zhǔn),它是一份瀏覽器技術(shù)的規(guī)范,提供了Web服務(wù)從不同網(wǎng)域傳來(lái)沙盒腳本的方法,以避開(kāi)瀏覽器的同源策略,這是JSONP模式的現(xiàn)代版。
在Spring框架中,對(duì)于CORS也提供了相應(yīng)的解決方案。
非同源限制
- 無(wú)法讀取非同源網(wǎng)頁(yè)的 Cookie、LocalStorage 和 IndexedDB
- 無(wú)法接觸非同源網(wǎng)頁(yè)的 DOM
- 無(wú)法向非同源地址發(fā)送 AJAX 請(qǐng)求
二、java后端實(shí)現(xiàn)CORS跨域請(qǐng)求的方式
- 返回新的CorsFilter
- 重寫(xiě) WebMvcConfigurer
- 使用注解 @CrossOrigin
- 手動(dòng)設(shè)置響應(yīng)頭 (HttpServletResponse)
- 自定web filter 實(shí)現(xiàn)跨域
注意:
- CorFilter / WebMvConfigurer / @CrossOrigin 需要 SpringMVC 4.2以上版本才支持,對(duì)應(yīng)springBoot 1.3版本以上
- 上面前兩種方式屬于全局 CORS 配置,后兩種屬于局部 CORS配置。如果使用了局部跨域是會(huì)覆蓋全局跨域的規(guī)則,所以可以通過(guò) @CrossOrigin 注解來(lái)進(jìn)行細(xì)粒度更高的跨域資源控制。
- 其實(shí)無(wú)論哪種方案,最終目的都是修改響應(yīng)頭,向響應(yīng)頭中添加瀏覽器所要求的數(shù)據(jù),進(jìn)而實(shí)現(xiàn)跨域
1.返回新的 CorsFilter(全局跨域)
在任意配置類,返回一個(gè) 新的 CorsFIlter Bean ,并添加映射路徑和具體的CORS配置路徑。
@Configuration public class GlobalCorsConfig { @Bean public CorsFilter corsFilter() { //1. 添加 CORS配置信息 CorsConfiguration config = new CorsConfiguration(); //放行哪些原始域 config.addAllowedOrigin("*"); //是否發(fā)送 Cookie config.setAllowCredentials(true); //放行哪些請(qǐng)求方式 config.addAllowedMethod("*"); //放行哪些原始請(qǐng)求頭部信息 config.addAllowedHeader("*"); //暴露哪些頭部信息 config.addExposedHeader("*"); //2. 添加映射路徑 UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource(); corsConfigurationSource.registerCorsConfiguration("/**",config); //3. 返回新的CorsFilter return new CorsFilter(corsConfigurationSource); } }
2. 重寫(xiě) WebMvcConfigurer(全局跨域)
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") //是否發(fā)送Cookie .allowCredentials(true) //放行哪些原始域 .allowedOrigins("*") .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"}) .allowedHeaders("*") .exposedHeaders("*"); } }
3. 使用注解 (局部跨域)
在控制器(類上)上使用注解 @CrossOrigin :,表示該類的所有方法允許跨域。
@RestController @CrossOrigin(origins = "*") public class HelloController { @RequestMapping("/hello") public String hello() { return "hello world"; } }
在方法上使用注解 @CrossOrigin:
@RequestMapping("/hello") @CrossOrigin(origins = "*") //@CrossOrigin(value = "http://localhost:8081") //指定具體ip允許跨域 public String hello() { return "hello world"; }
4. 手動(dòng)設(shè)置響應(yīng)頭(局部跨域)
使用 HttpServletResponse 對(duì)象添加響應(yīng)頭(Access-Control-Allow-Origin)來(lái)授權(quán)原始域,這里 Origin的值也可以設(shè)置為 “*”,表示全部放行。
@RequestMapping("/index") public String index(HttpServletResponse response) { response.addHeader("Access-Allow-Control-Origin","*"); return "index"; }
5. 使用自定義filter實(shí)現(xiàn)跨域
首先編寫(xiě)一個(gè)過(guò)濾器,可以起名字為MyCorsFilter.java
@Component public class MyCorsFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "x-requested-with,content-type"); chain.doFilter(req, res); } public void init(FilterConfig filterConfig) {} public void destroy() {} }
在web.xml中配置這個(gè)過(guò)濾器,使其生效
<!-- 跨域訪問(wèn) START--> <filter> <filter-name>CorsFilter</filter-name> <filter-class>com.mesnac.aop.MyCorsFilter</filter-class> </filter> <filter-mapping> <filter-name>CorsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 跨域訪問(wèn) END -->
三、例子
首先創(chuàng)建兩個(gè)普通的SpringBoot項(xiàng)目,第一個(gè)命名為provider提供服務(wù),第二個(gè)命名為consumer消費(fèi)服務(wù),第一個(gè)配置端口為8080,第二個(gè)配置配置為8081,然后在provider上提供兩個(gè)hello接口,一個(gè)get,一個(gè)post,如下:
@RestController public class Provider { @GetMapping("/hello") public String hello() { return "hello"; } @PostMapping("/hello") public String hello2() { return "post hello"; } }
在consumer的resources/templates目錄下創(chuàng)建一個(gè)html文件,發(fā)送一個(gè)簡(jiǎn)單的ajax請(qǐng)求,如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CORS</title> </head> <body> <div id="app"></div> <input type="button" onclick="btnClick()" value="get_button"> <input type="button" onclick="btnClick2()" value="post_button"> <script> function btnClick() { $.get('http://localhost:8080/hello', function (msg) { $("#app").html(msg); }); } function btnClick2() { $.post('http://localhost:8080/hello', function (msg) { $("#app").html(msg); }); } </script> </body> </html>
然后分別啟動(dòng)兩個(gè)項(xiàng)目,發(fā)送請(qǐng)求按鈕,觀察瀏覽器控制臺(tái)如下:
Access to XMLHttpRequest at ‘//localhost:8080/hello’ from origin ‘//localhost:8081’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource
可以看到,由于同源策略的限制,請(qǐng)求無(wú)法發(fā)送成功。
使用CORS可以在前端代碼不做任何修改的情況下,實(shí)現(xiàn)跨域,那么接下來(lái)看看在provider中如何配置。首先可以通過(guò)@CrossOrigin注解配置某一個(gè)方法接受某一個(gè)域的請(qǐng)求,如下:
@RestController public class Provider { @CrossOrigin(value = "http://localhost:8081") @GetMapping("/hello") public String hello() { return "hello"; } @CrossOrigin(value = "http://localhost:8081") @PostMapping("/hello") public String hello2() { return "post hello"; } }
這個(gè)注解表示這兩個(gè)接口接受來(lái)自//localhost:8081地址的請(qǐng)求,配置完成后,重啟provider,再次發(fā)送請(qǐng)求,瀏覽器控制臺(tái)就不會(huì)報(bào)錯(cuò)了,consumer也能拿到數(shù)據(jù)了。
provider上,每一個(gè)方法上都去加注解未免太麻煩了,在Spring Boot中,還可以通過(guò)全局配置一次性解決這個(gè)問(wèn)題,全局配置只需要在配置類中重寫(xiě)addCorsMappings方法即可,如下:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:8081") .allowedMethods("*") .allowedHeaders("*"); } }
/**表示本應(yīng)用的所有方法都會(huì)去處理跨域請(qǐng)求,allowedMethods表示允許通過(guò)的請(qǐng)求數(shù),allowedHeaders則表示允許的請(qǐng)求頭。經(jīng)過(guò)這樣的配置之后,就不必在每個(gè)方法上單獨(dú)配置跨域了。
四、存在的問(wèn)題
了解了整個(gè)CORS的工作過(guò)程之后,我們通過(guò)Ajax發(fā)送跨域請(qǐng)求,雖然用戶體驗(yàn)提高了,但是也有潛在的威脅存在,常見(jiàn)的就是CSRF(Cross-site request forgery)跨站請(qǐng)求偽造。跨站請(qǐng)求偽造也被稱為one-click attack 或者 session riding,通常縮寫(xiě)為CSRF或者XSRF,是一種挾制用戶在當(dāng)前已登錄的Web應(yīng)用程序上執(zhí)行非本意的操作的攻擊方法,舉個(gè)例子:
假如一家銀行用以運(yùn)行轉(zhuǎn)賬操作的URL地址如下://icbc.com/aa?bb=cc,那么,一個(gè)惡意攻擊者可以在另一個(gè)網(wǎng)站上放置如下代碼:<img src="//icbc.com/aa?bb=cc">,如果用戶訪問(wèn)了惡意站點(diǎn),而她之前剛訪問(wèn)過(guò)銀行不久,登錄信息尚未過(guò)期,那么她就會(huì)遭受損失。
基于此,瀏覽器在實(shí)際操作中,會(huì)對(duì)請(qǐng)求進(jìn)行分類,分為簡(jiǎn)單請(qǐng)求,預(yù)先請(qǐng)求,帶憑證的請(qǐng)求等,預(yù)先請(qǐng)求會(huì)首先發(fā)送一個(gè)options探測(cè)請(qǐng)求,和瀏覽器進(jìn)行協(xié)商是否接受請(qǐng)求。默認(rèn)情況下跨域請(qǐng)求是不需要憑證的,但是服務(wù)端可以配置要求客戶端提供憑證,這樣就可以有效避免csrf攻擊。
到此這篇關(guān)于關(guān)于SpringBoot中的跨域問(wèn)題的文章就介紹到這了,更多相關(guān)SpringBoot 跨域內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot3.0整合mybatis-flex實(shí)現(xiàn)逆向工程的示例代碼
逆向工程先創(chuàng)建數(shù)據(jù)庫(kù)表,由框架負(fù)責(zé)根據(jù)數(shù)據(jù)庫(kù)表,自動(dòng)生成mybatis所要執(zhí)行的代碼,本文就來(lái)介紹一下springboot mybatis-flex逆向工程,感興趣的可以了解一下2024-06-06Mybatis關(guān)聯(lián)查詢結(jié)果集對(duì)象嵌套的具體使用
在查詢時(shí)經(jīng)常出現(xiàn)一對(duì)多”的關(guān)系,所有會(huì)出現(xiàn)嵌套對(duì)象的情況,本文主要介紹了Mybatis關(guān)聯(lián)查詢結(jié)果集對(duì)象嵌套的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02淺談Spring Boot 2.0遷移指南主要注意點(diǎn)
Spring官方的Spring Boot 2變動(dòng)指南,主要是幫助您將應(yīng)用程序遷移到Spring Boot 2.0,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10SpringBoot從2.7.x 升級(jí)到3.3注意事項(xiàng)
從SpringBoot 2.7.x升級(jí)到3.3涉及多個(gè)重要變更,特別是因?yàn)?nbsp;Spring Boot 3.x 系列基于 Jakarta EE 9,而不再使用 Java EE,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2024-09-09Java forEach對(duì)原數(shù)組的操作過(guò)程
forEach對(duì)于基本數(shù)據(jù)類型,是直接賦值,對(duì)于引用數(shù)據(jù)類型,是引用地址值,forEach遍歷時(shí),是創(chuàng)建的臨時(shí)變量,引用的數(shù)據(jù)地址,本文給大家介紹Java forEach對(duì)原數(shù)組的操作過(guò)程,感興趣的朋友一起看看吧2024-02-02