網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
關(guān)于Springboot中跨域問(wèn)題的解決(Response to preflight request doesn‘t pass access control check)
作者:一步一結(jié) 更新時(shí)間: 2022-03-14 編程語(yǔ)言Springboot中跨域問(wèn)題的解決
等不及的小伙伴,直接跳到結(jié)論部分即可,謝謝!!!
1. 背景
1.1 使用技術(shù)棧
- Spring Security
- Springboot
- Vue.axios
- Jwt
1.2 關(guān)鍵代碼
Spring Security實(shí)現(xiàn)了JWT驗(yàn)證
配置類相關(guān)代碼
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 {
@Autowired
JwtSecurityFilter jwtSecurityFilter;
/**
* 配置接收檢查的http請(qǐng)求
*
* @param http http
* @throws Exception 異常
*/
@Override
protected 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)
;
}
/**
* 配置忽略靜態(tài)資源
*
* @param web web
* @throws Exception 異常
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().mvcMatchers("/public/**", "/static/**");
}
/**
* 更改數(shù)據(jù)源實(shí)現(xiàn)
*
* @param auth 認(rèn)證
* @throws Exception 異常
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
}
Filter相關(guān)代碼: 獲取JWT認(rèn)證頭信息,然后進(jìn)行認(rèn)證
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 {
@Autowired
EasyToolProperties easyToolProperties;
@Autowired
JwtProvider jwtProvider;
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
if (checkJwtToken()) {
//do something
Map<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獲取攜帶的信息,然后構(gòu)建UsernamePasswordAuthenticationToken,并存入context
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(claims.get(JwtClaimKey.SUB.getKey()), null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
log.info("Token認(rèn)證成功,設(shè)置context | authenticationToken: {}", JsonUtils.toJson(authenticationToken));
} else {
//拿不到信息就clear context
log.info("Token認(rèn)證失敗,清空context | claims: {}", claims);
SecurityContextHolder.clearContext();
}
} else {
log.info("Token認(rèn)證失敗,清空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());
}
}
登錄接口: 獲取用戶信息,然后頒發(fā)令牌
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 {
@Autowired
JwtProvider jwtProvider;
@Autowired
UserService 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配置了請(qǐng)求攔截器,會(huì)在頭部帶上JWT認(rèn)證頭信息
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './plugins/element.js'
// 導(dǎo)入全局樣式表
import './assets/css/global.css'
import './assets/font/iconfont.css'
import axios from 'axios'
// 配置請(qǐng)求根路徑
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 = axios
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
除此之外,一般會(huì)事先進(jìn)行一個(gè)基本的跨域配置,如下代碼:
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 {
@Bean
public 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 問(wèn)題展示
以上代碼登錄接口都不會(huì)出現(xiàn)跨域問(wèn)題,但是當(dāng)請(qǐng)求普通接口(比如菜單接口)時(shí),會(huì)出現(xiàn)跨域問(wèn)題.
錯(cuò)誤信息:
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 解決方案
其實(shí)通過(guò)報(bào)錯(cuò)信息也能大概明白,是預(yù)檢請(qǐng)求沒有通過(guò)導(dǎo)致的報(bào)錯(cuò),一般就是Option請(qǐng)求出錯(cuò)
搜索了半天,有人說(shuō)加入如下配置即可解決
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;
/**
** 暫時(shí)沒發(fā)現(xiàn)有什么用,先注釋掉
* @author yiqiang
*/
@Component
public class AccessCorsFilter implements Filter {
@Override
public 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);
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig filterConfig) {
}
}
先說(shuō)結(jié)果,我嘗試加入了如上配置,但是并沒有什么卵用.這個(gè)只是加了個(gè)過(guò)濾器,你這里沒給人家退回去,不代表別人不會(huì)退回去.
經(jīng)過(guò)推敲,我認(rèn)為是Spring Security可能也有相關(guān)的限制,于是我去搜索了Security的跨域配置,果然被我找到了requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
這個(gè)配置項(xiàng).于是我修改了Security配置,如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(item -> item.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeRequests(req -> req
//非普通請(qǐng)求(比如請(qǐng)求新增了自定義頭部信息,比如Jwt頭),會(huì)發(fā)送預(yù)檢Option請(qǐng)求,這里直接讓他通過(guò)
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.antMatchers("/user/biz/login").permitAll()
.anyRequest().authenticated())
.addFilterBefore(jwtSecurityFilter, UsernamePasswordAuthenticationFilter.class)
.httpBasic(AbstractHttpConfigurer::disable)
;
}
經(jīng)驗(yàn)證,請(qǐng)求正常,改動(dòng)生效
3 結(jié)論
如果項(xiàng)目中符合如下幾點(diǎn),可以嘗試一下
- 項(xiàng)目中使用了Spring Security
- 前端跨域請(qǐng)求中攜帶了自定義的Header,比如Jwt等等
- 控制臺(tái)報(bào)錯(cuò):
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.
- 已配置常規(guī)跨域配置CorsFilter
以上幾點(diǎn)均滿足的話,請(qǐng)立刻嘗試新增Spring Security配置.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(item -> item.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeRequests(req -> req
//非普通請(qǐng)求(比如請(qǐng)求新增了自定義頭部信息,比如Jwt頭),會(huì)發(fā)送預(yù)檢Option請(qǐng)求,這里直接讓他通過(guò)
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.antMatchers("/user/biz/login").permitAll()
.anyRequest().authenticated())
.addFilterBefore(jwtSecurityFilter, UsernamePasswordAuthenticationFilter.class)
.httpBasic(AbstractHttpConfigurer::disable)
;
}
希望各位老大看到我這篇之后可以不用尋找下一篇博客.能幫到你們的話,麻煩幫忙點(diǎn)個(gè)贊或者評(píng)論一下,謝謝.
原文鏈接:https://blog.csdn.net/java0506/article/details/120620447
相關(guān)推薦
- 2022-11-08 Python?Panda中索引和選擇?series?的數(shù)據(jù)_python
- 2022-05-10 原生ajax 在服務(wù)器響應(yīng)前撤銷請(qǐng)求
- 2022-11-16 Docker如何安全地停止和刪除容器_docker
- 2022-11-13 Git實(shí)現(xiàn)克隆歷史的某個(gè)版本_相關(guān)技巧
- 2022-09-10 詳解Python腳本如何設(shè)置試用期_python
- 2023-02-10 C++?deque與vector對(duì)比的優(yōu)缺點(diǎn)_C 語(yǔ)言
- 2022-06-23 Android?Socket實(shí)現(xiàn)多個(gè)客戶端聊天布局_Android
- 2022-01-30 使用ref手動(dòng)改變antd的搜索框Input.Search的搜索內(nèi)容
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支