最近遇到跨域问题,以下为多服务与单服务下的跨域问题解决方案~
一.基于zuul网关解决跨域问题
1.跨域问题描述:
本人在基于Oauth2.0做鉴权/授权时,配合前端联调接口时,已经让前端请求时的请求头带上key:Authorization,但是前台报错为跨域问题,后台报错鉴权失败,于是跟踪错误,发现在网关处从header中一直拿不到这个 Authorization 这个属性的值(因为oauth 2的密码模式是需要请求头中带有token才能够完成鉴权),具体错误如下图所示:
上图中可以明显看到请求中是存在 Authorization 这个属性的,但是从后台图片看是并没有拿到这个属性的:
2.问题分析经过:
本人继续向下查找原因,发现通过 HttpRequest的api:getHeaderNames()可以找到请求头中的所有header属性,如下图:
于是猜想是否有一层转换是将请求头中所有的key属性转化了小写,于是继续尝试 request.getHeader(“authorization”),但是结果还是出乎意料,返回结果也是空,于是开始从头理思路:
从前台开始报错为跨域问题,查看发生错误请求的类型为Preflighted,这让本人感到疑惑,于是查阅资料:
查阅带预检(Preflighted)的跨域请求跨域原因有如下两种:
1). 除GET、HEAD和POST(only with application/x-www-form-urlencoded, multipart/form-data, text/plain Content-Type)以外的其他HTTP方法。
2). 请求中出现自定义HTTP头部。
以上摘自博客链接
此时豁然开朗,因为基于Oauth2的协议需要在请求中带上 Authorization 及其对应的token,所以自定义了请求头,且请求方不同源,所以浏览器(本次问题使用google浏览器)会发送两次请求:
1). 带预检(Preflighted)的跨域请求,此时请求类型为OPTIONS
2). 带真实数据的请求,此时才是我们真实想要的GET/POST请求
3.验证问题猜想:
如下图,断点处请求类型确实为 OPTIONS
4.解决方案:
既然已经明确了问题所在,解决方案便随之而出了,由于本人是基于zuul网关做的鉴权/授权,所以解决方案也是基于zuul,仅提供参考:
1). 配置跨域过滤器,优先级最高,生效在路由前,判断如果是带预检的请求,则在响应头中放入允许跨域相关的属性,然后不再向下路由,具体代码如下:
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class CrossFilter extends ZuulFilter {
/**
* pre:可以在请求被路由之前调用
* route:在路由请求时候被调用
* post:在route和error过滤器之后被调用
* error:处理请求时发生错误时被调用
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 优先级为0,数字越大,优先级越低
*/
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
//只过滤OPTIONS 请求
if (request.getMethod().equals(RequestMethod.OPTIONS.name())) {
return true;
}
if (!ctx.sendZuulResponse()) {
return false;
}
return false;
}
@Override
public Object run() {
// logger.info("*****************FirstFilter run start*****************");
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse response = ctx.getResponse();
HttpServletRequest request = ctx.getRequest();
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "Authorization, content-type, token, myauth, myAuth, userId, userid, RefreshToken");
response.setHeader("Access-Control-Allow-Methods", "POST,GET");
response.setHeader("Access-Control-Expose-Headers", "X-forwared-port, X-forwarded-host");
response.setHeader("Vary", "Origin,Access-Control-Request-Method,Access-Control-Request-Headers");
//不再路由
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(200);
return null;
}
}
二. 另外附单体服务解决跨域方案
以下需要注意的点为 @Order(Integer.MIN_VALUE) ,这行代码的意思是将此过滤器的作用在所有过滤链最前端,至于原因本人会单独开一篇博客讨论。
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
@Order(Integer.MIN_VALUE)
public class CrossFilter implements Filter {
@Override
public void doFilter(ServletRequest request1, ServletResponse response1, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) request1;
HttpServletResponse response = (HttpServletResponse) response1;
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "Authorization, content-type, token, myauth, myAuth,userId , userid,RefreshToken");
response.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS");
response.setHeader("Access-Control-Expose-Headers", "X-forwared-port, X-forwarded-host,Content-Disposition");
response.setHeader("Vary", "Origin,Access-Control-Request-Method,Access-Control-Request-Headers");
if (!request.getMethod().equals(HttpMethod.OPTIONS.name())) {
chain.doFilter(request, response);
}
}
}