跨域问题解决方案

跨域问题解决方案

前言

最近开发一个小项目,我负责后端开发,使用 Spring Boot。 由于是完全独立的服务,前后端联调时出现了跨域问题。

什么是跨域?

跨域(Cross-Origin Resource Sharing, CORS)是一种机制,它允许或拒绝来自不同源(origin)的Web页面向位于另一个源上的服务器发送请求。这里的“源”指的是协议、域名和端口号的组合。同源策略(Same-Origin Policy)是浏览器的一个安全功能,它限制了从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。然而,在现代Web应用中,经常需要从不同的源请求数据或服务,这时就需要用到跨域技术。

CORS通过增加额外的HTTP头部在服务器上进行配置,来告诉浏览器允许哪些跨域请求。如果一个请求是从一个源发起的,但请求的资源位于另一个源上,那么浏览器会先检查这个请求是否符合CORS策略。服务器通过在响应中添加特定的HTTP头部(如 Access-Control-Allow-Origin)来表明哪些源可以访问该资源

这是比较专业的说法,简单理解就是,在浏览器请求后端过程中,协议,主机地址,端口,这三者有一个不同,都会形成跨域。

解决跨域

实现 WebMvcConfigurer

SpringBoot应用中可以实现 WebMvcConfigurer 接口,重写addCorsMappings方法

    /**
     * 解决跨域请求
     *
     * @return
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowCredentials(true)
                .allowedOrigins("*")
                .allowedHeaders("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE","OPTIONS")
                .maxAge(3600);
        WebMvcConfigurer.super.addCorsMappings(registry);
    }

继承 HandlerInterceptorAdapter

SpringBoot应用中可以继承 HandlerInterceptorAdapter 类,重写 preHandle方法

@Component
public class CrossInterceptor extends HandlerInterceptorAdapter {
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        return true;
    }
}
 

要将定义的拦截器放到Spring拦截器链中

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CrossInterceptorHandler()).addPathPatterns("/**");
    }
}

过滤器 Filter

使用Java 原生的 Filter

@WebFilter(filterName = "CorsFilter", urlPatterns = "/*")
@Configuration
public class CorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin","*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        chain.doFilter(req, res);
    }
}

使用注解

在控制器Controller上使用注解 @CrossOrigin,表示该类的所有方法允许跨域

@RestController
@CrossOrigin(origins = "*")
public class UserController {
 
}

在方法上使用注解 @CrossOrigin

@PostMapping("/check/phone")
@CrossOrigin(origins = "*")
public boolean checkPhoneNumber(@RequestBody @ApiParam VerificationPojo verification) throws BusinessException {
    return false;
}

注意,使用注解方式仅对注解类或方法生效,属于局部跨域

Nginx 配置代理

使用Nginx配置代理的方式

location / {  
    add_header 'Access-Control-Allow-Origin' '*'; 
    add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS; 
    # 其他配置...  

    #为OPTIONS请求添加相应的头部
    if ($request_method = 'OPTIONS') {  
		add_header 'Access-Control-Allow-Origin' '*';  
		add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';  
		add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';  
		add_header 'Access-Control-Max-Age' 1728000;  
		add_header 'Content-Type' 'text/plain charset=UTF-8';  
		add_header 'Content-Length' 0;  
		return 204;  
    }
}

总结

本文基于日常开发中的跨域问题列出了几种解决方式,主要分为后端代码中和中间件中,根据需要选择合适的方案