Springboot +spring security,解决跨域问题

news/2024/11/22 20:59:37/

一.简介

这篇文章主要是解释什么是跨域,在Spring中如何解决跨域,引入Spring Security后Spring解决跨域的方式失效,Spring Security 如何解决跨域的问题。

二.什么是跨域

跨域的概率:

浏览器不能执行其他网站的脚本,从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。跨域是由浏览器的同源策略造成的,是浏览器施加的安全限制。a页面想获取b页面资源,如果a、b页面的协议、域名、端口、子域名不同,所进行的访问行动都是跨域的。

三.演示跨域

3.1创建项目

如何创建一个SpringSecurity项目,前面文章已经有说明了,这里就不重复写了。

3.2后端接口

代码如下:

@RequestMapping
@RestController
public class IndexController {@PostMapping("/cors")public String hello() {return "hello";}
}

3.3前端页面

代码如下:

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title></title><!-- <script src="https://unpkg.com/vue@next"></script> --><script src="js/v3.2.8/vue.global.prod.js" type="text/javascript" charset="utf-8"></script><script src="https://unpkg.com/axios/dist/axios.min.js"></script></head><body><div id="app"><button @click="sendCorsRequest">发起跨域请求</button></div><script>const App = {data() {return {}},methods: {sendCorsRequest() {axios.post('http://localhost:8080/cors', {firstName: 'Fred',lastName: 'Flintstone'}).then(function (response) {console.log(response)}).catch(function (error) {console.log(error);});}},};Vue.createApp(App).mount('#app');</script></body>
</html>

3.4演示

打开页面,截图如下:
在这里插入图片描述
点击按钮,发送跨域请求,最终也发生了跨域请求,截图如下:

在这里插入图片描述

四.解决跨域

解决跨域有很多中方式,代码层面和非代码层面(nginx),这篇文章内容主讲解通过代码来解决跨域

4.1Spring 解决跨域

4.1.1Cors注解

标注在方法上,代码如下:

@RequestMapping
@RestController
public class IndexController {@RequestMapping("/cors")@CrossOrigin("http://127.0.0.1:8848")public String hello() {return "hello";}
}

在这里插入图片描述

标注在类上,代码如下:

@RequestMapping
@RestController
@CrossOrigin("http://127.0.0.1:8848")
public class IndexController {@RequestMapping("/cors")public String hello() {return "hello";}
}

在这里插入图片描述

4.1.2配置 CorsRegistry

代码如下:

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/cors").allowCredentials(false).allowedHeaders("*").allowedMethods("POST").allowedOrigins("*");}
}

4.1.3Cors 过滤器

代码如下:

@Configuration
public class WebMvcFilterConfig {@BeanFilterRegistrationBean<CorsFilter> cors(){FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();CorsConfiguration configuration = new CorsConfiguration();configuration.addAllowedHeader("*");configuration.addAllowedMethod("*");configuration.addAllowedOrigin("*");configuration.addExposedHeader("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**",configuration);registrationBean.setFilter(new CorsFilter(source));return registrationBean;}
}

以上三种使用spring解决跨域的方式讲解完了,接下来我们来说下如何在SpringSecurity中解决跨域。

4.2为什么在SpringSecurity中使用之前的跨域解决方案会失效

或许有好多人在项目中都碰到这个问题了,今天演示下,然后解释下为什么会出现这个问题。

4.2.1演示跨域失效

基于上面的代码,我们加上security依赖

org.springframework.boot:spring-boot-starter-security

配置securityConfig,代码如下:

 @Beanpublic SecurityFilterChain config(HttpSecurity http) throws Exception {http.authorizeHttpRequests().anyRequest().authenticated().and().exceptionHandling().accessDeniedHandler((req, resp, e) -> {Map<String, Object> result = new HashMap<>();result.put("code", -1);result.put("msg", "访问失败");result.put("data", e.getMessage());writeResp(result, resp);}).and().csrf().disable();return http.build();}public void writeResp(Object content, HttpServletResponse response) {response.setContentType("application/json;charset=utf-8");try {response.getWriter().println(JSON.toJSONString(content));} catch (IOException e) {throw new RuntimeException(e);}}

再次访问,发现还是跨域,之前的解决方案失效,截图如下:
在这里插入图片描述

4.2.3跨域解决失效的原因

为什么会失效呢,这个得要看下web中过滤器和拦截器执行顺序了,我们用一张图来看下web个组件的执行顺序。
在这里插入图片描述

  1. 首先经过过滤器,包括web filter和security filter
  2. 再经过Dispatcherservlet
  3. 再来到拦截器
  4. 最后才到controller 这样梳理下,不知道大家有没有明白点,首先理清一点,spring实现跨域解决主要是通过拦截器(两个注解实现)和过滤器,那么为什么会失效呢, 主要有以下几点
  5. security 是基于过滤器开启全路径拦截(需要拦截的),包括options请求
  6. 注解是基于拦截器实现,在security filter之后,所以options请求会被拦截,最终不起作用
  7. 过滤器方式不生效 是由于它的优先级在security filter之后,所以也会被拦截,最终不起作用

