跨域、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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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");}@Overridepublic 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 {@Beanpublic WebMvcConfigurer corsConfigurer() {return new WebMvcConfigurer() {@Overridepublic 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);}};}@Beanpublic 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 {// 实际请求的OriginString requestOrigin = request.getHeaders().getOrigin();// 配置里被允许的OriginString 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 {// 实际请求的方法MethodHttpMethod requestMethod = this.getMethodToUse(request, preFlightRequest);// 配置里被允许的MethodList<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.rhkb.cn/news/345032.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java面试】十六、并发篇:线程基础

文章目录 1、进程和线程的区别2、并行和并发的区别3、创建线程的四种方式3.1 Runnable和Callable创建线程的区别3.2 线程的run和start 4、线程的所有状态与生命周期5、新建T1、T2、T3&#xff0c;如何保证线程的执行顺序6、notify和notifyAll方法有什么区别7、wait方法和sleep方…

Flutter Image源码分析

本文用于记录分析Imge图片加载流程源码分析学习笔记 切入点是Image.network,加载网络图片 构造方法会创建NetworkImage,加载图片的实现类,父类是ImageProvider 加载本地图片等等都是类似 下面进入_ImageState类 void resolveStreamForKey(ImageConfiguration configurat…

【云原生】基于windows环境搭建Docker

目录 一、Docker Desktop搭建 二、前置准备 2.1开启 Hyper-V 2.2 Hyper-V选项看不到问题解决 2.3 开启或升级wsl 三、安装过程 3.1 下载安装包 3.2 安装 Docker Desktop 3.2.1 Docker 图标一直处于starting状态问题解决 3.3 配置仓库与镜像 3.4 docker功能测试 四、…

C++中的一些困惑(长期更新中)

C中的一些困惑 文章目录 C中的一些困惑1. using std::具体命名与using namespace std;2. 【int \*p[10] 】与 【int (\*p)[10]】3. main()函数可带参&#xff0c;参从何来&#xff1f;4. constexpr函数的返回值可不为常量&#xff0c;那这时constexpr关键字作用是什么&#xff…

CTF Show MISC做题笔记

MISCX 30 题目压缩包为misc2.rar,其中包含三个文件:misc1.zip, flag.txt, hint.txt。其中后两个文件是加密的。 先解压出misc1.zip, 发现其中包含两个文件&#xff1a;misc.png和music.doc。其中后面文件是加密的。 解压出misc.png,发现图片尾部有消息&#xff1a;flag{flag…

一个简单的消息队列

目录 原理 实现代码 示例 原理 消息队列是一个先进先出栈&#xff0c;每次都处理第一项&#xff0c;处理完了过后会删除这个消息&#xff0c;这是一个简单的消息队列图&#xff1a; 实现代码 首先消息队列需要一个队列&#xff0c;我们用Python里的列表&#xff1a; self.…

Shell脚本学习_内置命令

目录 1.内置命令介绍&#xff1a; 2.Shell内置命令&#xff1a;alias设置别名 3.Shell内置命令&#xff1a;echo输出字符串 4.Shell内置命令&#xff1a;read读取控制台输入 5.Shell内置命令&#xff1a;exit退出 6.Shell内置命令&#xff1a;declare设置变量 1.内置命令…

【计算机毕业设计】283基于微信小程序校园订餐

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

用python编撰一个电脑清理程序

自制一个电脑清理程序&#xff0c;有啥用呢&#xff1f;在电脑不装有清理软件的时候&#xff0c;可以解决自己电脑内存不足的情况。 1、设想需要删除指定文件夹中的临时文件和缓存文件。以下是代码。 import os import shutil def clean_folder(folder_path): for root,…

【备战蓝桥杯】蓝桥杯省一笔记:算法模板笔记(Java)

蓝桥杯 0、快读快写模板1、回文判定2、前缀和3、差分4、二分查找5、快速幂6、判断素数7、gcd&lcm8、进制转换9、位运算10、字符串常用API11、n的所有质因子12、n的质因子个数13、n的约数个数14、n阶乘的约数个数15、n的约数和16、阶乘 & 双阶乘17、自定义升序降序18、动…

Java----抽象类和接口

欢迎大家来这次博客-----抽象类和接口。 1.抽象类 1.1 抽象类概念 在Java中我们都是通过类来描述对象&#xff0c;但反过来并不是所有的类都是用来描述对象的。当一个类中没有足够的信息来描述一个具体对象&#xff0c;我们就将该类称为抽象类。 如上图中的Shape类&#xff…

Wireshark自定义Lua插件

背景&#xff1a; 常见的抓包工具有tcpdump和wireshark&#xff0c;二者可基于网卡进行抓包&#xff1a;tcpdump用于Linux环境抓包&#xff0c;而wireshark用于windows环境。抓包后需借助包分析工具对数据进行解析&#xff0c;将不可读的二进制数转换为可读的数据结构。 wires…

SwiftUI五视图动画和转场

代码下载 使用SwiftUI可以把视图状态的改变转成动画过程&#xff0c;SwiftUI会处理所有复杂的动画细节。在这篇中&#xff0c;会给跟踪用户徒步的图表视图添加动画&#xff0c;使用animation(_:)修改器给一个视图添加动画效果非常容易。 下载起步项目并跟着本篇教程一步步实践…

单元测试覆盖率

什么是单元测试覆盖率 关于其定义&#xff0c;先来看一下维基百科上的一段描述&#xff1a; 代码覆盖&#xff08;Code coverage&#xff09;是软件测试中的一种度量&#xff0c;描述程序中源代码被测试的比例和程度&#xff0c;所得比例称为代码覆盖率。 简单来理解&#xff…

【Redis学习笔记05】Jedis客户端(中)

Jedis客户端 1. 命令 1.1 String类型 1.1.1 常见命令 SET命令 语法&#xff1a;SET key value [EX seconds | PX milliseconds] [NX|XX] 说明&#xff1a;将string类型的value值设置到指定key中&#xff0c;如果之前该key存在&#xff0c;则会覆盖原先的值&#xff0c;原先…

短剧看剧系统投流版系统搭建,前端uni-app

目录 前言&#xff1a; 一、短剧看剧系统常规款短剧系统和投流版的区别&#xff1f; 二、后端体系 1.管理端&#xff1a; 2.代理投流端 三、功能区别 总结&#xff1a; 前言&#xff1a; 23年上半年共上新微短剧481部&#xff0c;相较于2022年全年上新的454部&#xff0…

C语言王国——数据的内存管理

目录 一、引言 二、整形在内存中的存储 2.1 进制之间的转换 2.1.1 整形的二进制 2.1.2 十进制和二进制 2.1.3 十进制和八进制的转换 2.1.4 十六进制和十进制的转换 2.2 原码&#xff0c;反码&#xff0c;和补码 三、大、小端字节序 3.1 大小端的定义 3.2 为什么会有大…

高考后志愿填报信息采集系统制作指南

在高考的硝烟散去之后&#xff0c;每位学生都面临着一个重要的任务——志愿填报。老师们如何高效、准确地收集和整理这些信息&#xff0c;成为了一个棘手的问题。难道我们只能依赖传统的手工登记方式&#xff0c;忍受其繁琐和易错吗&#xff1f; 易查分是一个简单易用的在线工具…

容器中运行ping提示bash: ping: command not found【笔记】

容器中运行ping提示bash: ping: command not found 原因是容器中没有安装ping命令 在容器中安装ping命令&#xff0c;可以使用以下命令&#xff1a; 对于基于Debian/Ubuntu的容器&#xff0c;使用以下命令&#xff1a; apt-get update apt-get install -y iputils-ping对于基…

上位机图像处理和嵌入式模块部署(f407 mcu和其他mcu品类的选择)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多朋友读书的时候学的是stm32&#xff0c;工作中用的也是stm32。这本来问题不大&#xff0c;但是过去两三年的经历告诉我们&#xff0c;mcu的使用…