SpringMVC跨域配置——如何设置多个Access-Control-Allow-Origin

跨域问题

前后端分离的系统,如果“前端页面的域名”与“后端接口的域名”不相同(即不同的源),前端页面通过ajax调用后端接口时,就会发生跨域问题。如果“同源”则不会有跨域问题,“同源” 是指协议(https/http)、域名和端口都相同。

跨域阻拦原理

(1)浏览器会给跨域的ajax请求自动设置Origin请求头,这个请求头的值就是当前页面的完整域名,包括协议(https或http)、域名和端口。比如在前端页面为 https://www.front.com/index.html ,则:origin=https://www.front.com

(2)请求完成之后,浏览器会取响应头Access-Control-Allow-Origin的值(这个值由后端设置),与当前域名(即请求头中的Origin)做对比,如果发现不相等,则拒绝将服务端返回的数据给到ajax。实际上跨域时服务端是没有阻止你的,只是浏览器拿到服务端返回的数据后不把数据给你,你用抓包工具是可以看到服务端返回的数据。

问题解决

(1)根据上述原理,是否允许跨域就取决于服务端响应头中的Access-Control-Allow-Origin值,只要把这个值设置为前端的完整域名即可,即Access-Control-Allow-Origin=https://www.front.com

(2)由于Access-Control-Allow-Origin请求头只能设置一个值,如果有多个前端域名怎么解决呢?
动态设置Access-Control-Allow-Origin即可,你根据请求头中的origin来设置,当然不是每一个origin都设置进去,这样就没有安全性可言了,服务端应该有一个域名的白名单列表,如果发现请求头的origin与白名单匹配则设置到Access-Control-Allow-Origin响应头中。

SpringMVC实现

这种不属于业务层面的功能,可以通过Spring提供的拦截器统一处理。

(1)配置文件
application.properties

# 考虑到可能有三级域名,这里使用通配符“*”号  ,多个用英文逗号“,”分隔
config.allowOrigins=*.abc.com,*.xyz.com

(2)拦截器
CorsInterceptor .java

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

@Slf4j
@Component
public class CorsInterceptor implements HandlerInterceptor {

    //从上述配置文件中读出allowOrigins
    @Value("${config.allowOrigins}")
    private String allowOrigins;

    //正则匹配符集合
    private Set<Pattern> allowOriginPatterns;

    //根据配置初始化白名单的正则表达式  
    @PostConstruct
    private void init(){
        allowOriginPatterns =new HashSet<>();

        log.debug("allowOrigins = {}", allowOrigins);

        if(StringUtils.isBlank(allowOrigins)){
            return;
        }

        String[] origins=allowOrigins.split(",");
        for (String origin : origins) {
            if(StringUtils.isBlank(origin)){
                continue;
            }
            //将开头第一个星号*替换为.*,将所有的点号配置为\.,方便做正则表达式匹配 
            //由于在正在表达式中“.”和“*”都是特殊字符,因此需要转义  
            origin=origin.trim().replace("\\.","\\\\.").replace("*",".*");
            allowOriginPatterns.add(Pattern.compile(origin));
        }

        log.debug("allowOriginPatterns = {}",allowOriginPatterns);

    }

    /**
     * 返回true则会继续执行拦截器链中的后续拦截器, 否则不往后执行后续拦截器。  
     * 
     * 详细说明:
     * 在业务处理器Ccontroller处理请求之前被调用。    
     * 
     * (1)按拦截器链中的顺序执行所有拦截器的preHandle()方法,直到所有拦截器执行完为止(或者到该方法返回false的拦截器为止); 
     * (2)然后执行被拦截的Controller。  
     * (3)往回执行所有已执行过preHandle()方法的拦截器的postHandle()方法,与第(1)步中的执行方向相反。 
     * (4)渲染ModelView(如果Controller返回ModelView,比如jsp页面),前后端分离的忽略该步骤。  
     * (5)往回执行所有已执行过postHandle()方法的拦截器的afterCompletion()方法,与第(1)步中的执行方向相反。 
     * 
     */  
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler){

        log.debug(" cors processing begin ");
        //获取请求头中的 origin
        String headerOrigin=request.getHeader("Origin");
        log.debug("request url : {} , header origin : {}",request.getRequestURL(),headerOrigin);

        if(StringUtils.isBlank(headerOrigin)){
            return true;
        }

        for (Pattern pattern : allowOriginPatterns) {
            //白名单匹配  
            if(pattern.matcher(headerOrigin).matches()){
                log.debug("set '{}' to 'Access-Control-Allow-Origin' for response header ",headerOrigin);
                //允许跨域配置:http://www.ruanyifeng.com/blog/2016/04/cors.html
                //Access-Control-Allow-Origin:该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
                response.setHeader("Access-Control-Allow-Origin", headerOrigin);
                response.setHeader("Access-Control-Allow-Methods", "GET");
                //Access-Control-Allow-Credentials:该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
                response.setHeader("Access-Control-Allow-Credentials","true");
                //response.setHeader("Access-Control-Max-Age", "3600");
                //response.setHeader("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept");
                break;
            }
        }

        log.debug(" cors processing end ");

        return true;
    }

    /**
     * 在业务处理器处理请求执行完成后,生成视图之前执行的动作
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
       log.debug("postHandle()");
    }  
  
    /**  
     *   
     * 在DispatcherServlet完全处理完请求后被调用,  
     * 会从当前拦截器往回执行所有的拦截器的afterCompletion()  
     *   
     * @param request  
     *   
     * @param response  
     *   
     * @param handler  
     *   
     */  
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) {
        log.debug("afterCompletion()");
    }
}  

(3)配置拦截器
MyWebMvcConfig.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private CorsInterceptor corsInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //将CorsInterceptor拦截器添加进来  
        registry.addInterceptor(corsInterceptor).addPathPatterns("/**");
    }
    
}

参考文档

跨域资源共享 CORS 详解



更多文章请关注公众号 薛定谔的雄猫
SpringMVC跨域配置——如何设置多个Access-Control-Allow-Origin_第1张图片

















你可能感兴趣的:(Spring)