SpringBoot系列——CORS(跨源資源共享)


  前言

  出於安全原因,瀏覽器禁止ajax調用當前源之外的資源(同源策略),我們之前也有寫個幾種跨域的簡單實現(還在問跨域?本文記錄js跨域的多種實現實例),本文主要詳細介紹CORS,跨源資源共享,以及如何在SpringBoot的幾種實現方式

  這里主要參考spring的這篇:https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#mvc-cors

  以及:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

  CORS介紹

  跨源資源共享(Cross-Origin Resource Sharing, CORS)是由大多數瀏覽器實現的W3C規范,它允許指定授權了哪種跨域請求,而不是使用基於IFRAME(內嵌框架)或JSONP的不太安全且功能不太強大的方法。  

  CORS分為簡單請求非簡單請求兩種跨域請求方式,Spring MVC HandlerMapping的實現提供了對CORS的內置支持。成功地將請求映射到處理程序之后,HandlerMapping的實現將檢查CORS配置並不同的請求進行操作:預檢請求直接處理,而簡單請求和非簡單請求被攔截、驗證,並設置了所需的CORS響應頭。為了啟用跨源請求,您需要一些顯式聲明的CORS配置。如果沒有找到匹配的CORS配置,預檢請求將被拒絕。沒有將CORS標頭添加到簡單、非簡單CORS請求的響應中,瀏覽器會拒絕這個跨域請求。

  簡單請求不會觸發預檢請求,而非簡單請求在發起之前會先發起預檢請求,以獲知服務器是否允許該實際請求,"預檢請求“的使用,可以避免跨域請求對服務器的用戶數據產生未預期的影響。

 

  跨域響應字段含義:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#HTTP_%E5%93%8D%E5%BA%94%E9%A6%96%E9%83%A8%E5%AD%97%E6%AE%B5

  跨域請求字段含義:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#HTTP_%E8%AF%B7%E6%B1%82%E9%A6%96%E9%83%A8%E5%AD%97%E6%AE%B5

 

  簡單請求

  若請求滿足所有下述條件,則該請求可視為“簡單請求”:

  • 使用下列方法之一:
    • GET
    • HEAD
    • POST
  • Fetch 規范定義了對 CORS 安全的首部字段集合,不得人為設置該集合之外的其他首部字段。該集合為:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (需要注意額外的限制)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type 的值僅限於下列三者之一:
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  • 請求中的任意XMLHttpRequestUpload 對象均沒有注冊任何事件監聽器;XMLHttpRequestUpload 對象可以使用 XMLHttpRequest.upload 屬性訪問。
  • 請求中沒有使用 ReadableStream 對象。

   例如:

 

  非簡單請求

  當請求滿足下述任一條件時,即視為非簡單請求,應首先發送預檢請求:

  • 使用了下面任一 HTTP 方法:
    • PUT
    • DELETE
    • CONNECT
    • OPTIONS
    • TRACE
    • PATCH
  • 人為設置了對 CORS 安全的首部字段集合之外的其他首部字段。該集合為:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (需要注意額外的限制)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  •  Content-Type 的值不屬於下列之一:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • 請求中的XMLHttpRequestUpload 對象注冊了任意多個事件監聽器。
  • 請求中使用了ReadableStream對象。

  例如:  

 

 

 

  PS:如果ajax的contentType:"application/json;charset=UTF-8",設置成了json格式傳輸,那么你的data就要這樣傳JSON.stringify({id:1}),並且后端接參要加上@RequestBody,用對象去接MVC會幫我們自動注入參數,用字符串去接,會得到json字符串

 

  附帶身份憑證的請求

  CORS 的一個有趣的特性是,可以基於  HTTP cookies 和 HTTP 認證信息發送身份憑證。一般而言,對於跨域 XMLHttpRequest請求,瀏覽器不會發送身份憑證信息。如果要發送憑證信息,需要設置 XMLHttpRequest的某個特殊標志位 withCredentials=true,就可以向服務器發送Cookies,但是,如果服務器端的響應中未攜帶 Access-Control-Allow-Credentials: true,瀏覽器將不會把響應內容返回給請求的發送者。

  如果前端設置了true,后端為false,則會

 

  實現方式

  PS:不管是哪種方法,一定要看仔細前端的請求頭中Origins的值到底是什么,前端的值與后端配置的值對應不上則無法跨域,比如前端是http://localhost:8080,而后端配置成IP,則無法跨域

 

   @CrossOrigin

   TestController接口測試

package cn.huanzi.qch.springbootcors.controller;

import org.springframework.web.bind.annotation.*;

@RequestMapping("cors/")
@RestController
public class TestController {
    /*
       通過注解配置CORS跨域測試
       $.ajax({
           type:"POST",
           url:"http://localhost:10095/cors/corsByAnnotation",
           data:{id:1},
           dataType:"text",//因為我們響應的是不是json,這里要改一下
           contentType:"application/x-www-form-urlencoded",
           //contentType:"application/json;charset=UTF-8",//如果用這個,則為非簡單請求
           xhrFields:{ withCredentials:true },
           success:function(data){
               console.log(data);
           },
           error:function(data){
                console.log("報錯啦");
           }
       })
    */
    @CrossOrigin(
            origins = "https://www.cnblogs.com",
            allowedHeaders = "*",
            methods = {RequestMethod.POST},
            allowCredentials = "true",
            maxAge = 3600
    )
    @PostMapping("corsByAnnotation")
    public String corsByAnnotation(String id) {
        return "corsByAnnotation," + id;
    }
}

   如果@CrossOrigin注解在controller類上面聲明,則整個controller類的接口都可以跨域調用

 

  配置Config

   Java Configuration

  MyConfiguration配置類

