跨域、JSONP、CORS、Spring、Spring Security解决方案

概述

JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。跨域是浏览器(如Chrome浏览器基于JS V8引擎,可以简单理解为JS解释器)的一种同源安全策略,是浏览器单方面限制脚本的跨域访问。因此,仅有客户端运行在浏览器时才存在跨域问题,才需要考虑如何解决这个问题。

浏览器控制台输出类似于:No 'Access-Control-Allow-Origin' header is present on the requested resource.这种报错信息,即表明遇到跨域问题。

对跨域理解的一个误区是:资源跨域时无法请求。实际上,通常情况下请求是可以正常发起的(部分浏览器存在特例),后端也正常进行处理,只是在返回时被浏览器拦截,导致响应内容不可使用。可以论证这一点的著名案例就是CSRF跨站攻击。

什么情况下会出现跨域?不同源的访问,就是跨域请求。同源的定义是,即同一个请求来源,包括主机名、协议和端口号。例如:

  • https://blog.csdn.net和https://geek.csdn.net,两个不同的二级域名,存在跨域问题
  • http://blog.csdn.net和https://blog.csdn.net,使用不同的协议,存在跨域问题
  • https://blog.csdn.net和https://blog.csdn.net:3000,使用不同的端口号,存在跨域问题。注:此处使用的3000端口仅仅是在举例,CSDN不会暴露出3000端口的服务。
  • https://blog.csdn.net/lonelymanontheway和https://blog.csdn.net/about/,虽然文件夹不同,但是是相同域名下,不存在跨域问题。

跨域问题普遍么?在现在前后端分离,微服务化之后,会存在许多不同的域名,这种情况下,就存在非常普遍的跨域问题。

从原理上说,跨域实际上就是在HTTP请求的消息头部分新增一些字段:

// 浏览器自己设置的请求域名
Origin
// 浏览器告诉服务器请求需要用到的HTTP方法
Access-Control-Request-Method
// 浏览器告诉服务器请求需要用到的HTTP消息头
Access-Control-Request-Headers

当浏览器进行跨域请求时会和服务器端进行一次握手(OPTION请求),从响应结果中可以获取如下信息,有些是服务端必须要设置的,有些则是可选的:

// 指定哪些客户端的域名允许访问这个资源
Access-Control-Allow-Origin
// 服务器支持的HTTP方法
Access-Control-Allow-Methods
// 需要在正式请求中加入的HTTP消息头
Access-Control-Allow-Headers
// 取值为true时,浏览器会在接下来的真实请求中携带用户凭证信息(cookie等),服务器也可以使用Set-Cookie向用户浏览器写入新的cookie。使用AccessControl-Allow-Credentials时,Access-Control-Allow-Origin不应该设置为*
Access-Control-Allow-Credentials
// 用于指明本次预检请求的有效期,单位为秒。在有效期内,预检请求不需要再次发起
Access-Control-Max-Age
// 
Access-Control-Expose-Headers

上述几个请求及响应头,可以在Tomcat的源码org.apache.catalina.filters.CorsFilter里找到。

只要服务器合理设置了这些响应结果中的消息头,就相当于实现对CORS的支持,从而支持跨源通信。

结论:
跨域问题是客户端(前端、或叫浏览器)出于安全考虑引发的问题,而如何解决此问题需要依赖于服务端(后端,包括运维)。

解决方案

JSONP

JSON with Padding。由于浏览器允许一些带src属性的标签跨域,如,iframe、script、img等,所以JSONP利用script标签可以实现跨域。

JSONP是带有回调函数callback的JSON,可用于解决主流浏览器的跨域数据访问的问题。但JSONP方案的局限性在于只能实现GET请求。

JSONP实际上是在需要返回的JSON数据外,用JS函数进行封装。具体来说,服务器返回一个JS函数,参数是一个JSON数据如:callback(JSON 数据),AJAX不能跨域访问,但JS脚本是可以跨域执行的,因此前端将执行这个callback函数,并获取其中的JSON数据。

