Sping配置跨域详解

目录

跨域

一、概念

二、同源策略

三、违反规则

四、解决方案

跨域问题CORS解决方案

方案一:控制器加特定请求头【Access-Control-Allow-Origin】

方案二:注解@CrossOrigin

*方案一和方案二的优缺点

方案三:拦截器加特定请求头【Access-Control-Allow-Origin】

*方案三有一个简化方法,就是直接在【spring-mvc.xml】配置cors,如方案四

方案四:配置【mvc:cors】

(1)跨域请求默认无法携带凭证,不需要携带凭证的情况下

(2)如果需要携带凭证(如Cookie),那么除了前端需要做相应配置外,后端也要做修改

*配置cors后,若因业务需要再增加拦截器(如令牌检验)后,跨域问题会重新出现

Spring加拦截器后重新出现的跨域问题

案例一:拦截器验证请求头(如Token)

出现问题

分析

解决方案

案例二:在案例一基础上,需要携带凭证(如Cookie)

总结

跨域预检请求相关请求头

跨域预检请求相关响应头

参考资料


跨域

前后端分离项目时,首先浏览器就会报如下错误

Access to XMLHttpRequest at 'http://localhost:54321/projectName' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

一、概念

  • 违反同源策略现象被称为跨域

二、同源策略

浏览器的一种保护措施,主要用于预防浏览器通过JS代码进行跨站点的访问操作。

三、违反规则

  • 链接(url)三要素其中一个不同均为违反同源策略

  • url: 协议://主机地址:端口

协议://主机地址:端口
例:
http://localhost:8080
​
http://localhost:8080 与 https://localhost:8080   --协议不同
http://localhost:8080 与 http://127.168.0.2:8080  --主机地址不同
http://localhost:8080 与 http://localhost:8088    --端口不同

四、解决方案

  • JSONP(JSON with Padding):一些标签比如<scripti>,<link>,<a>,<img>,这样的获取静态资源的标签是没有跨域限制的

  • CORS(Cross-Origin Resource Sharing):即跨域资源共享。是一种基于HTTP头的机制,该机制通过允许服务器标示除了它自己以外的其他源域、协议或端口),使得浏览器允许这些源访问加载自己的资源

跨域问题CORS解决方案

方案一:控制器加特定请求头【Access-Control-Allow-Origin】

@RequestMapping("/pathDemo")
public String methodDemo(HttpServletResponse response){
    response.setHeader("Access-Control-Allow-Origin","*");
    //*表示所有人都可以,也可以设置成固定站点
    return "success";
}

方案二:注解@CrossOrigin

/**
* @CorssOrigin 注解相当于自动给每个响应加上方案一的特定请求头
* 该注解只能用在Controller中
*/
​
@RestController
@CrossOrigin //加在此处,整个控制器所有方法都生效
public class TestController{
    @RequestMapping("/pathDemo")
    @CrossOrigin //加在此处,当前方法生效
    public String methodDemo(){
        return "success";
    }
}

*方案一和方案二的优缺点

  • 优点:随用随加,使用上比较灵活

  • 缺点:解决跨域问题的范围太小

方案三:拦截器加特定请求头【Access-Control-Allow-Origin】

  • 在interceptor包中创建类

