本章我们主要讲解后端开发对于跨域问题的解决方案,下面我们先来了解一下跨域。
###跨域是什么?
跨域访问,简单来说就是 A 网站的 javascript 代码试图访问 B 网站时,浏览器出于安全考虑,自动阻止了对跨域服务的访问(包括对后端数据的增删改查请求),这里浏览器统一遵循了一种策略,这个策略就是同源策略,同源策略也是浏览器最核心、最基本的安全功能。
什么情况下会发生跨域?
前端页面地址 | 后端服务地址 | 存在跨域原因 | 备注 |
---|---|---|---|
http://www.zm.com | http://www.zhuma.com | 不同域名 | 主域相同,子域名不同也会出现跨域 |
http://www.zm.com | http://192.168.10.19 | 不同域名 | 域名和该域名对应ip也是不允许的 |
http://www.zm.com | https://www.zm.com | 不同协议 | |
https://www.zm:80.com | https://www.zm:8080.com | 不同端口 |
从上面的表格中我们可以看出,协议、域名、端口三者之间任意一与当前页面地址不同都会引起跨域问题。
###企业开发中出现跨域问题情况举例
- 前后端分离的的开发模式中,前端开发人员使用本地(http://localhost)去调用我们后端准备的联调环境接口服务(http://fe-api.zhuma.com) 。
- 当项目变得庞大,业务变得复杂,很可能会出现想要直接调用第三方的某些服务来完成业务功能,这时便出现了跨域问题。
解决方案
在我们的实际开发过程中,我们会使用 CORS方式,自己定义个拦截器 对开发、测试环境解决跨域问题,线上环境如果出现跨域问题会使用nginx做个代理中转的这种方式,这里说明下线上环境尽量保证 接口、页面 同一域名。
如果你的需求是只对某些特定的接口允许跨域访问,在springmvc项目开发中,你可以使用自定义注解+下面提到的拦截器的方式去处理,如果你还是不会写,可以在文章末尾留言或直接加公众号联系我O(∩_∩)O。
下面我们说下具体解决的代码逻辑:
- CORS方式
这是W3C提供的另一种跨域方式,作为一项标准的跨域规范,我们使用我们自己定义的拦截器进行统一设置实现解决测试环境上的跨域问题。下面我给出使用spirng boot时的相关代码。
拦截器主功能类
package com.zhuma.demo.comm.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.zhuma.demo.comm.enums.EnvironmentEnum;
import com.zhuma.demo.comm.util.StringUtil;
/**
* @desc 允许跨域拦截器
*
* @author zhumaer
* @since 6/20/2017 3:00 PM
*/
@Component
public class AllowCrossDomainInterceptor implements HandlerInterceptor {
@Autowired
private Environment env;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!EnvironmentEnum.isProdEnv(env)) {//仅在非正式环境下生效
String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", StringUtil.isEmpty(origin) ? "*" : origin);
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, PATCH");
response.setHeader("Access-Control-Max-Age", "0");
response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("XDomainRequestAllowed","1");
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// nothing to do
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// nothing to do
}
}
环境常量枚举类
public enum EnvironmentEnum {
PROD,//生产
FE,//联调
QA,//测试
STG;//仿真
public static boolean isProdEnv(Environment env) {
Assert.notNull(env, "env parameter not null.");
return EnvironmentEnum.PROD.name().equalsIgnoreCase(env.getProperty("spring.profiles.active"));
}
@Override
public String toString() {
return this.name();
}
}
拦截器配置类
package com.zhuma.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.zhuma.demo.interceptor.AllowCrossDomainInterceptor;
import com.zhuma.demo.comm.constants.Constants;
/**
* @desc WEB配置类
*
* @author zhumaer
* @since 6/23/2017 14:11 PM
*/
@Configuration
public class WebAppConfig extends WebMvcConfigurerAdapter {
@Autowired
private AllowCrossDomainInterceptor allowCrossDomainInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
String apiUri = "/" + Constants.API_ROOT + "/**";
//跨域拦截
registry.addInterceptor(allowCrossDomainInterceptor).addPathPatterns(apiUri);
}
}
-
后端服务做代理
使用nginx这种服务器,在后端做一个中转。本文我们不是重点讲这种方式,所以想了解的自行百度下吧。 -
JSONP
这种方式缺点是只能支持GET方法的请求,我看到一些公司为了支持JSONP将后端的添加、修改、删除功能接口都改为GET方式,这种是很不建议的,这样会存在安全隐患。如果你用的是spring boot的话可以参考下AbstractJsonpResponseBodyAdvice这个类的使用。
###最后
后面我们会在讲解几个关于拦截器在企业开发中的实战的例子,之后会讲解 统一登录校验、统一的参数校验、接口权限校验等。
源码github地址:https://github.com/zhumaer/zhuma
QQ群号:629446754(欢迎加群)
欢迎关注我们的公众号或加群,等你哦!