AJAX跨域访问问题详解

12 篇文章 1 订阅
2 篇文章 0 订阅

AJAX 跨域访问

明确跨域问题产生的原因

首先了解一个概念:浏览器的同源策略

AJAX同源策略

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

同源的定义

如果两个 URL 的 protocolport (en-US) (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。(“元组” 是指一组项目构成的整体,双重/三重/四重/五重/等的通用形式)。

当我们使用前端通过浏览器对后台接口发起请求的时候,浏览器判断我们的请求的目标接口所在主机和本机的协议、域名(ip)、端口号是否相同,当三者有一个不同时,发生请求跨域错误这就是我们所说的请求跨域问题。

参照如下:

我们以http://origin.test/home为源,源向下列URL发起请求的结果进行对比:

注:源的协议为http,域名为origin.test,端口号为80。

目标URL结果原因
http://target.test/home失败协议、端口号相同,域名不同,不满足三要素同时成立的条件。域名不同导致浏览器判断其来自不同主机。
http://origin.test:8081/home失败协议、域名相同,端口号不同,不满足三要素同时成立的条件。源的端口号为默认端口80。
https://origin.test/home失败域名、端口号相同,协议不同,不满足三要素同时成立的条件。源的协议为http,目标URL的协议为https。(在自测接口的时候需要注意此点,有时浏览器会默认为我们加上https)
https://origin.test/index/page.html成功满足域名、端口号、协议同时相同。
https://origin.test/home/user.html成功满足域名、端口号、协议同时相同。

跨域问题的解决

在JavaEE的开发中,我们通过在Sping的配置类写入如下代码得以避免跨域问题:

    /**
     * 跨域配置
     * @return
     */
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            //重写父类提供的跨域请求处理的接口
            public void addCorsMappings(CorsRegistry registry) {
                //添加映射路径
                registry.addMapping("/**")
                        //放行哪些原始域
                        .allowedOriginPatterns("*")
                        //是否发送Cookie信息
                        .allowCredentials(true)
                        //放行哪些原始域(请求方式)
                        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                        //放行哪些原始域(头部信息)
                        .allowedHeaders("*")
                        //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                        .exposedHeaders("Header1", "Header2");
            }
        };
    }

主要是在干一件事:为API所在的服务器设置允许哪些Origin服务器访问自己——.allowedOriginPatterns("*")

项目中遇见的问题

在前后端分离项目中,设置拦截器(Interceptor)实现登录拦截,LoginInterceptor内容如下:

注:拦截策略配置中,拦截的范围是/**

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HandlerMethod targetMethod = (HandlerMethod) handler;
        if (!targetMethod.hasMethodAnnotation(CheckToken.class)){
            return true;
        }
        
        String token = TokenUtil.getToken(request);
        if (token == null) {
            throw new LoginException("未登录,请登录后再操作。");
        }

        UserInfo userInfo = redisService.getUserByToken(token);
        if (userInfo == null) {
            redisService.removeToken(token);
            throw new LoginException("登录超时,请重新登录。");
        }
        return true;
    }
}

不用看拦截器preHandle方法中代码具体的内容,我们的问题是,在配置了拦截器后,前端对后端的接口发起请求,出现了跨域错误,且相同的请求浏览器发起了2次,两次请求的类型都不同,如下:
在这里插入图片描述

因为前端发起的是Ajax请求,类型是xhr,但浏览器发起的相同的请求是preflight类型,且点入Request发现其请求方式是OPTIONS,所以猜测其访问的并不是Controller的方法。
在这里插入图片描述

解决方案

解决此问题的方法是:在拦截器中加上判断handler类型是否不属是HanlderMethod的代码,如果不属于HanlderMethod,说明其不是要访问我们的控制器方法,直接将其放行。

if (!(handler instanceof HandlerMethod)) {
	return true;
}

修改后的LoginInterceptor拦截器如下:

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
        // 如果是预请求,则放行
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        HandlerMethod targetMethod = (HandlerMethod) handler;
        if (!targetMethod.hasMethodAnnotation(CheckToken.class)){
            return true;
        }
        
        String token = TokenUtil.getToken(request);
        if (token == null) {
            throw new LoginException("未登录,请登录后再操作。");
        }

        UserInfo userInfo = redisService.getUserByToken(token);
        if (userInfo == null) {
            redisService.removeToken(token);
            throw new LoginException("登录超时,请重新登录。");
        }
        return true;
    }
}

浏览器判断跨域的具体步骤

君子不器。既然解决了问题,我们就要明白其中的原理,不应拘泥于手段而不思考其背后的目的。

浏览器发起请求会用按照如下顺序顺序进行操作:

  1. 发起请求前,将目标URL的协议、域名、端口号和源依据同源对比策略进行对比(同源对比策略的演示上面已经阐述的很清楚)。
  2. 若同源,则直接发送数据请求,获取响应内容,操作完成。
  3. 若不同源,判断此AJAX请求为一个跨域请求:
  4. 此时浏览器会再发起一个OPTIONS请求,类型是preflight,其他的内容目标请求一样,确认目标URL所在服务器是否允许源所在服务器进行跨域访问,这个OPTIONS请求仅仅是为了确认是否允许跨域,不会真正访问Controller中的方法
  5. 如果目标URL所在服务器不进行任何响应,则判断目标服务器不允许跨域,出现跨域错误。
  6. 如果目标URL所在服务器响应的内容是不允许当前服务器进行跨域,则判断目标服务器不允许跨域,出现跨域错误。

这就是我们解决方案的为什么要判断handler类型是否不属是HanlderMethod的原因。

参考资料

浏览器的同源策略

彻底理解浏览器的跨域

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

君去何方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值