spring-mvc第三期:跨域是啥?如何搞定?

前期回顧:讓controller沒有祕密

1.跨域是啥?

跨域問題是web開發中很經典的一個問題,我們先來重現一下這個問題,讓大家能夠快速理解(只做重要代碼說明)

首先我們來準備兩個web項目,兩個項目分別在tomcat不同而端口部署,第一個web項目我們只寫一個提供一個簡單信息的接口,如下:

/**
 * @author swing
 */
@Slf4j
@Controller
@RequestMapping("/info")
public class WeatherController {
    @GetMapping
    @ResponseBody
    public String weather() {
        return "important information";
    }
}

然後我們啓動這個項目,將他部署在tomcat的8099端口,接口的信息如下:

URL:http://localhost:8099/info

Response:important information

然後我們回到主項目來,編寫如下頁面:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Welcome!</title>
    <script type="text/javascript">
        function change() {
            let xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    let divElement = document.getElementById("div1");
                    divElement.innerHTML = xhr.responseText;
                    divElement.style.color = "red";
                }
            };
            //記得轉碼
            xhr.open("get", "http://localhost:8099/info", true);
            xhr.send();
        }
    </script>
</head>
<body>
<div id="div1"></div>
<button onclick="change()">Get Info</button>
<h2><a href="http://localhost:8099/info">Get Info</a> </h2>
</body>
</html>

然後我們啓動這個項目,將他部署在tomcat的8080端口上,可以看出,我這裏分別使用ajax和一個鏈接標籤去請求這個接口,我們來分別看一下運行結果:

點擊鏈接標籤中的Get Info 頁面成功跳轉並獲取正確信息:

當點擊 按鈕進行ajax請求時,翻車了:

這時候就發生了跨域問題,由於瀏覽器的同源機制,當一個網站試圖在自己的域下去請求另外一個域的信息,從而引起這個錯誤,大家要注意:這裏的域和和域名的域還是有區別的,同域(或同源)指的是 域名,子域名,端口,協議 都相同。

那麼鏈接標籤<a>是怎麼可以請求到接口呢?很明顯,他已經直接跳出本域,到達請求目標的域(觀察地址欄),因此不涉及跨域問題。

2.如何搞定?

簡單瞭解了跨域問題,我們來說說如何搞定它!

常見的解決方案如下:

2.1.設置接口提供方允許跨域訪問

這裏我們可以使用一個過濾器,爲響應增加一些頭部信息:

/**
 * @author swing
 */
public class AllowOrigin implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(request, httpServletResponse);
    }

    @Override
    public void destroy() {

    }
}

此時瀏覽器在接收到該網站的訪問後,便不會阻難

2.2.後臺請求轉發

這個解決方案也很好理解,既然在瀏覽器中我的請求收到限制,那我爲何不找後端幫我代理一下,我想要啥數據跟他說,然後他替我請求,簡單給出handler代碼:

@GetMapping("info")
    @ResponseBody
    public String getInfo() {
        String httpUrl = "http://localhost:8099/info";
        return request(httpUrl, null);
    }

    /**
     * @param httpUrl :請求接口
     * @param httpArg :參數
     * @return 返回結果
     */
    public static String request(String httpUrl, String httpArg) {
        BufferedReader reader = null;
        String result = null;
        StringBuilder sbf = new StringBuilder();
        httpUrl = httpUrl + "?" + httpArg;

        try {
            URL url = new URL(httpUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            InputStream is = connection.getInputStream();
            reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            String strRead = null;
            while ((strRead = reader.readLine()) != null) {
                sbf.append(strRead);
                sbf.append("\r\n");
            }
            reader.close();
            result = sbf.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

2.3.CORS 後臺同源配置(SpringMVC提供)(這個不錯喲!)

spring爲我們提供了一種簡便的的方法來解決跨域問題:@CrossOrigin 註解

/**
 * @author swing
 */
@Slf4j
@Controller
@RequestMapping("/info")
public class WeatherController {

    @CrossOrigin(origins = {}, methods = {}, maxAge = 3600, allowedHeaders = {}, exposedHeaders = {})
    @GetMapping
    @ResponseBody
    public String weather() {
        return "important information";
    }
}

該註解也可作用在類上(表示作用於該類的所有Handler),其中有幾個屬性這裏說一下:

  • origins:允許的跨域訪問的站點,相當於 header中的 Access-Control-Allow-Origin 信息,如果設置爲" * "則表示所有站點都可以
  • methods:即Access-Control-Allow-Methods ,允許的請求方法,GET,POST,PUT,DELETE。
  • maxAge:即 Access-Control-Max-Age 

    瀏覽器的同源策略,就是出於安全考慮,瀏覽器會限制從腳本發起的跨域HTTP請求(比如異步請求GET, POST, PUT, DELETE, OPTIONS等等),所以瀏覽器會向所請求的服務器發起兩次請求,第一次是瀏覽器使用OPTIONS方法發起一個預檢請求,第二次纔是真正的異步請求,第一次的預檢請求獲知服務器是否允許該跨域請求:如果允許,才發起第二次真實的請求;如果不允許,則攔截第二次請求。

    Access-Control-Max-Age用來指定本次預檢請求的有效期,單位爲秒,,在此期間不用發出另一條預檢請求

  • allowedHeaders & exposedHeaders:即 Access-Control-Allow-Headers,表示允許或者排除有某些頭部的請求

當然,CORS還支持一個全局配置,在MvcConfig中實現WebMvcConfigurer.addCorsMappings方法即可,如下:

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/info")
                .allowedOrigins("http://localhost:8080")    
                .allowedMethods("PUT", "GET", "POST")
                .allowCredentials(true)
                .maxAge(3600);
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章