package cn.huanzi.qch.springbootcors.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyConfiguration {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/cors/corsByConfig")
                        .allowedOrigins("https://www.cnblogs.com")
                        .allowedMethods("POST")
                        .allowedHeaders("*")
                        .allowCredentials(true).maxAge(3600);
            }
        };
    }
}

  TestController接口測試

package cn.huanzi.qch.springbootcors.controller;

import org.springframework.web.bind.annotation.*;

@RequestMapping("cors/")
@RestController
public class TestController {
    /*
       通過Config配置CORS跨域測試
       $.ajax({
           type:"POST",
           url:"http://localhost:10095/cors/corsByConfig",
           data:{id:2},
           dataType:"text",//因為我們響應的是不是json,這里要改一下
           contentType:"application/x-www-form-urlencoded",
           //contentType:"application/json;charset=UTF-8",//如果用這個,則為非簡單請求
           xhrFields:{ withCredentials:true },
           success:function(data){
               console.log(data);
           },
           error:function(data){
                console.log("報錯啦");
           }
       })
    */
    @PostMapping("corsByConfig")
    public String corsByConfig(String id) {
        return "corsByConfig," + id;
    }
}

   XML Configuration

  xml格式我就不試了,大家看文檔就好了:https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#mvc-cors-global-xml

 

 

  CORS Filter

  配置攔截器在啟動項目的時候會報一個bean已存在,叫我們改名或啟用覆蓋默認bean

Description:

The bean 'myCorsFilter', defined in null, could not be registered. A bean with that name has already been defined in file [C:\Users\Administrator\Desktop\雜七雜八\springBoot\springboot-cors\target\classes\cn\huanzi\qch\springbootcors\filter\MyCorsFilter.class] and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

  配置覆蓋

#啟用覆蓋默認bean
spring.main.allow-bean-definition-overriding=true
 
        

  MyCorsFilter

package cn.huanzi.qch.springbootcors.filter;

import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

@Component
@ServletComponentScan
@WebFilter(filterName = "myCorsFilter", //過濾器名稱
        urlPatterns = "/cors/corsByMyCorsFilter",//url路徑
        initParams = {
                @WebInitParam(name = "allowOrigin", value = "https://www.cnblogs.com"),//允許的請求源,可用,分隔,*表示所有
                @WebInitParam(name = "allowMethods", value = "POST"),//允許的請求方法,可用,分隔,*表示所有
                @WebInitParam(name = "allowCredentials", value = "true"),
                @WebInitParam(name = "allowHeaders", value = "*"),
                @WebInitParam(name = "maxAge", value = "3600"),//60秒 * 60,相當於一個小時
        })
public class MyCorsFilter implements Filter {

    private String allowOrigin;
    private String allowMethods;
    private String allowCredentials;
    private String allowHeaders;
    private String maxAge;

    @Override
    public void init(FilterConfig filterConfig) {
        //讀取@WebFilter的initParams
        allowOrigin = filterConfig.getInitParameter("allowOrigin");
        allowMethods = filterConfig.getInitParameter("allowMethods");
        allowCredentials = filterConfig.getInitParameter("allowCredentials");
        allowHeaders = filterConfig.getInitParameter("allowHeaders");
        maxAge = filterConfig.getInitParameter("maxAge");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (!StringUtils.isEmpty(allowOrigin)) {
            if (allowOrigin.equals("*")) {
                response.setHeader("Access-Control-Allow-Origin", allowOrigin);
            } else {
                List<String> allowOriginList = Arrays.asList(allowOrigin.split(","));
                if (allowOriginList.size() > 0) {
                    //如果來源在允許來源內
                    String currentOrigin = request.getHeader("Origin");
                    if (allowOriginList.contains(currentOrigin)) {
                        response.setHeader("Access-Control-Allow-Origin", currentOrigin);
                    }
                }
            }
        }
        if (!StringUtils.isEmpty(allowMethods)) {
            response.setHeader("Access-Control-Allow-Methods", allowMethods);
        }
        if (!StringUtils.isEmpty(allowCredentials)) {
            response.setHeader("Access-Control-Allow-Credentials", allowCredentials);
        }
        if (!StringUtils.isEmpty(allowHeaders)) {
            response.setHeader("Access-Control-Allow-Headers", allowHeaders);
        }
        if (!StringUtils.isEmpty(maxAge)) {
            response.setHeader("Access-Control-Max-Age", maxAge);
        }

        //執行
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

  TestController接口測試

package cn.huanzi.qch.springbootcors.controller;

import org.springframework.web.bind.annotation.*;

@RequestMapping("cors/")
@RestController
public class TestController {/*
       通過攔截器配置CORS跨域測試
       $.ajax({
           type:"POST",
           url:"http://localhost:10095/cors/corsByMyCorsFilter",
           data:{id:3},
           dataType:"text",//因為我們響應的是不是json,這里要改一下
           contentType:"application/x-www-form-urlencoded",
           //contentType:"application/json;charset=UTF-8",//如果用這個,則為非簡單請求
           xhrFields:{ withCredentials:true },
           success:function(data){
               console.log(data);
           },
           error:function(data){
                console.log("報錯啦");
           }
        })
    */
    @PostMapping("corsByMyCorsFilter")
    public String corsByMyCorsFilter(String id) {
        return "corsByMyCorsFilter," + id;
    }
}

 

  測試

  打開博客園,F12打開控制台,開始測試

 

  注解

 

  java配置

 

 

  corsFilter

 

 

  后記

  暫時記錄到這里 

 

  代碼開源

  代碼已經開源、托管到我的GitHub、碼雲:

  GitHub:https://github.com/huanzi-qch/springBoot

  碼雲:https://gitee.com/huanzi-qch/springBoot


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM