Spring Boot先後端分離後,跨域問題怎麼解決?

如今基於spring boot先後端分離的開發模式愈來愈廣泛,那麼,因爲先後端分離引起的跨域問題,你知道怎麼解決嗎?javascript

什麼是跨域

跨域是指 不一樣域名之間相互訪問。即瀏覽器控制當前網頁下不能執行其餘網站的腳本,這是由瀏覽器的同源策略形成的,是瀏覽器對JavaScript施加的安全限制。
也就是若是在A網站中,咱們但願使用Ajax來得到B網站中的特定內容
若是A網站與B網站不在同一個域中,那麼就出現了跨域訪問問題。css

跨域的安全限制都是對瀏覽器端來講的,服務器端是不存在跨域安全限制的。html

同源策略
同源策略/SOP(Same origin policy)是一種約定,由Netscape公司1995年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能,若是缺乏了同源策略,瀏覽器很容易受到XSS、CSFR等攻擊。所謂同源是指"協議+域名+端口"三者相同,即使兩個不一樣的域名指向同一個ip地址,也非同源。
前端發起的請求只要不符合同源策略就會出現跨域問題。前端

案例分析java

URL 說明 是否容許通訊
http://www.a.com/a.jshttp://www.a.com/b.js 同一域名下 容許
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不一樣文件夾 容許
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不一樣端口 不容許
http://www.a.com/a.js https://www.a.com/b.js 同一域名,不一樣協議 不容許
http://www.a.com/a.js http://70.32.92.74/b.js 域名和域名對應ip 不容許
http://www.a.com/a.js http://script.a.com/b.js 主域相同,子域不一樣 不容許
http://www.a.com/a.js http://a.com/b.js 同一域名,不一樣二級域名(同上) 不容許(cookie這種狀況下也不容許訪問)
http://www.cnblogs.com/a.js
http://www.a.com/b.js
不一樣域名 不容許

爲何先後端分離後會致使跨域問題?

先後端分離後,前端代碼和後端代碼都是獨立部署的,通常前端採用Nginx做爲web服務器部署,後端spring boot因爲內置了tomcat,通常都是經過jar包直接啓動。
假設先後端部署在同一臺服務器上,那麼2者訪問的端口一定不一致,不符合同源策略,因此出現跨域問題。
若是先後端部署在不一樣服務器上,那麼訪問的ip或者域名必然不一致,也會出現跨域問題。jquery

經過jsonp實現跨域

jsonp 全稱是JSON with Padding,是爲了解決跨域請求資源而產生的解決方案,是一種依靠開發人員創造出的一種非官方跨域數據交互協議。
一個是描述信息的格式,一個是信息傳遞雙方約定的方法。nginx


前端json請求示例:web

<!DOCTYPE html>
<html>
<head>
<title>測試跨域訪問</title>
<meta charset="utf-8" />
</head>
<body>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function({
            $.ajax({
                type : "get",
                async : true,
                jsonp : "callbackName",// 後端接口參數名
                jsonpCallback : "callbackFunction"// 回調函數名
                url : "http://A/hello/map/getUser.json",
                dataType : "jsonp"// 數據格式爲 jsonp
                success : function(data{
                    console.log("success");
                }
            });
        });
    
</script>
    <script type="text/javascript">
        var callbackFunction = function(data{
            alert('接口返回的數據是:' + JSON.stringify(data));
        };
    
</script>
</body>
</html>

後端jsonp代碼參考:ajax

/**
 * 
 * The class JsonBackController.
 *
 * Description:該控制器返回一串簡單的json數據,json數據由一個簡單的User對象組成
 *
 * @author: huangjiawei
 * @since: 2018年6月12日
 * @version: $Revision$ $Date$ $LastChangedBy$
 *
 */

@RestController
@RequestMapping(value = "/map")
public class JsonBackController {
    private static final Logger logger = LoggerFactory.getLogger(JsonBackController.class);
    /**
     * 解決跨域請求數據
     * @param response
     * @param callbackName 前端回調函數名
     * @return
     */

    @RequestMapping(value = "getUser.json")
    public void getUser(HttpServletResponse response, @RequestParam String callbackName) {
        User user = new User("huangjiawei"22);
        response.setContentType("text/javascript");
        Writer writer = null;
        try {
            writer = response.getWriter();
            writer.write(callbackName + "(");
            writer.write(user.toString());
            writer.write(");");
        } catch (IOException e) {
            logger.error("jsonp響應寫入失敗! 數據:" + user.toString(), e);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    logger.error("輸出流關閉異常!", e);
                }
                writer = null;
            }
        }
    }
}

因爲JSONP的原理其實就是欺騙瀏覽器,以<script></script>方式調用接口,解析數據。因此,jsonp的請求方式只能是get方式,若是接口只接受post請求方式,則再作考慮。
這種方式代碼侵入強,請求方式也有限制,如今基本再也不使用。spring

Spring Boot對JSONP的支持
4.1版本之後的SpringMVC中,爲咱們提供了一個AbstractJsonpResponseBodyAdvice的類用來支持jsonp的數據(SpringBoot接收解析web請求是依賴於SpringMVC實現的)
使用AbstractJsonpResponseBodyAdvice來支持跨域請求很簡單,只須要繼承這個類就能夠了。具體代碼以下:

全局jsonp請求配置:

package com.laowan.jsonp.config;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;

/**
 * Created by wb-zhangkenan on 2016/12/1.
 */

@ControllerAdvice(basePackages = "com.laowan.jsonp.controller")
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice{
    public JsonpAdvice() {
        super("callback","jsonp");
    }
}

測試接口:

