Springboot中跨域问题的解决

等不及的小伙伴,直接跳到结论部分即可,谢谢!!!

1. 背景

1.1 使用技术栈

  • Spring Security
  • Springboot
  • Vue.axios
  • Jwt

1.2 关键代码

Spring Security实现了JWT验证

配置类相关代码

package xyz.yq56.sm.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import xyz.yq56.sm.filter.JwtSecurityFilter;/*** @author yi qiang* @date 2021/9/25 9:16*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredJwtSecurityFilter jwtSecurityFilter;/*** 配置接收检查的http请求** @param http http* @throws Exception 异常*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors(AbstractHttpConfigurer::disable).csrf(AbstractHttpConfigurer::disable).sessionManagement(item -> item.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeRequests(req -> req.antMatchers("/user/biz/login").permitAll().anyRequest().authenticated()).addFilterBefore(jwtSecurityFilter, UsernamePasswordAuthenticationFilter.class).httpBasic(AbstractHttpConfigurer::disable);}/*** 配置忽略静态资源** @param web web* @throws Exception 异常*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().mvcMatchers("/public/**", "/static/**");}/*** 更改数据源实现** @param auth 认证* @throws Exception 异常*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {}
}

Filter相关代码: 获取JWT认证头信息,然后进行认证

package xyz.yq56.sm.filter;import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;import lombok.extern.slf4j.Slf4j;
import xyz.yq56.easytool.enums.JwtClaimKey;
import xyz.yq56.easytool.properties.EasyToolProperties;
import xyz.yq56.easytool.provider.jwt.JwtProvider;
import xyz.yq56.easytool.utils.json.JsonUtils;
import xyz.yq56.easytool.utils.nvll.NullUtil;
import xyz.yq56.easytool.utils.string.TextUtils;
import xyz.yq56.sm.common.context.RequestContextUtil;/*** @author yi qiang* @date 2021/10/2 2:09*/
@Component
@Slf4j
public class JwtSecurityFilter extends OncePerRequestFilter {@AutowiredEasyToolProperties easyToolProperties;@AutowiredJwtProvider jwtProvider;@Overrideprotected void doFilterInternal(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull FilterChain filterChain) throws ServletException, IOException {if (checkJwtToken()) {//do somethingMap<String, Object> claims = jwtProvider.validateAccessToken(extractToken());if (!NullUtil.isEmpty(claims)) {List<String> list = TextUtils.strToList(String.valueOf(claims.get(JwtClaimKey.AUTHORITIES.getKey())));List<SimpleGrantedAuthority> authorities = list.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());//从token获取携带的信息,然后构建UsernamePasswordAuthenticationToken,并存入contextUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(claims.get(JwtClaimKey.SUB.getKey()), null, authorities);SecurityContextHolder.getContext().setAuthentication(authenticationToken);log.info("Token认证成功,设置context | authenticationToken: {}", JsonUtils.toJson(authenticationToken));} else {//拿不到信息就clear contextlog.info("Token认证失败,清空context | claims: {}", claims);SecurityContextHolder.clearContext();}} else {log.info("Token认证失败,清空context | Token不存在或格式异常");SecurityContextHolder.clearContext();}filterChain.doFilter(request, response);}/*** 检查头是否存在** @return 是否为jwt*/private boolean checkJwtToken() {String jwtHeader = extractToken();log.info("检查Token格式 | token: {}", jwtHeader);return TextUtils.isNotEmpty(jwtHeader) && jwtHeader.startsWith(easyToolProperties.getJwt().getPrefix());}private String extractToken() {return RequestContextUtil.getHeaderOrParam(easyToolProperties.getJwt().getHeader());}}

登录接口: 获取用户信息,然后颁发令牌

package xyz.yq56.sm.module.user.controller;import java.util.List;import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import com.baomidou.mybatisplus.core.toolkit.Wrappers;import xyz.yq56.easytool.provider.jwt.JwtProvider;
import xyz.yq56.easytool.utils.collection.CollectUtil;
import xyz.yq56.easytool.utils.collection.MapUtil;
import xyz.yq56.easytool.utils.log.LogUtil;
import xyz.yq56.sm.common.dto.Result;
import xyz.yq56.sm.common.enums.LogPrefix;
import xyz.yq56.sm.common.enums.ResponseCode;
import xyz.yq56.sm.common.util.ResultUtil;
import xyz.yq56.sm.module.user.model.User;
import xyz.yq56.sm.module.user.model.UserVo;
import xyz.yq56.sm.module.user.service.UserService;/*** @author yi qiang* @date 2021/10/1 17:40*/
@RestController
@RequestMapping("/user/biz/")
public class UserBizController {@AutowiredJwtProvider jwtProvider;@AutowiredUserService userService;@PostMapping("login")public Result<UserVo> login(@RequestBody User user) {LogUtil.info(LogPrefix.USER_BIZ.getPrefix(), MapUtil.builder().put("user", user).maps());List<User> userList = userService.list(Wrappers.<User>query().eq("username", user.getUsername()).eq("password", user.getPassword()));if (CollectUtil.isEmpty(userList)) {return ResultUtil.fail(ResponseCode.USER_NOT_EXIST);}if (userList.size() > 1) {return ResultUtil.fail(ResponseCode.USER_EXIST_SAME);}return ResultUtil.success(convertToVo(userList));}private UserVo convertToVo(List<User> userList) {User user = userList.get(0);UserVo userVo = new UserVo();BeanUtils.copyProperties(user, userVo);userVo.setAccessToken(jwtProvider.generateAccessToken(JwtProvider.buildClaims(userVo.getUid(), "ADMIN", "ADMIN,USER"), userVo.getUsername()));return userVo;}@PostMapping("logout")public Result<UserVo> logout(String uid) {return ResultUtil.success(null);}}

前端部分给axios配置了请求拦截器,会在头部带上JWT认证头信息

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './plugins/element.js'
// 导入全局样式表
import './assets/css/global.css'
import './assets/font/iconfont.css'
import axios from 'axios'// 配置请求根路径
axios.defaults.baseURL = 'http://localhost:8080/'
axios.interceptors.request.use(config => {if (config.url.indexOf('login') === -1) {config.headers.Authorization = window.sessionStorage.getItem('Authorization')}return config
})
Vue.prototype.$http = axiosVue.config.productionTip = falsenew Vue({router,render: h => h(App)
}).$mount('#app')

除此之外,一般会事先进行一个基本的跨域配置,如下代码:

package xyz.yq56.sm.config;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;/*** @author yiqiang*/
@Configuration
public class CorsConfig {@Beanpublic CorsFilter corsFilter() {CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.setAllowCredentials(true);corsConfiguration.addAllowedOrigin("*");corsConfiguration.addAllowedMethod("*");corsConfiguration.addAllowedHeader("*");UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);return new CorsFilter(urlBasedCorsConfigurationSource);}
}

1.3 问题展示

以上代码登录接口都不会出现跨域问题,但是当请求普通接口(比如菜单接口)时,会出现跨域问题.
错误信息:

Access to XMLHttpRequest at xx from orgin xx has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

F12截图

2 解决方案

其实通过报错信息也能大概明白,是预检请求没有通过导致的报错,一般就是Option请求出错
搜索了半天,有人说加入如下配置即可解决

package xyz.yq56.sm.filter;import java.io.IOException;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;/**** 暂时没发现有什么用,先注释掉* @author yiqiang*/
@Component
public class AccessCorsFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {HttpServletResponse res = (HttpServletResponse) response;res.addHeader("Access-Control-Allow-Credentials", "true");res.addHeader("Access-Control-Allow-Origin", "*");res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");res.addHeader("Access-Control-Allow-Headers", "Content-Type,X-CAF-Authorization-Token,sessionToken,X-TOKEN");if (((HttpServletRequest) request).getMethod().equals(HttpMethod.OPTIONS.name())) {response.getWriter().println("ok");return;}chain.doFilter(request, response);}@Overridepublic void destroy() {}@Overridepublic void init(FilterConfig filterConfig) {}
}

先说结果,我尝试加入了如上配置,但是并没有什么卵用.这个只是加了个过滤器,你这里没给人家退回去,不代表别人不会退回去.
经过推敲,我认为是Spring Security可能也有相关的限制,于是我去搜索了Security的跨域配置,果然被我找到了requestMatchers(CorsUtils::isPreFlightRequest).permitAll()这个配置项.于是我修改了Security配置,如下:

    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors(AbstractHttpConfigurer::disable).csrf(AbstractHttpConfigurer::disable).sessionManagement(item -> item.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeRequests(req -> req//非普通请求(比如请求新增了自定义头部信息,比如Jwt头),会发送预检Option请求,这里直接让他通过.requestMatchers(CorsUtils::isPreFlightRequest).permitAll().antMatchers("/user/biz/login").permitAll().anyRequest().authenticated()).addFilterBefore(jwtSecurityFilter, UsernamePasswordAuthenticationFilter.class).httpBasic(AbstractHttpConfigurer::disable);}

经验证,请求正常,改动生效

3 结论

如果项目中符合如下几点,可以尝试一下

  1. 项目中使用了Spring Security
  2. 前端跨域请求中携带了自定义的Header,比如Jwt等等
  3. 控制台报错: Access to XMLHttpRequest at xx from orgin xx has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
  4. 已配置常规跨域配置CorsFilter

以上几点均满足的话,请立刻尝试新增Spring Security配置.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()

    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors(AbstractHttpConfigurer::disable).csrf(AbstractHttpConfigurer::disable).sessionManagement(item -> item.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeRequests(req -> req//非普通请求(比如请求新增了自定义头部信息,比如Jwt头),会发送预检Option请求,这里直接让他通过.requestMatchers(CorsUtils::isPreFlightRequest).permitAll().antMatchers("/user/biz/login").permitAll().anyRequest().authenticated()).addFilterBefore(jwtSecurityFilter, UsernamePasswordAuthenticationFilter.class).httpBasic(AbstractHttpConfigurer::disable);}

希望各位老大看到我这篇之后可以不用寻找下一篇博客.能帮到你们的话,麻烦帮忙点个赞或者评论一下,谢谢.

关于Springboot中跨域问题的解决(Response to preflight request doesn‘t pass access control check)相关推荐

  1. 关于跨域 Response to preflight request doesn‘t pass access control check

    做项目的时候由于访问了不同的服务器,然后导致了跨域问题,报错情况为: has been blocked by CORS policy: Response to preflight request do ...

  2. Response to preflight request doesn‘t pass access control check: No ‘Access-Control-Allow-Origin跨域问题

    出现问题:在本地运行,有配置vue.config.js中的proxy,所以解决了跨域问题.但将项目打包放到服务器上去,在打包的过程中,proxy是不会被打包进去的,也就是说代理所配置的跨域就已经失效了 ...

  3. 跨域 Response to preflight request doesn‘t pass access control check: It does not have HTTP ok status.

    在登录的时候没有跨域问题, token 认证的时候就跨域了(请求拦截器中配置的请求头携带 token),有点玄学了. 后端没有报错,不过显示请求方法为 OPTION 预检请求.前端报错如下,解决也很简 ...

  4. vue跨域问题Response to preflight request doesn‘t pass access control check: No ‘Access-Control-Allow-Ori

    每天一个报错,个各不相同. 看见下面的这行报错是不是感觉跟熟悉? 没错,vue的跨域问题,记录一下我的解决方法并分享给大家 ~ . ~ Response to preflight request do ...

  5. 跨域Response to preflight request doesn‘t pass access control check: It does not have HTTP ok status.

    最近后端添加自定义请求头拦截器后,前端也增加请求头后,解决过程中先后出现两种跨域错误. 一.请求后台出现如下跨域问题: has been blocked by CORS policy: Respons ...

  6. has been blocked by CORS policy: Response to preflight request doesn‘t pass access control check

    has been blocked by CORS policy: Response to preflight request doesn't pass access control check 这个错 ...

  7. Response to preflight request doesn‘t pass access control check: The value of the ‘Access-Control-Al

    错误:Response to preflight request doesn't pass access control check: The value of the 'Access-Control ...

  8. GO + React + Axios Response to preflight request doesn't pass access control check: It does not hav

    使用Go + Reat 使用 Axios 请求后端, 出现: Access to XMLHttpRequest at 'http://127.0.0.1:20002/v1/user/login' fr ...

  9. 跨域Response to preflight request doesn‘t pass access control check: It does not have HTTP ok status

    今天前端突然跟我说接口调不通,跨域了 我就觉得奇怪怎么突然就这样了,本来还以为代码跨域策略没配好,检查来检查去都和网上的一样,应该没啥问题. 最后想起来配置了一个中间件类拦截了请求 问题出在使用顺序搞 ...

最新文章

  1. CodeForces 114B 【STL应用】
  2. android 向左滑动动画,Android中的滑动动画
  3. 接口中默认方法和静态方法_接口中的默认方法和静态方法
  4. qt鼠标进入窗体后,窗体自动置顶
  5. leetcode题解(二叉树和递归问题)
  6. HBASE整合ldap权限管理
  7. Cityengine, 3ds MAX, FME
  8. cmd mysql utf8_MySQL中UTF8编码的数据在cmd下乱码
  9. 提取html中的音频文件,如何将网页中的音频文件提取出来
  10. java判断浏览器杂项_杂 项 - HelloJava菜鸟社区
  11. dw常用标签_Dreamweaver制作基本标签
  12. asm bin hex elf文件区别
  13. python ocr文字识别竖排繁体_小巧免费的图片文字识别OCR软件 支持简体识别和竖排繁体中文...
  14. 最强思维导图训练营教程
  15. P1598 垂直柱状图(模拟)
  16. ACL 2020 | 香侬科技提出用Dice Loss缓解数据集数据不平衡问题
  17. python支持char数据类型吗_python有char类型吗,
  18. [data structure] heap 堆
  19. sim900芯片—GPRS使用C语言接电话和收短信应用程序
  20. 模式窗口 window.showmodaldialog 总结

热门文章

  1. html微信小游戏,白鹭HTML5游戏转微信小游戏问题集锦,你关心的都在这里
  2. 信号分析——导数(Java/Matlab)
  3. Qt5:输入控件 QPushButton/ QToolButton/ QRadioButton/ QCheckBox/ QTextEdit/ QComboBox/ QSpinBox/ QLabel
  4. 浙江大学PAT考试基础知识整理
  5. xshell 基本操作命令
  6. 【POI1999】【BZOJ2936】降 水
  7. hdu 5148 树形dp,分组背包
  8. 如何高效管理电脑里的文件
  9. 软件设计师知识点(七):程序设计语言与语言处理程序、法律法规知识
  10. CentOS7安装与克隆