1. 程式人生 > >前後端分離專案跨域問題分析及解決思路

前後端分離專案跨域問題分析及解決思路

什麼是跨域

瀏覽器的同源策略限制預設情況下前端頁面和後端服務在不同伺服器(域名、埠不一樣)時,前端頁面js無法請求到後端介面服務,即存在跨域問題。

跨域問題解決思路

  • 使用jsonp方式解決
  • 使用cors解決
  • 使用nginx代理解決

這裡不討論jsonp的方式,主要討論cors和代理方式。

cors方式

通過服務端設定介面響應頭Header允許介面被跨域請求,介面做如下設定即可:

@RequestMapping("crossorigin")
public String crossorigin(HttpServletResponse response) {
	response.setHeader("Access-Control-Allow-Origin", "*");
	response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
	return "crossorigin請求";
}

 那麼服務端即允許介面被跨域訪問,不同源的前端頁面也就可以呼叫該介面。

當然,這裡只是單獨設定了一個介面的響應頭,實際專案中可以根據需要選擇不同的設定方式,例如:

  •  ​​​​​​使用spring的註解@CrossOrigin
  • 自定義攔截器,對每個請求的response header進行設定
  • spring自帶的cors標籤進行xml設定
  • web.xml中通過filter設定

我更喜歡自定義攔截器的方式對response header設定,允許介面跨域請求。

還存在的一些問題

上面操作只是解決了介面可以跨域請求問題,對於跨域請求攜帶cookies資訊,還沒有解決。因為預設前端頁面ajax跨源請求不提供憑據(cookie、HTTP認證及客戶端SSL證明等),這也是跨域請求sessionID每次都變化的問題,下面舉例說明sessionID每次變化的原因:

瀏覽器第一次請求伺服器,cookies中沒有攜帶sessionID,伺服器會隨機生成一個唯一sessionID,然後返回給瀏覽器,瀏覽器將這個sessionID儲存到cookies。
正常非跨域請求,以後瀏覽器再次請求伺服器,會將這個sessionID通過cookies傳送給伺服器,伺服器接收到sessionID,就不會重新生成一個sessionID返回給瀏覽器,而是返回本次接受到的sessionID,通過這種方式來保持session會話,這樣每次請求服務端時sessionID就不會變化。
因為跨源請求不提供憑據(cookie、HTTP認證及客戶端SSL證明等),也就是每次請求不攜帶cookie中資料,每次請求cookies中都不會攜帶sessionID,那麼服務端就會認為請求是沒有sessionID的第一次請求,便重新生成一個sessionID返回給瀏覽器。所以,每次前端頁面跨源ajax請求sessionID都會變化。



要讓跨域請求提供憑據,可以在ajax請求中通過將withCredentials屬性設定為true,指定某個請求應該傳送憑據。
這樣每次跨域請求都會將cookie中的sessionID傳送給伺服器,sessionID就不會再改變了。

要使前端ajax跨域請求攜帶憑據(cookies、http認證等),需要在ajax請求中增加屬性,增加下面屬性

xhrFields: {  withCredentials: true } 即可。

前端程式碼:

<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title>session-test</title>
<script src="jquery-3.3.1.min.js" type="text/javascript"></script>
</head>
<body>
	<h1>session</h1>
	<script>
		$(document).ready(function() {
			$.ajax({
				url:"http://localhost:8080/ssm-example/getSession", 
				xhrFields: {
					   withCredentials: true
				},
				success:function(data){
					alert("sessionID = " + data);
				}
			});
		});
	</script>
</body>
</html>

後端程式碼:

package com.ssm.example.controller;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {
	
	@RequestMapping("/getSession")
	@ResponseBody
	@CrossOrigin()
	public String getSession(HttpSession session) {
		String sessionID = session.getId();
		System.out.println(sessionID);
		return sessionID;
	}
}

當然,因為session的侷限性,越來越多的專案放棄使用session的方式做登入認證,而是將需要認證的資訊例如token放在請求頭中,傳遞給服務端,因此也無需跨源請求傳遞請求憑證,前端頁面也無需增加上面屬性。

代理方式解決跨域問題

代理方式工具有多種,這裡使用常用的nginx舉例,nginx代理解決跨域原理其實很簡單,從瀏覽器同源策略的限制角度考慮,通過代理方式將前端頁面和後端介面代理成為同源服務(即瀏覽器訪問前端頁面和後端介面的ip主機相同、port埠相同),這樣前端頁面請求介面時就不會因為同源策略限制出現跨域問題。例如:

nginx配置,將前端頁面代理為localhost:80地址,將後端介面代理為localhost:80/apis地址,那麼瀏覽器就會以為前端頁面和後端介面同源。

注意:因為nginx配置代理路徑時,對Windows路徑支援不太好,因此root路徑最好使用相對路徑。

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;
		charset UTF-8;
		location / { # 這裡配置前端頁面代理
            root   html;
            index  index.html index.htm;
        }
        location /apis { # 這裡配置後端介面代理
            proxy_pass http://localhost:8080/;
        }
    }
}

前端頁面JavaScript配置,經過上面nginx代理,現在前端頁面地址通過 http://localhost/具體頁面路徑 訪問,頁面ajax請求介面地址也要換成nginx代理後的http://localhost/apis/具體介面地址 來訪問,這樣瀏覽器在呼叫的時候才會沒有同源限制。

<h3 id="h3">hello crossorigin</h3>
<script type="text/javascript">
	$("#h3").click(function() {
		alert("開始請求");
		$.ajax({
			url:"http://localhost/apis/crossorigin", # 這裡使用的是nginx代理後的介面路徑,關鍵
			success:function(data) {
				alert("Data: " + data);
			}
		});
	});
</script>

頁面訪問http://localhohst/crossorigin.html,那麼頁面和介面的ip都是localhost,port埠都是80,沒有瀏覽器同源限制,解決了跨域問題,前端頁面請求會攜帶cookies等憑證,後端介面無需針對response header做跨域調整。

代理方式應該是解決跨域問題的最優方案,開發中應該優先採用該方式處理跨域問題,當然方案的選擇還是要根據實際遇到的需求變通。