SpringSecurity 中的 CORS實現


1.基礎知識##

1.1 跨域###

跨域是瀏覽器的一種同源安全策略,是瀏覽器單方面限制的,所有僅在客戶端運行在瀏覽器中才需要考慮這個問題。
跨域分為三種情況,協議跨域(http->https)、端口跨域、主機跨域。
常用的解決跨域的三種方式,JSONP(只能支持GET跨域),NGINX代理轉發(一般前端同學愛這么用),CORS

1.2 CORS原理###

因為cors是由瀏覽器控制的,實際就是在HEADER中新增一些字段,跨域時和服務器端進行一個的握手協議,比如下面的request部分,就是瀏覽器告訴服務器,我要從origin這里訪問你了,要訪問你的xxMethod,還會帶xxHeader這個參數。你告訴我行不行吧。然后response會帶一堆東西回來。告訴你,我這里只能有Access-Control-Allow-Origin這些域名可以訪問,你自個兒先看看行不行,然后你的方法支持不支持,header支持不支持。。。等等東西

比如
request部分:

    Origin     //瀏覽器自己設置的,表示請求從哪個域名下發出來的
    //下面兩個只有預檢請求會帶
    Access-Control-Request-Method    //告訴服務器我要用什么方法,服務器會根據配置做下篩選 再返回一個Access-Control-Allow-Methods告訴瀏覽器哪些可以用
    Access-Control-Request-Headers   //告訴服務器我要帶哪些header,服務器會根據配置做下篩選 再返回一個Access-Control-Allow-Headers告訴瀏覽器哪些可以用

response部分:

    Access-Control-Allow-Origin    //指定哪些客戶端的域名允許訪問這個資源
    Access-Control-Allow-Credentials  //服務器允許瀏覽器帶cookie上來,不設這個服務器端就無法獲得登錄信息
    Access-Control-Max-Age   //告訴瀏覽器多久不需要再發出預檢請求
    Access-Control-Allow-Methods //服務器支持的方法 比如POST GET之類的
    Access-Control-Allow-Headers  //需要在正式請求中加入的header值,否則正式請求會被拒絕,也是預檢請求的響應中帶回去的
    Access-Control-Expose-Headers   //很少用,告訴客戶端哪些header可以使用

1.3 CORS的三種場景###

簡單請求

瀏覽器來決定請求的種類,比如不帶自定義請求頭信息的GET請求、HEAD請求以及Content-type為application/x-www-form-urlencoded、multipart/form-data或者text/plain的post請求,都是簡單請求。
簡單請求時,瀏覽器的request只有一個origin字段,后端的返回值也很簡單只要加一個Access-Control-Allow-Origin九可以了,瀏覽器拿到Access-Control-Allow-Origin的值后和自己的進行首部比對,通過就允許跨域了。

預檢請求(preflight)

預檢請求是瀏覽器發現不是簡單請求后,封裝一個OPTIONS請求到服務器,根據服務器的返回值來判斷是否可以進行跨域,可以的話,后面還有一次真正帶數據的請求。上面的response部分:就是預檢請求后服務器端的響應

帶憑據的請求

其實就是當預檢請求的返回值中 Access-Control-Allow-Credentials 的值為TRUE,那么后續的正式請求就會攜帶憑據信息(cookie等)

2.springsecurit中cors的實現##

springboot中是通過CorsFilter來實現的,具體干活的是DefaultCorsProcessor,一會看下代碼。開啟方式也很簡單,就是security的配置中通過HttpSecurity中的.cors()方法開啟。

DefaultCorsProcessor.java

    //干活的代碼
    public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
                //回寫三個vary屬性
		response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
		response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
		response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
                //如果不是跨域請求,直接返回true了
		if (!CorsUtils.isCorsRequest(request)) {
			return true;
		}
                //response已經有Access-Control-Allow-Origin 就是已經處理過了,就退回,兼容其他的自定義filter
		if (response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) != null) {
			logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
			return true;
		}
                //判斷是否是預檢請求
		boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
                //缺少cors的配置
		if (config == null) {
                        //預檢請求直接拒絕
			if (preFlightRequest) {
				rejectRequest(new ServletServerHttpResponse(response));
				return false;
			}
                        //簡單請求直接通過
			else {
				return true;
			}
		}
                //具體處理
		return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
	}


    protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
			CorsConfiguration config, boolean preFlightRequest) throws IOException {

		String requestOrigin = request.getHeaders().getOrigin();
		String allowOrigin = checkOrigin(config, requestOrigin);
		HttpHeaders responseHeaders = response.getHeaders();
                //確保請求帶了origin這個屬性
		if (allowOrigin == null) {
			logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
			rejectRequest(response);
			return false;
		}
                //請求的method,復雜請求從Access-Control-Request-Method里面拿,簡單請求直接就是request.getMethod()
		HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
		List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
                //確保請求的method 確保在服務器端的配置中是允許的
		if (allowMethods == null) {
			logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
			rejectRequest(response);
			return false;
		}
                //和方法類似,校驗下header
		List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
		List<String> allowHeaders = checkHeaders(config, requestHeaders);
		if (preFlightRequest && allowHeaders == null) {
			logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
			rejectRequest(response);
			return false;
		}
                //設置Access-Control-Allow-Origin
		responseHeaders.setAccessControlAllowOrigin(allowOrigin);
                //如果是預檢請求,設置下Access-Control-Allow-Methods
		if (preFlightRequest) {
			responseHeaders.setAccessControlAllowMethods(allowMethods);
		}
                //如果是預檢請求,並且allowHeaders不為空,設置下Access-Control-Allow-Headers
		if (preFlightRequest && !allowHeaders.isEmpty()) {
			responseHeaders.setAccessControlAllowHeaders(allowHeaders);
		}
                //設置Access-Control-Expose-Headers
		if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
			responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
		}
                //設置Access-Control-Allow-Credentials
		if (Boolean.TRUE.equals(config.getAllowCredentials())) {
			responseHeaders.setAccessControlAllowCredentials(true);
		}
                //設置Access-Control-Max-Age
		if (preFlightRequest && config.getMaxAge() != null) {
			responseHeaders.setAccessControlMaxAge(config.getMaxAge());
		}
		response.flush();
		return true;
	}

3. springboot如何開啟cors##

3.1 整體配置###

寫一個CorsConfigurationSource的bean就可以了,CorsConfigurer里面會自動注入這個bean

    @Bean
    public CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://www.baidu.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",configuration);
        return source;
    }

3.2 具體配置###

在controller或者具體方法上面加注釋

    @CrossOrigin(origins = Constant.originalHost, maxAge = 3600,allowCredentials = "true") 就可以了


免責聲明!

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



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