在做前后端分离项目的时候,经常会遇见跨域的问题。上百度或者谷歌可以搜到很多解决方案,但把那些解决方案直接粘到自己的项目里面用的时候会出现各种各样的问题。这些问题的出现很耽误进度。所以我参考这篇文章基于Springboot,自己实现了后端的跨域配置,前端无需设置proxy或者使用nginx之类的东西。
实现思路
所谓跨域是指客户端向服务器发送一个http请求,服务器向客户端发送http响应报文时因为客户端和服务器端处于两个域下(端口号不同或者域名都不同),浏览器认为这个请求是不安全的,所以会直接屏蔽这次请求。因此我们需要让服务器的响应报文告知浏览器这次的跨域请求是安全的,从而使得客户端可以收到响应报文。
需要为响应报文添加的head
- Access-Control-Allow-Origin 它的值必须要和请求中的origin(客户端的url)相同
- Access-Control-Allow-Methods 指定Restful api
- Access-Control-Max-Age 指定存活时间
- Access-Control-Allow-Headers 指定contentType
- Access-Control-Allow-Credentials 当客户端设置credential为true时,这个值也必须设为true,否则会影响cookie的发送
具体流程
- 检查请求报文的origin与预先设定的origin是否相同,如果相同,则执行步骤2否则认为这是一次非法访问,直接返回
- 检查请求报文的头部method是否是Options,如果是Options或者是一次简单请求且未设置过head,则执行步骤3,否则认为头部信息已经设置过了,没有必要重复设置头部信息
- 为响应报文添加相应的head
具体代码
整体基于拦截器实现
import org.apache.commons.lang.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 这里是跨域逻辑
*/
public class CorsFilter implements Filter {
/**
* 允许调用server http服务的client
* http://localhost:9528 is true
* http://localhost:9528/ not true
*/
private String origin = "http://localhost:9528";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (!isLegalRequest(request)) {
writeErrorMessage(request, response);
return;
}
handleOptions(request, response);
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* 处理options方法(http预检)
*
* @param request
* @param response
*/
private void handleOptions(HttpServletRequest request, HttpServletResponse response) {
if (!checkIsOptions(request)) {
//not options method or this request is a simple request and
//not set head
if(hasSetResponseHead(response)){
return;
}
}
//设置Http响应头
//允许客户端的origin访问服务器端
response.addHeader("Access-Control-Allow-Origin", origin);
//设置客户端可以使用的method,使用RestfulAPI
response.addHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
//设置客户端的存活时间1天,在存活时间内,不必再次触发options方法
response.addHeader("Access-Control-Max-Age", String.valueOf(86400));
//设置contentType,其中X-PING为自定义type
response.addHeader("Access-Control-Allow-Headers", "X-PING");
if (request.getCookies() != null) {
//client设置了credential,所以要将Access-Control-Allow-Credentials设为true
response.addHeader("Access-Control-Allow-Credentials", "true");
}
response.addHeader("Content-Length", "0");
response.addHeader("Content-Type", "text/plain");
}
/**
* 检查请求的合法性
*
* @param request
* @return
*/
private boolean isLegalRequest(HttpServletRequest request) {
String accessOrigin = request.getHeader("Origin");
return StringUtils.equals(origin, accessOrigin);
}
private boolean checkIsOptions(HttpServletRequest request) {
return StringUtils.equals(request.getMethod(), "OPTIONS");
}
private boolean hasSetResponseHead(HttpServletResponse response) {
return StringUtils.equals(response.getHeader("Access-Control-Allow-Origin"),
origin);
}
private void writeErrorMessage(HttpServletRequest request,
HttpServletResponse response) throws IOException {
response.addHeader("Content-Type", "text/plain");
PrintWriter writer = response.getWriter();
writer.print("This HTTP resource is not allowed for "
+ request.getHeader("Origin"));
writer.close();
response.flushBuffer();
}
}
拦截器的注册代码
import com.zacharytse.wangpan.filter.CorsFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
CorsFilter corsFilter = new CorsFilter();
registrationBean.setFilter(corsFilter);
List<String> urls = new ArrayList<>();
urls.add("/*");
registrationBean.setUrlPatterns(urls);
return registrationBean;
}
}
测试接口代码如下
@GetMapping("/test_cors")
@ResponseBody
public String testCors() {
return "success";
}
前端使用axios做测试(前端url为http://localhost:9528,也就是请求报文中的origin)
Axios({
method:'Get',
url:'http://localhost:9000/test_cors',
contentType:'application/json;charset=UTF-8'
}).then(function(response){
console.log(response)
})
发现可以成功访问