ajax跨域问题
1.为什么会出现ajax跨域
ajax跨域问题的出现本身跟后台服务无关,而是浏览器行为,是因为该ajax请求违反了浏览器的同源策略从而导致的。
ajax跨域的三个原因,只有同时满足了才会发生ajax跨域问题:
1.1浏览器限制:
即使请求被后台正确处理,返回了正确的结果,但是没有通过浏览器的安全检查而错误,这是浏览器对ajax请求的限制。
1.2 跨域
调用和被调用的接口不在同域,而同域就是说协议、域名、端口号三者都要相同,只要其一不同,都为跨域。
1.3 xmlhttprequest(xhr)请求
如何请求不是xmlhttprequest请求,即使跨域,也不会出现ajax跨域问题。
解决ajax跨域的思路
既然需要满足三个条件才会出现ajax跨域问题,那么只要打破其中之一条件就行。
首先:
针对“浏览器限制”问题,可以通过浏览器端关闭相关的安全检查即可,但是对于要求用户做这种繁琐又不友好的操作,显得有些不切实际,可行性太差,基本放弃。
然后:
针对“xhr请求”问题,通常的做法是通过jsonp的方式实现,jsonp不是一种标准协议,可以说是一种约定,通过这种约定,前后台可以根据这种约定来正确的传输数据。jsonp方式就是改变请求方式,通过动态的创建一个script标签,发送请求,带上规定的callback参数,后台就可以根据该参数来识别该请求为jsonp,从而正确处理,然后返回一个带相应内容的js函数。jsonp现在已经无法满足开发需求,用的比较少了,因为服务端需要修改代码、只支持get请求、不是xmlhttprequest请求(xhr支持很多新的特性就没有了),最终还是倾向于解决跨域问题。
最后:
针对“跨域”问题,可以从两个方面出发:1.如果可以修改被调用方代码,可以让被调用方支持ajax跨域。2.也可以通过调用方实现隐藏跨域,隐藏跨域可以通过代理的方式实现,代理服务器和调用方在同域内便可。
解决方法
1. 禁用浏览器限制
通过命令行参数禁用web安全检查启动浏览器。
2.使用jsonp
josn方式使用过在ajax请求中设置dataType:”jsonp”来实现前端的设置,后台也要做相应的改动,以针对jsonp做相应的改动,否则返回的json对象是不能解析。
3.跨域实现
3.1 被调用方支持跨域
3.1.1 服务器端实现
filter解决方案:
浏览器的跨域安全检查顺序?
–简单请求是先执行后检查,非简单命令是先检查后执行,通过首先发送请求类型为OPTIONS的预检请求,判断是否安全,在发送实际请求。
浏览器是怎么判断跨域?
–通过添加请求头(Origin:http://localhost:8080),如果没有响应头没有(Access-Control-Allow-Origin),就会出现跨域安全问题。
filter解决?
–通过创建过滤器,对响应添加响应头(Access-Control-Allow-Origin)实现支持跨域。
出现ajax跨域问题如下图
服务端返回正确处理,后浏览器进行判断
浏览器通过判断响应头中是否含有(Access-Control-Allow-Origin),没有则判断为跨域问题
使用filter解决方案,通过增加响应头
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SpringbootDemo1Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemo1Application.class, args);
}
/**
* 注册过滤器
* @return
*/
@Bean
public FilterRegistrationBean registFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.addUrlPatterns("/*");
bean.setFilter(new ScroFilter());
return bean;
}
}
过滤器实现类,添加具体响应头
package com.example.demo;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
public class ScroFilter implements Filter {
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse res= (HttpServletResponse) response;
res.addHeader("Access-Control-Allow-Origin", "*");
res.addHeader("Access-Control-Allow-Mothods", "*");
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
启动服务,刷新浏览器,重新请求
成功添加了响应头,ajax跨域问题得到解决!
如果带cookie的跨域就不能这么做了,而且Access-Control-Allow-Origin:*是不允许的;
package com.example.demo;
import java.awt.PageAttributes.OriginType;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ScroFilter implements Filter {
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse res= (HttpServletResponse) response;
//可以判断跨域来获取调用方地址
HttpServletRequest req = (HttpServletRequest) request;
//获取origin
String origin = req.getHeader("Origin");
if (origin.length() != 0) {
//带cookie时,Access-Control-Allow-Origin必须是全匹配,不能是*
res.addHeader("Access-Control-Allow-Origin", origin);
}
res.addHeader("Access-Control-Allow-Mothods", "*");
res.addHeader("Access-Control-Allow-Headers", "Content-Type");
//缓存预检命令,否则非简单请求每次都会调用预检命令
res.addHeader("Access-Control-Max-Age", "3600");
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
如果带有自定义请求头,则响应头要设置为
package com.example.demo;
import java.awt.PageAttributes.OriginType;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers;
public class ScroFilter implements Filter {
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse res= (HttpServletResponse) response;
//可以判断跨域来获取调用方地址
HttpServletRequest req = (HttpServletRequest) request;
//获取origin
String origin = req.getHeader("Origin");
if (origin.length() != 0) {
//带cookie时,Access-Control-Allow-Origin必须是全匹配,不能是*
res.addHeader("Access-Control-Allow-Origin", origin);
}
//带自定义请求头
String headers = req.getHeader("Access-Control-Allow-Headers");
if (headers.length() != 0) {
res.addHeader("Access-Control-Allow-Headers",headers);
}
res.addHeader("Access-Control-Allow-Credentials", "true");
res.addHeader("Access-Control-Allow-Mothods", "*");
//缓存预检命令,否则非简单请求每次都会调用预检命令
res.addHeader("Access-Control-Max-Age", "3600");
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
2 niginx实现
Nginx实现是将前台请求通过http处理,服务端不需要处理响应头。Nginx的配置如下
server
{
listen 80;
server_name 172.0.01;
location /{
proxy_pass http://localhost:8080/;
add_header Access-Control-Allow-Mothods *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Headers $http_access_control-request-headers;
if ($request_method = OPTIONS){
return 200;
}
}
}
3.1.3 Apache实现
原理跟Nginx差不多,把相关的响应头配置在Apache的配置文件中配置即可。
3.1.4 spring框架实现
只需要在controller层添加@CossOrigin注解即可,可注解在类和具体的方法上。
3.2 调用方隐藏跨域
通过在前端使用反向代理(nginx)实现,比如:使用a.com访问前台服务,a.com/ajax访问后台服务,对于浏览器来说请求在是同域。
server
{
listen 80;
server_name a.com;
location /{
proxy_pass http://localhost:8080/;
}
location /ajax{
proxy_pass http://localhost:8081/test;
}
}
同理,Apache的配置也差不多。