package com.laowan.jsonp.controller;

import com.laowan.jsonp.domain.PersonDomain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by wb-zhangkenan on 2016/12/1.
 */

@RestController
@RequestMapping("/jsonp")
public class JsonpTestController {
    @Autowired
    private PersonDomain personDomain;

    @RequestMapping(value = "/testJsonp",produces = MediaType.APPLICATION_JSON_VALUE)
    public PersonDomain testJsonp(){
        return personDomain;
    }
}

當發送的請求爲:http://localhost:8003/jsonp/testJsonp?callback=callback的時候,返回的數據就是jsonp的;
當咱們請求參數中不帶callback的時候:http://localhost:8003/jsonp/testJsonp,返回的數據是json的。

利用Nginx解決跨域

經過反向代理服務器監聽同端口,同域名的訪問,不一樣路徑映射到不一樣的地址,好比,在nginx服務器中,監聽同一個域名和端口,不一樣路徑轉發到客戶端和服務器,把不一樣端口和域名的限制經過反向代理,來解決跨域的問題。

方式一:經過Nginx反向代理,將跨域請求轉變爲非跨域請求,不一樣請求路徑代理到不一樣的地址:

server {
        listen       80;
        server_name  abc.com;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;

        location /client { #訪問客戶端路徑
            proxy_pass http://localhost:81;
            proxy_redirect default;
        }
        location /apis { #訪問服務器路徑
            rewrite  ^/apis/(.*)$ /$1 break;
            proxy_pass   http://localhost:82;
       }
}

方式二:經過Nginx在請求頭中添加CORS參數解決跨域

location / {
   add_header Access-Control-Allow-Origin *;
   add_header Access-Control-Allow-Headers X-Requested-With;
   add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;

   if ($request_method = 'OPTIONS') {
     return 204;
   }
}

利用CORS解決跨域問題

CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。

它容許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。
CORS須要瀏覽器和服務器同時支持。目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10

整個CORS通訊過程,都是瀏覽器自動完成,不須要用戶參與。對於開發者來講,CORS通訊與同源的AJAX通訊沒有差異,代碼徹底同樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感受。

所以,實現CORS通訊的關鍵是服務器。只要服務器實現了CORS接口,就能夠跨源通訊。

CORS與JSONP的使用目的相同,可是比JSONP更強大。
JSONP只支持GET請求,CORS支持全部類型的HTTP請求。JSONP的優點在於支持老式瀏覽器,以及能夠向不支持CORS的網站請求數據。

CORS核心參數介紹。

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

(1)Access-Control-Allow-Methods
該字段必需,它的值是逗號分隔的一個字符串,代表服務器支持的全部跨域請求的方法。注意,返回的是全部支持的方法,而不單是瀏覽器請求的那個方法。這是爲了不屢次"預檢"請求。

(2)Access-Control-Allow-Headers
若是瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。

(3)Access-Control-Allow-Credentials
該字段與簡單請求時的含義相同。

(4)Access-Control-Max-Age
該字段可選,用來指定本次預檢請求的有效期,單位爲秒。上面結果中,有效期是20天(1728000秒),即容許緩存該條迴應1728000秒(即20天),在此期間,不用發出另外一條預檢請求。

更多原理能夠參看:阮一峯   寫的跨域資源共享 CORS 詳解
http://www.ruanyifeng.com/blog/2016/04/cors.html

spring boot中使用CORS

方式一:在須要指出跨域的Controller類或方法上添加@CrossOrigin註解

@CrossOrigin // 註解方式  
@RestController  
public class HandlerScanController {  
    @CrossOrigin(allowCredentials="true", allowedHeaders="*", methods={RequestMethod.GET,  
            RequestMethod.POST, RequestMethod.DELETE, RequestMethod.OPTIONS,  
            RequestMethod.HEAD, RequestMethod.PUT, RequestMethod.PATCH}, origins="*")  
    @PostMapping("/confirm")  
    public Response handler(@RequestBody Request json){  
        return null;  
    }  
}  

方式二:配置CorsFilter,能夠全局生效  (推薦)

/**
 * 實現基本的跨域請求
 * @author linhongcun
 */

@Configuration
public class CorsConfig {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*"); // 容許任何域名使用
        corsConfiguration.addAllowedHeader("*"); // 容許任何頭
        corsConfiguration.addAllowedMethod("*"); // 容許任何方法(post、get等)
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig()); // 對接口配置跨域設置
        return new CorsFilter(source);
    }
}

固然,若是微服務多的話,須要在每一個服務的主類上都加上這麼段代碼,這違反了DRY原則,更好的作法是在zuul的網關層解決跨域問題,一勞永逸。

方式三:直接對WebMvcConfigurer進行配置,對全部請求都添加跨域參數

@Configuration  
    public class MyConfiguration {  
        @Bean  
        public WebMvcConfigurer corsConfigurer() {  
            return new WebMvcConfigurerAdapter() {  
                @Override  
                public void addCorsMappings(CorsRegistry registry) {  
                    registry.addMapping("/**")  
                    .allowCredentials(true)  
                    .allowedMethods("GET");  
                }  
            };  
        }  
    } 

總結

一、什麼是跨域?
二、爲何先後端分離後會產生跨域問題?
三、解決跨域有哪些方式?

參考:
https://blog.csdn.net/zknxx/article/details/53443181
https://blog.csdn.net/qq_43486273/article/details/83272500
http://www.ruanyifeng.com/blog/2016/04/cors.html

以爲有用,記得點贊加關注。

跟着老萬學java



本文分享自微信公衆號 - 跟着老萬學java(douzhe_2019)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索