所以基于上面的分析,我们看下如何一一解决。

4.3Spring Security 跨域解决

4.3.1粗暴方式让Spring 方式生效

首先拦截器不生效,是由于请求被拦截,所以需要将请求放过去,配置如下:

@Beanpublic SecurityFilterChain config(HttpSecurity http) throws Exception {http.authorizeHttpRequests().antMatchers("/cors").permitAll().anyRequest().authenticated().and().exceptionHandling().accessDeniedHandler((req, resp, e) -> {Map<String, Object> result = new HashMap<>();result.put("code", -1);result.put("msg", "访问失败");result.put("data", e.getMessage());writeResp(result, resp);}).accessDeniedHandler(new AccessDeniedHandler() {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {Map<String, Object> result = new HashMap<>();result.put("code", -1);result.put("msg", "访问失败");result.put("data", accessDeniedException.getMessage());writeResp(result, response);}}).and().csrf().disable();return http.build();}

在这里插入图片描述

这种方式虽然可以,但是违背了我们业务,如果这个接口需要认证呢,所以这种方式一般用不上。

4.3.2将跨域过滤器提前于Security 过滤器

如果想让过滤器提前,仅需要设置过滤器的优先级就好,调整下代码,只需要加上一行代码: registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); 同时移除上面的配置,代码如下:

@BeanFilterRegistrationBean<CorsFilter> cors(){FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();CorsConfiguration configuration = new CorsConfiguration();configuration.addAllowedHeader("*");configuration.addAllowedMethod("*");configuration.addAllowedOrigin("*");configuration.addExposedHeader("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**",configuration);registrationBean.setFilter(new CorsFilter(source));registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);return registrationBean;}

在这里插入图片描述
请求成功,但是由于cors接口需要认证被拦截,但是接口已经调用成功,说明跨域已经解决

4.3.3通过.cors()解决

代码如下:

@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.addAllowedHeader("*");configuration.addAllowedMethod("*");configuration.addAllowedOrigin("*");configuration.addExposedHeader("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration);return source;}

在这里插入图片描述


http://www.ppmy.cn/news/91665.html

相关文章

[Daimayuan] pSort(C++,强连通分量)

题目描述 有一个由 n n n 个元素组成的序列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1​,a2​,…,an​&#xff1b;最初&#xff0c;序列中的每个元素满足 a i i a_ii ai​i。 对于每次操作&#xff0c;你可以交换序列中第 i i i 个元素和第 j j j 个元素当且仅当满足 …

《操作系统》——计算机系统概述

前言&#xff1a; 在之前的【Linux】学习中&#xff0c;我们已经对常见指令已经开发工具等进行了详细的了解。紧接着&#xff0c;我们将要学习的便是关于【Linux进程】的基本知识。但是为了帮助大家更好的理解相关的知识概念&#xff0c;我先带领大家来学习关于《操作系统》这…

图像生成简单介绍并给出相应的示例代码

文章目录 图像生成简单介绍并给出相应的TensorFlow示例代码1. 介绍2. 准备工具和库3. 数据集准备4. 创建生成对抗网络&#xff08;GAN&#xff09;模型5. 训练模型6. 生成新图像7. 总结 图像生成简单介绍并给出相应的TensorFlow示例代码 图像生成是计算机视觉中的一个重要任务…

android 12.0状态栏高度为0时,系统全局手势失效的解决方案

1.概述 在12.0的framework 系统全局手势事件也是系统非常重要的功能,但是当隐藏状态栏, 当把状态栏高度设置为0时,这时全局手势事件失效,这就要从系统手势滑动流程来分析 看怎么样实现系统手势功能的,然后根据功能做修改 2. 状态栏高度为0时,系统全局手势失效的解决方案…

14-C++面向对象(单例模式、const成员、浅拷贝、深拷贝)

单例模式 单例模式&#xff1a;设计模式的一种&#xff0c;保证某个类永远只创建一个对象 构造函数\析构函数 私有化 定义一个私有的static成员变量指向唯一的那个单例对象&#xff08;Rocket* m_rocket&#xff09; 提供一个公共的访问单例对象的接口&#xff0…

基于混合蛙跳的路径规划算法

路径规划算法&#xff1a;基于混合蛙跳优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于混合蛙跳优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

数据库的简介

文章目录 前言一、为什么需要数据库二、数据库基本概念1.什么是数据库2.什么是数据库管理系统3.数据库表4.数据库表 三、常见的数据库管理系统 前言 数据库的简介 一、为什么需要数据库 信息时代数据容量海量增长&#xff0c;结构化存储大量数据&#xff0c;便于高效的检索和…

Transformer应用之构建聊天机器人(二)

四、模型训练解析 在PyTorch提供的“Chatbot Tutorial”中&#xff0c;关于训练提到了2个小技巧&#xff1a; 使用”teacher forcing”模式&#xff0c;通过设置参数“teacher_forcing_ratio”来决定是否需要使用当前标签词汇来作为decoder的下一个输入&#xff0c;而不是把d…