如果需要返回的JSON数据如下:

[{"id": 2,"name": "ipad mini","price": 2500}]

则对应的JSONP格式是:
callback([{"id":2,"name":"ipad mini","price":2500}]);

使用jQuery发送基于JSONP的AJAX请求:

$.ajax({
	type: 'get',
	url: 'http://localhost:8080/api/product/10086',
	dataType: 'jsonp',
	jsonp: '_jsonp',
	jsonpCallback: 'callback',
	success: function(data) {
		var template = $("#product_table_template").html();
		var render = Handlebars.compile(template);
		var html = render({
			data: data
		});
		$('#product').html(html);
	}
});

三个参数选项的解释:

  • dataType:必须为jsonp,表示返回的数据类型为JSONP格式
  • jsonp:表示URL中JSONP回调函数的参数名
  • jsonpCallback:表示回调函数的名称,若未指定,由jQuery自动生成

中间转发层

跨域问题的核心是不同源访问。如果转换成同源请求,就不存在这个问题。因此通过搭建中间层,通过将服务端的请求进行转发,即增加dispatcher一层,那么前端请求的地址会被转发,可解决跨域问题。

当然,如果对性能有考量的产品,就需要慎重选择这个方案,因为多一层中间转发,对网络开销有一定影响。

Nginx反向代理

需要搭建一个Nginx中转服务器,用于转发请求。通过Nginx解析URL地址时进行判断,将请求转发的具体的服务器上。
在这里插入图片描述
当用户请求xx.720ui.com/server1的时候,Nginx会将请求转发给Server1这个服务器上的具体应用,从而达到跨域的目的。

CORS

Cross Origin Resource Sharing,跨域资源共享,参考W3C提出的CORS规范。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,用户不会有感觉。因此,实现CORS通信的关键是服务端。服务端只需添加相关响应头信息,即可实现客户端发出跨域请求。

浏览器首先会发起一个请求方法为OPTIONS的预检请求,用于确认服务器是否允许跨域,只有在得到许可后才会发出实际请求。预检请求还允许服务器通知浏览器跨域携带身份凭证(如cookie)。

但是,CORS不支持低版本的IE浏览器,如IE8以下的版本。如果想在IE8中使用jQuery发送AJAX请求时,需要配置$.support.cors = true,才能开启CORS特性。

在这里插入图片描述
在Java Web开发中,解决跨域问题的方案有很多。

Filter

Tomcat内置一个org.apache.catalina.filters.CorsFilter

Spring Web(即maven spring-web模块)提供org.springframework.web.filter.CorsFilter,该过滤器会先判断来自客户端的请求是不是一个跨域请求,然后根据CORS配置判断该请求是否合法:

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
	CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
	boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
	if (isValid && !CorsUtils.isPreFlightRequest(request)) {
		filterChain.doFilter(request, response);
	}
}

使用spring-web模块提供的CorsFilter,可实现全局级别的跨域设置。

如果想要针对某个域名设置允许跨域请求,也可以自定义一个CrossFilter

public class CrossFilter implements Filter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		HttpServletResponse res = (HttpServletResponse) response;
		res.addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE");
		res.addHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
		res.addHeader("Access-Control-Allow-Origin", "https://blog.csdn.net");
		res.addHeader("Access-Control-Max-Age", "1800");
		res.addHeader("Access-Control-Allow-Credentials", "true");
	}

	@Override
	public void destroy() {
	}
}

Java Config

借助于spring-webmvc提供的CorsRegistry和WebMvcConfigurer,也可以实现如下配置类:

import jakarta.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class CrossConfig {
	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                        .allowedOrigins("https://blog.csdn.net")
                        .allowedMethods("PUT", "DELETE")
                        .allowedHeaders("header1", "header2", "header3")
                        .exposedHeaders("header1", "header2")
                        .allowCredentials(false)
                        .maxAge(3600);
            }
        };
    }

    @Bean
    public FilterRegistrationBean<Filter> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("https://blog.csdn.net");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(0);
		return bean;
	}
}

