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;
    }
}
posted @ 2021-07-31 22:01  jiawei3998  阅读(780)  评论(0编辑  收藏  举报