SpringBoot添加Cors跨域配置,解決No 'Access-Control-Allow-Origin' header is present on the requested resource


什么是CORS

跨域(CORS)請求:同源策略/SOP(Same origin policy)是一種約定,由Netscape公司1995年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到XSS、CSFR等攻擊。所謂同源是指 "協議+域名+端口" 三者相同, 不同源則跨域。

如果還想了解更多,下面這兩個文檔更加詳細的介紹了CORS

CORS 參考鏈接https://developer.mozilla.org/zh-CN/docs/Glossary/CORS

關於HTTP請求分類參考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS



SpringBoot 全局配置CORS

添加一個 WebMvc 的配置類,在沒有過濾器和攔截器的情況下是ok了。

我這里還配置了一個攔截器,HTTP的預檢請求會經過攔截器,我就直接在攔截器里面對預檢請求進行處理。(如果配置了過濾器可以在過濾器中進行處理,因為過濾器比攔截器更早經過)

package com.pro.config;

import com.pro.interceptor.BaseInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 擴展 Web MVC
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    private static final String[] whiteList = {"/admin/login", "/admin/logout"};

    @Autowired
    BaseInterceptor baseInterceptor;

    /**
     * 配置攔截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(baseInterceptor).excludePathPatterns(whiteList);
    }

    /**
     * 配置跨域請求
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                //設置允許跨域請求的域名
                .allowedOriginPatterns("*")
                // 設置允許請求方式
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD")
                // 是否允許證書(cookies)
                .allowCredentials(true)
                // 預請求的結果能被緩存多久
                .maxAge(3600)
                // 設置允許的請求頭
                .allowedHeaders("*");
    }
}

攔截器處理預檢請求

直接在攔截器里面對預檢請求進行處理,處理方法就是這個方法 responseCors

核心處理邏輯

/**
 * 請求方法執行之前
 * @param request
 * @param response
 * @param handler
 * @return
 * @throws Exception
 */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
	// 在進入這個攔截器之前,對跨域提供支持
	if (responseCors(response, request)) {
	    return false;
	}
	return true;
}

/**
 * 在進入這個攔截器之前, 對跨域提供支持
 * @param response
 * @param request
 * @return
 */
private boolean responseCors(HttpServletResponse response, HttpServletRequest request) {
	// 判斷是否是預檢請求
    if (RequestMethod.OPTIONS.name().equals(request.getMethod())) {
        // response.setHeader("Cache-Control","no-cache");
        response.setHeader("Access-control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        response.setHeader("Access-Control-Allow-Headers", "*");
        // 跨域時會首先發送一個OPTIONS請求,這里我們給OPTIONS請求直接返回正常狀態
        response.setStatus(HttpStatus.OK.value());
        return true;
    }
    return false;
}

完整攔截器代碼

package com.pro.interceptor;

import com.pro.constant.ErrorConstant;
import com.pro.utils.*;
import com.pro.vo.user.UserInfoVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 自定義攔截器
 */
@Component
public class BaseInterceptor implements HandlerInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(BaseInterceptor.class);
    private static final String USER_AGENT = "user-agent";

    // 后台管理請求接口白名單前綴
    private final String[] whiteListPrefix = {"/admin/login", "/admin/css", "/admin/js", "/admin/plugins", "/admin/editormd", "/admin/images"};

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 請求方法執行之前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在進入這個攔截器之前,對跨域提供支持
        if (responseCors(response, request)) {
            return false;
        }

        // 獲取用戶訪問的路由
        String uri = request.getRequestURI();

        LOGGER.info("UserAgent: {}", request.getHeader(USER_AGENT));
        LOGGER.info("用戶訪問地址: {}, 來源地址: {}", uri, IPKit.getIpAddrByRequest(request));


        // 獲取登錄用戶同時刷新用戶過期時間
        UserInfoVO user = redisUtil.getLoginUser(request, true);
        // 請求攔截
        if (uri.startsWith("/admin") && user == null && verifyUriPrefix(uri)) {
            this.responseResult(response);
            return false;
        }

        return true;
    }

    /**
     * 驗證 uri 是否在白名單中
     * @param uri 統一資源標志符/路由
     * @return boolean
     */
    private boolean verifyUriPrefix(String uri) {
        if (uri == null) return false;
        for (String prefix : whiteListPrefix) {
            // 判斷 uri 是否以白名單的前綴開頭
            if (uri.startsWith(prefix)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 用戶未登錄 使用 response 返回結果
     * @param response
     * @throws IOException
     */
    private void responseResult(HttpServletResponse response) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().println(JsonKit.toJSON(Result.fail(401, ErrorConstant.Auth.NOT_LOGIN)));
    }

    /**
     * 在進入這個攔截器之前, 對跨域提供支持
     * @param response
     * @param request
     * @return
     */
    private boolean responseCors(HttpServletResponse response, HttpServletRequest request) {
        if (RequestMethod.OPTIONS.name().equals(request.getMethod())) {
            // response.setHeader("Cache-Control","no-cache");
            response.setHeader("Access-control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
            response.setHeader("Access-Control-Allow-Headers", "*");
            // 跨域時會首先發送一個OPTIONS請求,這里我們給OPTIONS請求直接返回正常狀態
            response.setStatus(HttpStatus.OK.value());
            return true;
        }
        return false;
    }
}


免責聲明!

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



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