由一个浏览器跨域问题引发的思考

1、问题背景:

       公司在做自己的OA系统,采用的前后端分离的开放模式,在与前端对接"获取当前用户信息"这个接口时,浏览器的控制台就报了以下错误:

Access to XMLHttpRequest at 'http://192.168.0.101:10000/api/login/login?username=1&password=1' from origin 'http://localhost:63342' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

我们采用token来维护用户登录状态的方案,用户登录成功后,以后每次需要登录的接口都需要在header中带着这个token,后端用springMVC的拦截器做登录拦截,一切看着都是这么的自然,然而......

        第一个对接的是"登录接口",很顺利的通过了。当前端调用第二个接口——"获取当前用户信息"接口时,突然前端小姐姐叫了一声:"哎?这是什么错误?是不是跨域了?"。我当时心想:"不可能呀,我在搭框架的时候已经在后端处理了跨域问题"。下面是我的解决方式:

 @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") //表示所有的资源都允许跨域访问
                .allowedOrigins("*")   //表示所有的源都可以访问
                 表示允许那些http请求方式
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") 
                .allowCredentials(true)  //表示请求中可以携带cookie
                .maxAge(3600)    //表示预检请求的有效期,有效期内不会在发送第二次预检请求。
                .allowedHeaders("*");  //表示允许所有请求头
    }

标准的CORS解决方式,也确实能解决跨域问题,但是并不适合我这里的场景,为什么?因为我使用了拦截器。为什么使用了拦截器就不行了?下面听我分析一下:

2、问题原因:

        为什么第一个登录接口没事,第二个就有跨域问题?因为第一个登陆接口我没做登录拦截,会执行到上面的方法(此时该登录请求是一个简单请求)。第二个接口做了登录拦截,##但我在请求头中加入了token值,还是应该可以通过拦截器到达目标方法,也会执行上面的跨域解决方法,为什么还是被拦截出现了跨域问题呢?##OK,问题就出现在这里,就是因为我在请求头中加入了token,导致了该请求从一个简单请求变成了非简单请求,至于什么是非简单请求暂时先不说?但他有一个区别于简单请求的最大区别:会在正式请求发送之前,先发送一个预检请求,就是个普通请求,用于检查目标服务器是否允许当前源跨域访问。该预检请求可是什么参数都不带的,自然就被我的登录拦截器给拦下了,然后回去告诉客户端浏览器说目标服务器不允许你跨域访问,浏览器说了声,哦~,然后就向前端小姐姐抛了一个错误,可是把小姐姐难住了~~

3、解决方案:

        既然该预检请求会被拦截器拦截,导致无法执行我解决跨域的方法,那我只需要在拦截器拦截之前,给请求的响应头加入允许跨域访问的响应头即可。springMVC的本质是servlet,当时学javaWeb时知道filter的执行时机是在servlet之前,那我使用filter来增加响应头就行了呗。

        自己写一个filter也行,但其实springMVC早就提供了这么一个解决跨域的filter——CorsFilter。若是springBoot项目的话,直接在配置类中加入该bean即可。

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");//允许所有的源访问
        config.setAllowCredentials(true);//允许请求中带着cookie
        config.addAllowedMethod("*");//允许哪些http请求方式
        config.addAllowedHeader("*");//允许请求头中额外带着哪些请求头
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);//表示所有的资源都允许跨域访问
        return new CorsFilter(configSource);
    }

其他的解决方式也有很多,这里我再提供几种后端解决跨域的方式:

1)自定义filter

@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        filterChain.doFilter(servletRequest, servletResponse);
    }

2)重写springMVC的addCorsMappings方法

@Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") //表示所有的资源都允许跨域访问
                .allowedOrigins("*")   //表示所有的源都可以访问
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") //表示允许那些http请求方式
                .allowCredentials(true)  //表示请求中可以携带cookie
                .maxAge(3600)    //表示预检请求的有效期,有效期内不会在发送第二次预检请求。
                .allowedHeaders("*");  //表示允许所有请求头
    }

3)使用@CrossOrigin注解:更细粒度的跨域访问控制方式

        注:可用在controller上或方法上,会覆盖全局的跨域配置

 

@Controller
@RequestMapping("/home")
@CrossOrigin(origins = "*", maxAge = 3600)
public class HomeController {

    @RequestMapping("/index")
    public String index(){
        return "index";
    }

}

 

以上四种方式,根据实际项目中拦截器的使用情况灵活选用,但还是最推荐filter的方式,因为最强大和通用。

 

4、相关知识

1)简单请求和非简单请求

浏览器将跨域请求分为简单请求和非简单请求。只要同时满足以下两大条件,就属于简单请求

(1) 请求方法是以下三种方法之一:
    HEAD
    GET
    POST

(2)HTTP的头信息不超出以下几种字段:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

以上任何一个条件不满足的,就属于非简单请求。非简单请求就会额外发送"预检请求"。

2)跨域及产生的原因

     跨域的定义:即通讯协议、ip、端口三个有任何一个不相同就被称为跨域。如http://localhost:9000/a.html,要去访问http://localhost:10000/api/getUser接口,就称之为跨域请求,因为两者的端口不一样。此时如果不做处理的话,两者是无法进行通信的。

    跨域产生的原因:就是浏览器的同源策略。该策略是在1995年由网景公司引入的,目前各浏览器都支持该策略。该策略的引入也是出于对浏览器访问的一种安全机制,具体例子这里就不细说了,大家去问度娘吧。

3)标准的解决的方案Cors

      Cors是W3C组织引入的,全称为(Cross-origin resource sharing)跨域资源共享,是用来解决浏览器跨域访问问题的一种标准的行业规范,目前各大浏览器都支持Cors。

     Cors的核心思想是:通过服务器端增加一系列的允许跨域的响应头,来解决跨域问题。

5、结束语

      好了,今天就到这里吧,能把以上部分搞明白,日常开发中的跨域问题基本上都能解决了。

     最后说一下我的心声:

           在我遇到本文开头的那个问题之前,我以为我已经能解决跨域问题了,直到解决那个问题之后,我才发现我所知道的也只是浮于表面的一些东西,更深入的东西还会有,只是我现在还没碰到而已。所以不要自满,不要自以为我最牛逼,别人都是傻逼,每个人都要他所擅长的地方,只是你还没发现而已。

      你知道的越多,你就知道的越少,这句话至少在计算机领域是很经典的。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值