这里写自定义目录标题
ajax跨域请求完整解决方案
简介
在发送Ajax请求的时候,由于浏览器的同源策略,会对跨域请求进行检测。
什么情况属于跨域请求:
- 协议不同 ,例如http与https;
- 域名不同 ,例如www.a.com与www.b.com;
- 端口号不同 ,例如http://localhost:8080与http://localhost:8081;
以上三种情况符合一种即为跨域请求。
跨域请求解决方案
JSONP
jsonp实现跨域请求的原理简单的说就是动态台创建script标签,然后利用script的src不受同源策略约束来跨域获取数据(script中src请求静态资源例如图片等都不受同源策略约束
jsonp由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的json数据。
前端ajax请求
function getinfo(){
$.ajax(
{
url: "http://127.0.0.1:8082/test/submit",
type: "GET",
contentType: "jsonp",
data:JSON.stringify({
"username": username,
"password": password
}) ,
jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
success:function (data) {
alert(data["username"]);
},
error:function (data) {
alert("请求失败")
}
}
)
}
//也可以在回调函数中获取返回值与ajax中的success的函数作用相同
function flightHandler(){
alert(data.username);
}
Java后台代码
我后台用的spring框架,因此新建一个类添加@ControllerAdvice注解用于操作controller层
(PS:spring boot2.0以后不推荐使用jsonp,因此没有AbstractJsonResponseBodyAdvice类)
@RestControllerAdvice
public class JsonpAdvice extends AbstractJsonResponseBodyAdvice{
public JsonAdvice(){
super("callback");//参数与前端ajax请求中jsonp参数相同
}
}
需要注意的一点:使用jsonp时只能发送get请求,但数据量大时jsonp方法就不适用了,因此可以采用cros方法
CORS
cros是W3C颁发的一个标准,全称是“跨域资源共享”。它允许浏览器向跨院服务器发出XMLHttpRequest请求,在响应头header中添加Access-Control-Allow-Origin,当浏览器检测到header中的Access-Control-Allow-Origin就可以进行跨域操作,从而克服了ajax只能同源使用的限制。
浏览器将cors请求分为两类:简单请求和非简单请求
简单请求:
请求方法是以下三种方法之一:
- HEAD
- GET
- POST
HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencode、multipart/form-data、text/plain
如果上述两个条件没有同时满足则属于非简单请求
被调用方代码实现
下述代码中我将简单请求和非简单请求,携带自定义代码头,携带cookie等信息整合在一起,大家可以按照自己的需求删减部分代码
ajax代码
function submit() {
var username = $("input[name='username']").val();
var password = $("input[name='password']").val();
$.ajax(
{
url: "http://127.0.0.1:8082/test/submit",
type: "POST",
contentType: "application/json;charset=utf-8",
headers:{
"age":"18", //第一种方式添加自定义的请求头
},
data:JSON.stringify({
"username": username,
"password": password
}) ,
beforeSend:function (xhr) {
xhr.setRequestHeader("grade","大学");//第二种方式添加自定义的请求头
xhr.withCredentials=true;//cros跨域请求时默认是不携带cookie,将此参数设置true标准表示浏览器需要获取被调用服务的cookie
xhr.domain=true;
},
success:function (data) {
alert(data["username"]);
},
error:function (data) {
alert("请求失败")
}
}
)
}
实现cros,需要修改被调用方的后台代码,在后台中添加过滤器
@WebFilter("/*")
@Component
public class AjaxFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
Cookie cookie = new Cookie("cookie1","11111" );
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
httpServletResponse.addCookie( cookie );//在返回信息中添加cookie用于测试
String origin = httpServletRequest.getHeader( "Origin" );//获取浏览器中请求跨域的源
if(!StringUtils.isEmpty( origin ))
{
httpServletResponse.setHeader( "Access-Control-Allow-Origin",origin );//设置请求源
}
String headers = httpServletRequest.getHeader("Access-Control-Request-Headers");//获取浏览器中自定义添加的头信息
if(!StringUtils.isEmpty( origin ))
{
httpServletResponse.setHeader("Access-Control-Allow-Headers", headers);//设置请求头
}
httpServletResponse.setHeader( "Access-Control-Allow-Methods","*" );// *表示接受所有类型的请求,也可以写入实际需要的请求,但是经过实际测试后发现这行代码不起作用。
httpServletResponse.setHeader( "Access-Control-Allow-Credentials","true" );// 表示允许携带cookie
httpServletResponse.setHeader( "Access-Control-Max-Age","3600" );//预检请求的有效期
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
我们能看到第一次发送请求时一共发出两条请求,其中第一条OPTIONS为我们的预检请求,及检测我们的请求是否符合跨域要求
OPTIONS中的请求头和响应头
如果我们设置了Access-Control-Max-Age,请求信息会被缓存,当第二次请求时,会看到网络中只有一个POST请求命令
也可以使用spring注解在controller层加入@CrossOrigin注解,以上步骤都不用配置可直接实现跨域请求,很方便吧!
nginx实现
和代码实现的原理相同在配置文件中添加:
location / {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Methods '*';
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers $http_acess_control_request_headers;
if ($request_method = OPTIONS) {
return 200;
}
}
隐藏跨域
在nginx中通过反向代理将跨域请求映射到同一域下实现隐藏跨域
server{
listen 80;
server_name a.com;
location/{
proxy_pass http://localhost:8080/;
}
location /ajaxserver{
proxy_pass http://localhost:8081/hello.html
}
}
通过反向代理将8080和8081端口映射为同一域名的80端口,这样就不存在不同源的情况,及可实现隐藏跨域