@CrossOrigin

spring-web提供@CrossOrigin注解,源码如下:

// 此注解可用于方法或类
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
	// 以数组形式支持配置多个origin,作用和origins方法一样,如果为空则表示origin=*,即允许全部域名,不建议设置为*
    @AliasFor("origins")
    String[] value() default {};

    @AliasFor("value")
    String[] origins() default {};

    String[] originPatterns() default {};

    String[] allowedHeaders() default {};

    String[] exposedHeaders() default {};

    RequestMethod[] methods() default {};

    String allowCredentials() default "";

    String allowPrivateNetwork() default "";
    // 允许跨域访问的最大时间,过期后,客户端再次请求会出现跨域问题。不设置则默认值为-1,表示设置的跨域规则永远生效
    long maxAge() default -1L;
}

如何使用@CrossOrigin注解,参考代码片段:

@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
	@GetMapping("/{id}")
	public void retrieve(@PathVariable Long id) {
	}
}

Spring Security

基于CorsFilter,在Spring Security应用开发中,支持CORS的配置非常简单,:

@Override
protected void configure(HttpSecurity http) throws Exception {
	http.cors(c -> {
		CorsConfigurationSource source = request -> {
			CorsConfiguration config = new CorsConfiguration();
			config.setAllowedOrigins(Arrays.asList("*"));
			config.setAllowedMethods(Arrays.asList("*"));
			return config;
		};
		c.configurationSource(source);
	});
}

至于具体的实现原理,参考CorsProcessor接口的唯一实现类DefaultCorsProcessor的方法handleInternal方法:

protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response, CorsConfiguration config, boolean preFlightRequest) throws IOException {
	// 实际请求的Origin
    String requestOrigin = request.getHeaders().getOrigin();
    // 配置里被允许的Origin
    String allowOrigin = this.checkOrigin(config, requestOrigin);
    HttpHeaders responseHeaders = response.getHeaders();
    if (allowOrigin == null) {
        logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
        this.rejectRequest(response);
        return false;
    } else {
    	// 实际请求的方法Method
        HttpMethod requestMethod = this.getMethodToUse(request, preFlightRequest);
        // 配置里被允许的Method
        List<HttpMethod> allowMethods = this.checkMethods(config, requestMethod);
        if (allowMethods == null) {
            logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
            this.rejectRequest(response);
            return false;
        } else {
        	// 实际请求头
            List<String> requestHeaders = this.getHeadersToUse(request, preFlightRequest);
            // 配置里被允许的请求头
            List<String> allowHeaders = this.checkHeaders(config, requestHeaders);
            if (preFlightRequest && allowHeaders == null) {
                logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
                this.rejectRequest(response);
                return false;
            } else {
            	// 检查通过,设置响应头
                responseHeaders.setAccessControlAllowOrigin(allowOrigin);
                if (preFlightRequest) {
                    responseHeaders.setAccessControlAllowMethods(allowMethods);
                }
                if (preFlightRequest && !allowHeaders.isEmpty()) {
                    responseHeaders.setAccessControlAllowHeaders(allowHeaders);
                }
                if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
                    responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
                }
                if (Boolean.TRUE.equals(config.getAllowCredentials())) {
                    responseHeaders.setAccessControlAllowCredentials(true);
                }
                if (Boolean.TRUE.equals(config.getAllowPrivateNetwork()) && Boolean.parseBoolean(request.getHeaders().getFirst("Access-Control-Request-Private-Network"))) {
                    responseHeaders.set("Access-Control-Allow-Private-Network", Boolean.toString(true));
                }
                if (preFlightRequest && config.getMaxAge() != null) {
                    responseHeaders.setAccessControlMaxAge(config.getMaxAge());
                }
                response.flush();
                return true;
            }
        }
    }
}

