熟悉 web 系统开发的同学,对下面这样的错误应该不会太陌生。
之所以会出现这个错误,是因为浏览器出于安全的考虑,采用同源策略的控制,防止当前站点恶意攻击 web 服务器盗取数据。
同源策略,简单的说就是当浏览器访问 web 服务器资源时,只有源相同才能正常进行通信,即协议、域名、端口号都完全一致,否则就属于跨域请求。当发起跨域请求时,服务端是能收到请求并正常返回结果的,只是结果被浏览器拦截了。
像上文中,浏览器访问的站点是http://127.0.0.1:8848/
,而站点内发起的接口请求源是http://localhost:8080
,因为不同源,所以报跨域请求异常。
由此可见,想要实现接口请求的正常访问,浏览器的访问站点源和接口请求源,必须得一致。
事实上,在现在流行的前后端分离的开发模式下,很难做到请求源高度一致,那怎么办呢?
答案肯定是有办法啦!
虽然浏览器出于安全的考虑,默认采用同源策略控制,以便减少服务器被恶意攻击的机会,但是开发者可以通过CORS
协议在浏览器内实现站内跨域请求访问。
实现很简单,通过在 web 服务器中增加一个特殊的Header
响应属性来告诉浏览器解除跨域的限制,如果浏览器支持CORS
并且判断允许通过的话,此时发起的跨域请求就可以正常展示了。
常用的 Header 响应属性如下:
采用过滤器的方式来实现所有接口支持跨域请求,是一种比较通用的做法,也是 Java web 项目中常用的方法,实现过程如下!
首先,创建一个实现自Filter
接口的过滤器,示例如下:
public class CrossFilter implements Filter {
/**
* 允许跨域的白名单域名
*/
private final static Set<String> ALLOW_DOMAINS = new HashSet<>();
static {
ALLOW_DOMAINS.add("http://127.0.0.1:8848");
}
@Override
public void init(FilterConfig config) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
// 获取客户端原始请求域
String origin = request.getHeader("Origin");
String originDomain = removeHttp(origin);
if(ALLOW_DOMAINS.contains(originDomain)){
// 在响应对象中,添加CROS协议相关的header属性
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,HEAD,PUT,PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,Authorization,authorization");
response.setHeader("Access-Control-Allow-Credentials","true");
}
//继续往下传递
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {}
/**
* 移除http协议头部
* @param url
* @return
*/
public static String removeHttp(String url){
return url.replace("http://", "").replace("https://", "");
}
}
接着,将其注册到Servlet
容器中,示例如下:
@Configuration
public class FilterConfig {
/**
* 添加CrossFilter过滤器
* @return
*/
@Bean
public FilterRegistrationBean crossFilterBean() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setName("crossFilter"); // 指定过滤器名称
registration.setFilter(new CrossFilter()); // 指定过滤器实现类
registration.setUrlPatterns(Collections.singleton("/*"));// 指定拦截路径
registration.setOrder(1);// 指定顺序
return registration;
}
}
最后,启动服务后,再到浏览器中发起跨域请求,看看效果如下。
从结果上看,浏览器成功进行了跨域请求,并展示了服务器返回的结果。
在 Spring Boot 应用,除了采用过滤器的方式实现跨域访问外,我们还可以通过全局配置类实现跨域访问。
实现方法也非常简单,只需要重写WebMvcConfigurer
接口中的addCorsMappings
方法即可,示例如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.maxAge(3600)
.allowedHeaders("Origin", "Accept", "Content-Type", "Authorization")
.allowCredentials(true);
}
}
其中allowedOrigins("*")
表示对所有请求都允许跨域访问。
某些场景,如果不希望所有的接口都能跨域访问,只想在部分接口上放开跨域访问。此时,可以通过 Spring Boot 提供的@CrossOrigin
注解,在对应的方法上加上该注解,即可实现跨域访问。
示例如下:
@RestController
public class UserController {
@Autowired
private UserService userService;
@CrossOrigin
@PostMapping(value = "/queryAll")
public List<User> queryAll(){
List<User> result = userService.queryAll();
return result;
}
}
如果使用在controller
类上,表示当前类下的所有接口方法都允许跨域访问。
同时,@CrossOrigin
注解也支持设置更小的粒度,示例如下:
@CrossOrigin(origins = "http://domain.com", maxAge = 1800)
更多的属性行为,内容如下:
最后总结一下,在 Spring Boot 服务中可以通过过滤器或者配置类实现全局跨域访问,也可以通过@CrossOrigin
注解实现局部跨域访问。
跨域访问的配置,更适合在开发环境中方便前后端进行联调对接。为了安全起见,在上生产的时候,建议将其关闭掉或者做限制。
最后,送大家一份Java 生态知识体系/面试必看资料还在找工作的小伙伴看过来啦!好多读者都问我有没有面试题神器,我苦苦整了一份内部资料,Java 生态知识体系/面试必看资料!如果你想获得完整PDF可以通过以下方式获得喜欢就分享
面试大全怎么获取:
1.加我微信备注【Java极客技术】 2.后台回复【pdf】即可。
支持就在看
一键四连,你的offer也四连