/**
* 拦截器类
*/
​
public class CorsInterceptor implements handlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest requst, HttpServletResponse response, Object Handler)throws Exception {
        response.setHeader("Access-Control-Allow-Origin","*");
    }
}
  • 在【spring-mvc.xml】中配置创建的拦截器

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <!-- /**代表所有路径,及其下的所有子路径-->
        <mvc:exclude-mapping path="/log"/>
        <!-- 使用exclude-mapping 排除登录请求 -->
        <bean id="corsInterceptor" class="org.w.interceptor.CorsInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

*方案三有一个简化方法,就是直接在【spring-mvc.xml】配置cors,如方案四

方案四:配置【mvc:cors】

(1)跨域请求默认无法携带凭证,不需要携带凭证的情况下

<!-- 全局配置 -->
    <!-- 其中:
        allowed-origins 为允许的访问源地址(*为所有请求)
        allowed-methods 为允许访问方式
	    allowed-headers 为允许携带的请求头
    -->
    <mvc:cors>
        <mvc:mapping path="/**"
                     allowed-origins="*"
                     allowed-methods="*"
                     allowed-headers="*"
        />
    </mvc:cors>

(2)如果需要携带凭证(如Cookie),那么除了前端需要做相应配置外,后端也要做修改

<!-- 全局配置 -->
    <!-- 其中:
        allowed-origins 为允许的访问源地址(此处不能使用*,而是"")
		allowed-origin-patterns 为自动将访问源地址加入允许列表中(即响应请求头中)
        allow-credentials 为允许携带凭证(如cookies)(允许时origins不能为*)
    -->
    <mvc:cors>
        <mvc:mapping path="/**"
                     allowed-origin-patterns="*"
                     allow-credentials="true"
                     allowed-origins=""
                     allowed-headers="*"
        />
    </mvc:cors>

*配置cors后,若因业务需要再增加拦截器(如令牌检验)后,跨域问题会重新出现

Spring加拦截器后重新出现的跨域问题

案例一:拦截器验证请求头(如Token)

出现问题

  • 登录请求可以正常访问,而其他被拦截的请求全部报错

  • 拦截器收到的请求头header里token为null

  • 在拦截器处理token为null时不会返回非200状态码时,前端浏览器仍然返回CORS:ERR错误

分析

  • 查看浏览器发送的请求,会发现浏览器发送了两个请求,其中一个是不带token的OPTIONS请求

  • 查看CORS介绍后发现,原来CROS复杂请求时会首先发送一个OPTIONS请求做嗅探,来测试服务器是否支持本次请求,请求成功后才会发送真实的请求;

  • 而OPTIONS请求不会携带任何数据,导致这个请求不符合我们拦截器的校验规则被拦截了,直接返回了状态码,响应头中也没携带解决跨域需要的头部信息,进而出现了跨域问题

  • 将拦截器先关闭,查看正常情况下(即仅仅配置了mvc:cors)发出的预检请求如下:

Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Access-Control-Request-Headers: token
Access-Control-Request-Method: GET
Cache-Control: no-cache
Connection: keep-alive
Host: localhost:54321
Origin: http://localhost:8080
Pragma: no-cache
Referer: http://localhost:8080/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36

  • 后端配置的cors跨域会自动回复响应如下:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: token
Access-Control-Allow-Methods: GET,HEAD,POST
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Max-Age: 1800
Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
Connection: keep-alive
Content-Length: 0
Date: Sat, 09 Mar 2024 15:13:36 GMT
Keep-Alive: timeout=20
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers

  • 由此可以看出,CORS的预检请求是通过请求中携带了一些特定的请求头(如Access-Control-Request-Headers,Access-Control-Request-Method,Origin)来告知服务器,在正式请求中,将会携带的请求头、请求方式、以及请求源。

  • 而响应中,必须根据特定请求头做出响应,例如上述的Access-Control-Allow-Headers、Access-Control-Allow-Methods、Access-Control-Allow-Origin。当前端浏览器接收到这些特定的响应头后,这次预检请求才算成功。

  • 而加了拦截器后,拦截器会将预检请求当作正常请求对待,回复的自然不包含以上这些CORS专属请求头了。那么浏览器在接收到这个响应后,会认为服务器不接收本次跨域请求,那么正式请求就不会发送,自然会报CORS:ERR错误了。此时后端只收到了一个不带token的预检请求,根本没有进行后续操作,所以后端数据自然也到不了前端。

解决方案

  • 拦截器将OPTIONS方法的响应增加允许跨域的响应头

package org.w.interceptor;

/**
 * 店铺员工拦截器
 */
public class StaffInterceptor implements HandlerInterceptor {

    /**
     * 前置拦截器(解析请求头token)
     * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (request.getMethod().equals("OPTIONS")){
            System.err.println("request method is OPTIONS");
            response.setHeader("Access-Control-Allow-Origin","*");//允许所有访问源
            response.setHeader("Access-Control-Allow-Headers","*");//允许携带所有请求头
            response.setHeader("Access-Control-Max-Age","1800");//可选,在1800秒内同源请求无须预检
            return false;//若拦截器下控制器接口中没有使用OPTIONS方法,则可以直接将其拦截
        }

        String token = request.getHeader("token");
        //后续代码省略
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
}
  • springmvc.xml文件配置拦截器和CORS

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/staff/**"/>
        <bean id="staffInterceptor" class="org.w.interceptor.StaffInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>


<!-- 全局配置 -->
    <!-- 其中:
        allowed-origins 为允许的访问源地址(*为所有请求)
        allowed-methods 为允许访问方式
	    allowed-headers 为允许携带的请求头
    -->
    <mvc:cors>
        <mvc:mapping path="/**"
                     allowed-origins="*"
                     allowed-methods="*"
                     allowed-headers="*"
        />
    </mvc:cors>

经测试,前端响应恢复正常

案例二:在案例一基础上,需要携带凭证(如Cookie)

  • 前端axios请求需要进行相应默认设置

axios.defaults.withCredentials = true; //开启凭证携带功能
  • 后端拦截器配置(需要注意的是,前端携带凭证设置开启后,后端无法再用【*】号配置允许的访问源和允许携带的请求头了)

package org.w.interceptor;

/**
 * 店铺员工拦截器
 */
public class StaffInterceptor implements HandlerInterceptor {

    /**
     * 前置拦截器(解析请求头token)
     * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (request.getMethod().equals("OPTIONS")){
            System.err.println("staff interceptor --- >request method is OPTIONS");
            String origin = request.getHeader("Origin");
            System.out.println("origin="+origin);
            response.setHeader("Access-Control-Allow-Origin",origin);//允许预检请求的访问源
            response.setHeader("Access-Control-Allow-Credentials","true");//允许携带凭证
            response.setHeader("Access-Control-Allow-Headers","token,Content-Type");//允许token,Content-Type请求头
            response.setHeader("Access-Control-Max-Age","1800");//可选,在1800秒内同源请求无须预检
            return false;//若拦截器下控制器接口中没有使用OPTIONS方法,则可以直接将其拦截
        }

        String token = request.getHeader("token");
        //后续代码省略
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
}
  • springmvc.xml文件配置

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/staff/**"/>
        <bean id="staffInterceptor" class="org.w.interceptor.StaffInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

<!-- 全局配置 -->
    <!-- 其中:
        allowed-origins 为允许的访问源地址(此处不能使用*,而是"")
		allowed-origin-patterns 为自动将访问源地址加入允许列表中(即响应请求头中)
        allow-credentials 为允许携带凭证(如cookies)(允许时origins不能为*)
    -->
    <mvc:cors>
        <mvc:mapping path="/**"
                     allowed-origin-patterns="*"
                     allow-credentials="true"
                     allowed-origins=""
                     allowed-headers="*"
        />
    </mvc:cors>

总结

由此可以看出,springmvc中,拦截器和CORS配置非前后关系,而是平行关系,即通过拦截器进入控制层的,不会再通过CORS配置。所以若一定要使用拦截器,可以将所有CORS的配置都放在对应拦截器中进行配置。此时只要针对预检请求进行对应的响应请求头的设置即可

跨域预检请求相关请求头

请求头说明
Origin请求的源URI,无论是否跨域都必然会发送
Access-Control-Request-Method实际请求方法
Access-Control-Request-Headers实际请求携带的请求头

跨域预检请求相关响应头

响应头说明
Access-Control-Allow-Origin允许发个文该资源的外域URI,若允许携带凭证,则不可为【*
Access-Control-Expose-Headers指定XMLHttpRequest的getResponseHeader可以访问到的响应头(即响应暴露的响应头)
Access-Control-Max-Age指定预检请求缓存时间,之后同源请求在缓存时间内无需预检
Access-Control-Allow-Credentials是否允许浏览器读取response的内容,即是否允许携带凭证(如Cookie)
Access-Control-Allow-Methods实际请求允许使用的HTTP方法
Access-Control-Allow-Headers实际请求允许携带的请求头

参考资料

学会Spring Mvc 跨域你只需要看完这一篇-腾讯云开发者社区 

Access-Control-Allow- 设置跨域资源共享CORS详解-CSDN博客

SpringBoot加了拦截器后出现的跨域问题解析 - 掘金 (juejin.cn)

【http】2、http request header Origin 属性、跨域 CORS、同源、nginx 反向代理、预检请求_requesthead 中的 origin-CSDN博客

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值