参考

  • Spring Security实战
  • spring跨域的几种方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/699664.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python使用wkhtmltopdf将html字符串保存pdf,解决出现方框的问题

出现的问题: 解决办法: <html> <head><meta charset="UTF-8"/> </head> <style> * {font-family: Arial,SimSun !important; } </style> </html>在html字符串前面加上上面代码,意思是设置字体编码和样式 html示例:…

足球实况分析系统YOLO

① 足球运动员、裁判和球检测&#xff1b; ② 球员球队预测&#xff1b; ③ 足球地图上球员和球位置的估计&#xff1b; ④ 足球跟踪&#xff1b; 当你启动应用程序时&#xff0c;会自动加载两个演示视频以及推荐的设置和超参数. 1. 使用侧栏菜单“浏览文件”按钮上传视频…

【Linux系统编程】进程终止

目录 strerror函数 errno错误码 退出码 正常终止&#xff08;可以通过 echo $? 查看进程退出码&#xff09;&#xff1a; 1. 从main返回&#xff08;return&#xff09; 2. 调用exit 3. _exit&#xff08;一般尽量不要用&#xff09; 异常退出&#xff1a; ctrl c&am…

瓦片边界可视化工具

本文涉及的核心内容 瓦片边界可视化-VisibleTileBoundariesmeethigher/visible-tile-boundaries: visible tiles boundaries demo 一、瓦片边界可视化 1.1 背景 日常GIS开发中&#xff0c;需要了解瓦片是什么&#xff0c;瓦片展示的效果是什么样的。这种口头上抽象的东西&a…

惊艳的短视频:成都科成博通文化传媒公司

惊艳的短视频&#xff1a;瞬间之美&#xff0c;震撼心灵 在数字化时代&#xff0c;短视频以其短小精悍、内容丰富的特点&#xff0c;迅速占领了我们的屏幕和时间。而在这个浩如烟海的视频海洋中&#xff0c;总有一些短视频能够脱颖而出&#xff0c;以其惊艳的视觉效果、深刻的…

您对薪资待遇是否满意?没证据怎么办?这样做很可能会补上来!

您对薪资待遇是否满意&#xff1f;没证据怎么办&#xff1f; 这样做很可能会补上来&#xff01; 您有时可能对自己的工资或福利待遇感到不满意&#xff1a;感到为何我付出的不比别人少&#xff0c;但是工资待遇总是比别人低&#xff0c;是不是觉得很不服气&#xff1f;那么不服…

【技巧】让xorg和gnome不要使用GPU

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 默认xorg会使用GPU加速&#xff1a; 现在取消他对GPU的占用&#xff1a; sudo vim /etc/X11/xorg.conf修改或添加以下内容&#xff1a; Section &quo…

迁移学习助力机器学习实践应用

大家好&#xff0c;迁移学习是一种技术&#xff0c;能使机器利用从以前任务中获得的知识来提高对新任务的泛化能力。作为ChatGPT和Google Gemini等模型的核心原理&#xff0c;迁移学习在长文档总结、复杂文章撰写、旅行规划以及诗歌和歌曲创作等重要任务中发挥着关键作用。 本…

ArcGIS+SWAT+CENTURY:流域生态系统水-碳-氮耦合过程模拟

目录 章节一 流域水碳氮建模-概述 章节二 数据准备 章节三 流域水模拟 章节四 流域氮模拟 章节五 流域碳模拟 章节六 模型结果分析及地图制作 章节七 案例分析 更多应用 流域是一个相对独立的自然地理单元&#xff0c;它是以水系为纽带&#xff0c;将系统内各自然地理要…

verilog阻塞和非阻塞语法

阻塞和非阻塞是FPGA硬件编程中需要了解的一个概念,绝大部分时候,因为非阻塞的方式更加符合时序逻辑设计的思想,有利于时钟和信号的同步,更加有利于时序收敛,所以除非特殊情况,尽量采用非阻塞方式。 1,非阻塞代码 非阻塞赋值,A和B是同时被赋值的,具体是说在时钟的上升…

