跨域解决
CorsFilter(全局跨域)
java
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;
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
// 放行哪些原始域
config.addAllowedOrigin("*");
// 是否发送 Cookie
config.setAllowCredentials(true);
// 放行哪些请求方式
config.addAllowedMethod("*");
// 放行哪些原始请求头部信息
config.addAllowedHeader("*");
// 暴露哪些头部信息
config.addExposedHeader("*");
//2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
//3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
WebMvcConfigurer(全局跨域)
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
//放行哪些原始域
.allowedOrigins("*")
.allowedHeaders("*")
// 是否发送Cookie
.allowCredentials(true)
.allowedMethods("GET", "POST", "OPTIONS", "DELETE", "PUT", "PATCH")
.maxAge(3600);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
使用注解 (局部跨域)
在控制器(类上)上使用注解 @CrossOrigin,表示该类的所有方法允许跨域。
java
@RestController
@CrossOrigin(origins = "*")
public class VerificationController {
}
在方法上使用注解 @CrossOrigin
java
@PostMapping("/check/phone")
@CrossOrigin(origins = "*")
public boolean checkPhoneNumber(@RequestBody @ApiParam VerificationPojo verification) throws BusinessException {
return false;
}
手动设置响应头(局部跨域)
使用 HttpServletResponse 对象添加响应头(Access-Control-Allow-Origin)来授权原始域,这里 Origin的值也可以设置为 “*”,表示全部放行。
java
@RequestMapping("/home")
public String home(HttpServletResponse response) {
response.addHeader("Access-Allow-Control-Origin","*");
return "home";
}
使用自定义filter实现跨域
java
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.HttpServletResponse;
import org.springframework.stereotype.Component;
@Slf4j
@Configuration
@WebFilter(filterName = "accessFilter", urlPatterns = "/*")
public class MyCorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
System.out.println("跨域请求过滤器启动");
String origin = request.getHeader("Origin");
// 是为了让前端可以传递cookie,因为要想cookie生效 Access-Control-Allow-Origin不能填*
if (origin!=null && !"".equalsIgnoreCase(origin.trim())) {
response.addHeader("Access-Control-Allow-Origin", origin);
}else{
response.setHeader("Access-Control-Allow-Origin", "*");
}
// 非简单请求跨域
response.addHeader("Access-Control-Allow-Headers", "content-type");
// 允许跨域请求的方法
response.addHeader("Access-Control-Allow-Methods", "*");
// 携带cookie的跨域
response.addHeader("Access-Control-Allow-Credentials", "true");
// 放行方法
filterChain.doFilter(servletRequest, servletResponse);
}
public void init(FilterConfig filterConfig) throws ServletException {
log.info("AccessFilter过滤器初始化!");
Filter.super.init(filterConfig);
}
public void destroy() {}
}
xml使自定义Filter生效方式
xml
<!-- 跨域访问 START-->
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.chuancey.filter.MyCorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 跨域访问 END -->
Spring Cloud Gateway 跨域配置
yaml
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
# 允许跨域的源(网站域名/ip),设置*为全部
# 允许跨域请求里的head字段,设置*为全部
# 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
allow-credentials: true
allowed-origins:
- "http://xb.abc.com"
- "http://sf.xx.com"
allowed-headers: "*"
allowed-methods:
- OPTIONS
- GET
- POST
- DELETE
- PUT
- PATCH
max-age: 3600
注意: 通过gateway 转发的其他项目,不要进行配置跨域配置
有时即使配置了也不会起作用,这时你可以根据浏览器控制的错误输出来查看问题,如果提示是 response 中 header 出现了重复的 Access-Control-* 请求头,可以进行如下操作
java
import java.util.ArrayList;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component("corsResponseHeaderFilter")
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
// 指定此过滤器位于NettyWriteResponseFilter之后
// 即待处理完响应体后接着处理响应头
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.defer(() -> {
exchange.getResponse().getHeaders().entrySet().stream()
.filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
.filter(kv -> (
kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)
|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)
|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS)
|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_MAX_AGE)))
.forEach(kv -> {
kv.setValue(new ArrayList<String>() {{
add(kv.getValue().get(0));
}});
});
return chain.filter(exchange);
}));
}
}
使用Nginx配置
shell
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
if ($request_method = 'OPTIONS') {
return 204;
}
}
继承HandlerInterceptorAdapter
java
@Component
public class CrossInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
return true;
}
}
Cookie操作
参考文章: https://fishpi.cn/article/1690536467101
java
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/items")
public class ItemsController {
@GetMapping("/get")
public Object getById(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
if (cookies!=null){
for(Cookie cookie : cookies){
System.out.println(cookie);
}
}
// 创建一个Cookie
Cookie myCookie = new Cookie("myCookieName", "myCookieValue");
// 设置Cookie的有效期为1天
myCookie.setMaxAge(24 * 60 * 60); // 秒数
// 设置Cookie的路径(可选)
myCookie.setPath("/");
myCookie.setSecure(true);
// 将Cookie添加到响应中
response.addCookie(myCookie);
/* 推荐这个 */
ResponseCookie cookie = ResponseCookie.from("Hb", "xxxxxxxxxxx")
.maxAge(24 * 60 * 60)
.domain("127.0.0.1")
.sameSite("None")
.secure(true)
.path("/")
.build();
//Hb=xxxxxxxxxxx; Path=/; Domain=127.0.0.1; Max-Age=86400; Expires=Sun, 21 Jul 2024 10:03:15 GMT; Secure; SameSite=None
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
return "OK";
}
}
注意
你只能设置后端服务器的域名cookie,比如你前端地址是 a.com,请求的后端地址是b.com,那么你后端接口只能设置domain为b.com的cooke值。
默认javax.servlet.http.Cookie不支持设置sameSite的值,可以通过ResponseCookie来实现
SameSite的值不能在前端修改!!!
SameSite:指定Cookie 是否可以被跨站点请求访问,有三个可能的值:
- Strict:仅允许来自同一站点的请求访问 Cookie。
- Lax:允许部分跨站点访问,例如从导航到 URL 的 GET 请求。
- None:允许任何跨站点请求访问 Cookie。
如果跨站设置cookie,必须要设置SameSite,不然会无效