设计模式-享元模式(结构型)

享元模式 享元模式是一种结构型模式&#xff0c;它主要用于减少创建对象的数量&#xff0c;减少内存占用。通过重用现有对象的方式&#xff0c;如果未找到匹配对象则新建对象。线程池、数据库连接池、常量池等池化的思想就是享元模式的一种应用。 图解 角色 享元工厂&#xf…

【GreenHills】关于GHS加密狗license激活成功后打开软件提示无可用授权

【更多软件使用问题请点击亿道电子官方网站】 1、 问题场景 用于解决在使用加密狗license去激活旧版本的GHS的时候&#xff0c;激活页面显示激活成功&#xff0c;但是&#xff0c;打开软件显示无可用license&#xff08;如图2-1&#xff09;&#xff0c;重新激活现象还是一样的…

catia零件装配中通过指南针移动零件

1 将零件导入进来后 2 把指南针移动到零件上 具体移动哪个可以通过模型树点击选中&#xff0c;选中那个就可以移动那个。 这种情况需要注意的是 需要双击选择要移动零件的父节点 如下图&#xff0c;Product2蓝色表示是激活的&#xff0c;这样才可以单击选中下面的零件后通过…

Echarts 绘制地图(中国、省市、区县),保姆级教程!

前言&#xff1a;大家好呀&#xff0c;这篇讲述 VueEcharts 绘制地图&#xff08;中国、省市、区县&#xff09;&#xff0c;保姆级教程&#xff01;话不多说&#xff0c;上干货&#xff1a; 先安利两个网址&#xff0c;是制作地图的资源&#xff1a; DataV.地图GeoJSON数据 Ap…

IINA for Mac v1.3.5 安装教程(保姆级)

Mac分享吧 文章目录 效果一、准备工作二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功 三、运行测试1、打开软件&#xff0c;测试2、查看版本号 **安装完成&#xff01;&#xf…

【总线】设计fpga系统时,为什么要使用总线?

目录 为什么用总线 为什么选择AMBA 总结 系列文章 【总线】AMBA总线架构的发展历程-CSDN博客 【总线】设计fpga系统时&#xff0c;为什么要使用总线&#xff1f;-CSDN博客 为什么用总线 在FPGA系统设计中&#xff0c;使用总线是为了实现组件间的高效互联与通信&#xff0c…

30岁迷茫?AI赛道,人生新起点

前言 30岁&#xff0c;对于许多人来说&#xff0c;是一个人生的分水岭。在这个年纪&#xff0c;有些人可能已经在某个领域取得了不小的成就&#xff0c;而有些人则可能开始对未来的职业方向感到迷茫。如果你正处于这个阶段&#xff0c;那么你可能会问自己&#xff1a;30岁转行…

influxDB部署

influxDB部署 1.首先我们进入influxDB的官方网站的下载页面&#xff1a; https://portal.influxdata.com/downloads/获取相应的版本。我们会看到如下界面 然后这里我们选择influxDB中的V2.0.4版本进行点击进入&#xff0c;进入到对应的页面之后这里我们会看到针对不同操作系统…

Unity HoloLens2 MRTK 空间锚点 基础教程

Unity HoloLens2 MRTK 空间锚点 基础教程 Unity HoloLens2 空间锚点MRTK 空间锚点 准备Unity 工程创建设置切换 UWP 平台UWP 平台设置 下载并安装混合现实功能工具导入混合现实工具包和 OpenXR 包 Unity 编辑器 UWP 设置Unity 2019.4.40 设置Unity 2022.3.0 设置Unity 2022.3.0…

Python pickle反序列化

基础知识 Pickle Pickle在Python中是一个用于序列化&#xff08;将对象转换为字节流&#xff09;和反序列化&#xff08;将字节流转换回对象&#xff09;的标准库模块。它主要用于将Python对象保存到文件或通过网络进行传输&#xff0c;使得数据可以跨会话和不同的Python程序共…