Spring Security 中,想在权限中使用通配符,怎么做?

本文探讨了如何在 Spring Security 中实现权限通配符功能,类似于 Shiro 的 system:user:*。首先介绍了 SpEL(Spring Expression Language)的基础知识,然后展示了如何自定义权限评估器,最后通过使用 AntPathMatcher 解决权限通配符问题,并对比了 TienChin 项目中的实现方式。

小伙伴们知道,在 Shiro 中,默认是支持权限通配符的,例如系统用户有如下一些权限:

  • system:user:add
  • system:user:delete
  • system:user:select
  • system:user:update
  • ...

现在给用户授权的时候,我们可以像上面这样,一个权限一个权限的配置,也可以直接用通配符:

  • system:user:*

这个通配符就表示拥有针对用户的所有权限。

当然这是 Shiro 里边的,对 Shiro 不熟悉的小伙伴,可以在公众号后台回复 shiro,查看松哥之前录的视频教程。

今天我们来聊聊 Spring Security 中对此如何处理,也顺便来看看 TienChin 项目中,这块该如何改进。

1. SpEL

要搞明白基于注解的权限管理,那么得首先理解 SpEL,不需要了解多深入,我这里就简单介绍下。

Spring Expression Language(简称 SpEL)是一个支持查询和操作运行时对象导航图功能的强大的表达式语言。它的语法类似于传统 EL,但提供额外的功能,最出色的就是函数调用和简单字符串的模板函数。

SpEL 给 Spring 社区提供一种简单而高效的表达式语言,一种可贯穿整个 Spring 产品组的语言。这种语言的特性基于 Spring 产品的需求而设计,这是它出现的一大特色。

在我们离不开 Spring 框架的同时,其实我们也已经离不开 SpEL 了,因为它太好用、太强大了,SpEL 在整个 Spring 家族中也处于一个非常重要的位置。但是很多时候,我们对它的只了解一个大概,其实如果你系统的学习过 SpEL,那么上面 Spring Security 那个注解其实很好理解。

我先通过一个简单的例子来和大家捋一捋 SpEL。

为了省事,我就创建一个 Spring Boot 工程来和大家演示,创建的时候不用加任何额外的依赖,就最最基础的依赖即可。

代码如下:

String expressionStr = "1 + 2";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expressionStr);

expressionStr 是我们自定义的一个表达式字符串,这个字符串通过一个 ExpressionParser 对象将之解析为一个 Expression,接下来就可以执行这个 exp 了。

执行的时候有两种方式,对于我们上面这种不带任何额外变量的,我们可以直接执行,直接执行的方式如下:

Object value = exp.getValue();
System.out.println(value.toString());

这个打印结果为 3。

我记得之前有个小伙伴在群里问想执行一个字符串表达式,但是不知道怎么办,js 中有 eval 函数很方便,我们 Java 中也有 SpEL,一样也很方便。

不过很多时候,我们要执行的表达式可能比较复杂,这时候上面这种调用方式就不太够用了。

此时我们可以为要调用的表达式设置一个上下文环境,这个时候就会用到 EvaluationContext 或者它的子类,如下:

StandardEvaluationContext context = new StandardEvaluationContext();
System.out.println(exp.getValue(context));

当然上面这个表达式不需要设置上下文环境,我举一个需要设置上下文环境的例子。

例如我现在有一个 User 类,如下:

public class User {
    private Integer id;
    private String username;
    private String address;
    //省略 getter/setter
}

现在我的表达式是这样:

String expression = "#user.username";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("广州");
user.setUsername("javaboy");
user.setId(99);
ctx.setVariable("user", user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

这个表达式就表示获取 user 对象的 username 属性。将来创建一个 user 对象,放到 StandardEvaluationContext 中,并基于此对象执行表达式,就可以打印出来想要的结果。

如果我们将 user 对象设置为 rootObject,那么表达式中就不需要 user 了,如下:

String expression = "username";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("广州");
user.setUsername("javaboy");
user.setId(99);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

表达式就一个 username 字符串,将来执行的时候,会自动从 user 中找到 username 的值并返回。

当然表达式也可以是方法,例如我在 User 类中添加如下两个方法:

public String sayHello(Integer age) {
    return "hello " + username + ";age=" + age;
}
public String sayHello() {
    return "hello " + username;
}

我们就可以通过表达式调用这两个方法,如下:

调用有参的 sayHello:

String expression = "sayHello(99)";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("广州");
user.setUsername("javaboy");
user.setId(99);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

就直接写方法名然后执行就行了。

调用无参的 sayHello:

String expression = "sayHello";
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext ctx = new StandardEvaluationContext();
User user = new User();
user.setAddress("广州");
user.setUsername("javaboy");
user.setId(99);
ctx.setRootObject(user);
String value = exp.getValue(ctx, String.class);
System.out.println("value = " + value);

这些就都好懂了。

甚至,我们的表达式也可以涉及到 Spring 中的一个 Bean,例如我们向 Spring 中注册如下 Bean:

@Service("us")
public class UserService {
    public String sayHello(String name) {
        return "hello " + name;
    }
}

然后通过 SpEL 表达式来调用这个名为 us 的 bean 中的 sayHello 方法,如下:

@Autowired
BeanFactory beanFactory;
@Test
void contextLoads() {
    String expression = "@us.sayHello('javaboy')";
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression(expression);
    StandardEvaluationContext ctx = new StandardEvaluationContext();
    ctx.setBeanResolver(new BeanFactoryResolver(beanFactory));
    String value = exp.getValue(ctx, String.class);
    System.out.println("value = " + value);
}

给配置的上下文环境设置一个 bean 解析器,这个 bean 解析器会自动跟进名字从 Spring 容器中找打响应的 bean 并执行对应的方法。

当然,关于 SpEL 的玩法还有很多,我就不一一列举了。这里主要是想让小伙伴们知道,有这么个技术,方便大家理解 @PreAuthorize 注解的原理。

总结一下:

  1. 在使用 SpEL 的时候,如果表达式直接写的就是方法名,那是因为在构建 SpEL 上下文的时候,已经设置了 RootObject 了,我们所调用的方法,实际上就是 RootObject 对象中的方法。
  2. 在使用 SpEL 对象的时候,如果像调用非 RootObject 对象中的方法,那么表达式需要加上 @对象名 作为前缀,例如前面案例的 @us

2. 自定义权限该如何写

那么自定义权限到底该如何写呢?首先我们来看下在 Spring Security 中,不涉及到通配符的权限该怎么处理。

松哥举一个简单的例子,我们创建一个 Spring Boot 工程,引入 Web 和 Security 依赖,为了方便,这里的用户我直接创建在内存中,配置如下:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    @Bean
    UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager m = new InMemoryUserDetailsManager();
        m.createUser(User.withUsername("javaboy").password("{noop}123").authorities("system:user:add","system:user:delete").build());
        return m;
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll();
        return http.build();
    }

}

都是常规配置,没啥好说的。注意前面的注解,开启基于注解的权限控制。

这里我多啰嗦一句,大家看创建用户的时候,调用的是 authorities 方法去设置权限的,这个跟 roles 方法其实没啥大的区别,调用 roles 方法会自动为你设置的字符串添加一个 ROLE_ 前缀,其他的其实都一样。在 Spring Security 中,role 和 permission 仅仅只是人为划分出来的东西,底层的实现包括判断逻辑基本上都是没有区别的。

接下来我们定义四个测试接口,如下:

@RestController
public class UserController {

    @GetMapping("/add")
    @PreAuthorize("hasPermission('/add','system:user:add')")
    public String addUser() {
        return "add";
    }
    @GetMapping("/delete")
    @PreAuthorize("hasPermission('/delete','system:user:delete')")
    public String deleteUser() {
        return "delete";
    }
    @GetMapping("/update")
    @PreAuthorize("hasPermission('/update','system:user:update')")
    public String updateUser() {
        return "update";
    }
    @GetMapping("/select")
    @PreAuthorize("hasPermission('/select','system:user:select')")
    public String selectUser() {
        return "select";
    }
}

接口访问都需要不同的权限。

此时如果大家启动项目去此时,系统会提示你四个接口统统都不具备权限,这是啥原因呢?我们来继续分析。

小伙伴们看这里,调用的时候 @PreAuthorize 注解中执行写方法名,不用写对象名,说明调用的方法是 RootObject 中的方法,这里的 RootObject 实际上就是 SecurityExpressionRoot,我们来看看这个对象中的 hasPermission 方法:

@Override
public boolean hasPermission(Object target, Object permission) {
	return this.permissionEvaluator.hasPermission(this.authentication, target, permission);
}
@Override
public boolean hasPermission(Object targetId, String targetType, Object permission) {
	return this.permissionEvaluator.hasPermission(this.authentication, (Serializable) targetId, targetType,
			permission);
}

最终的调用又指向了 permissionEvaluator 对象。

在 Spring Security 中,permissionEvaluator 有一个统一的接口就是 PermissionEvaluator,但是这个接口只有一个实现类,就是 DenyAllPermissionEvaluator,看名字就知道,这是拒绝所有。

public class DenyAllPermissionEvaluator implements PermissionEvaluator {

	private final Log logger = LogFactory.getLog(getClass());

	/**
	 * @return false always
	 */
	@Override
	public boolean hasPermission(Authentication authentication, Object target, Object permission) {
		return false;
	}

	/**
	 * @return false always
	 */
	@Override
	public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
			Object permission) {
		return false;
	}

}

这两个方法里啥都没干,直接返回了 false,这下就破案了!

所以,在 Spring Security 中,如果想判断权限,需要自己提供一个 PermissionEvaluator 的实例,我们来看下:

@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if (authority.getAuthority().equals(permission)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

我这里的判断逻辑比较简单,所以只需要实现第一个方法就行了,这个方法三个参数,第一个参数就是当前登录成功的用户对象,后面两个参数则是我们在 @PreAuthorize("hasPermission('/select','system:user:select')") 注解中的两个参数,现在该有的东西都有了,我们只需要判断需要的权限当前用户是否有就行了。

这个自定义的权限评估器写好之后,注册到 Spring 容器就行了,其他什么事情都不用做。

接下来我们就可以对刚才的四个接口进行测试了,测试过程我就不演示了,小伙伴们自行用 postman 测试就行了。

3. 权限通配符

看明白了上面的逻辑,现在不用我说,大家也知道权限通配符在 Spring Security 中是不支持的(无论你在 @PreAuthorize 注解中写的 SpEL 是哪个,调用的是哪个方法,都是不支持权限通配符的)。

例如我现在这样描述我的用户权限:

@Bean
UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager m = new InMemoryUserDetailsManager();
    m.createUser(User.withUsername("javaboy").password("{noop}123").authorities("system:user:*").build());
    return m;
}

我想用 system:user:* 字符串表示 javaboy 具有针对用户的所有权限。

直接这样写肯定是不行的,最终字符串比较一定是不会通过的。

那么怎么办呢?用正则似乎也不太行,因为 * 在正则中不代表所有字符,如果拆解字符串去比较,功能虽然也行得通,但是比较麻烦。

想来想去,想到一个办法,不知道小伙伴们是否还记得我们之前在 vhr 中用过的 AntPathMatcher,用这个不就行了!

修改后的 CustomPermissionEvaluator 如下:

@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if (antPathMatcher.match(authority.getAuthority(), (String) permission)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

修改之后,现在只要用户具备 system:user:* 权限,就四个接口都能访问了。

4. TienChin 项目怎么做的?

TienChin 项目用的是 RuoYi-Vue 脚手架,我们来看下这个脚手架的实现方式:

@PreAuthorize("@ss.hasPermi('tienchin:channel:query')")
@GetMapping("/list")
public TableDataInfo getChannelList() {
    startPage();
    List<Channel> list = channelService.list();
    return getDataTable(list);
}

看了前面的讲解,现在 @ss.hasPermi('tienchin:channel:query') 应该很好懂了:

  • ss 是一个注册在 Spring 容器中的 bean,对应的类位于 org.javaboy.tienchin.framework.web.service.PermissionService 中。
  • 很明显,hasPermi 就是这个类中的方法。

这个 hasPermi 方法的逻辑其实很简单:

public boolean hasPermi(String permission) {
    if (StringUtils.isEmpty(permission)) {
        return false;
    }
    LoginUser loginUser = SecurityUtils.getLoginUser();
    if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
        return false;
    }
    return hasPermissions(loginUser.getPermissions(), permission);
}
private boolean hasPermissions(Set<String> permissions, String permission) {
    return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}

这个判断逻辑很简单,就是获取到当前登录的用户,判断当前登录用户的权限集合中是否具备当前请求所需要的权限。具体的判断逻辑没啥好说的,就是看集合中是否存在某个字符串,从判断的逻辑中我们也可以看出来,这个权限也是不支持通配符的。

 

<think> 我们正在解决Spring Cloud Gateway 4.2中配置了跨域(CORS)后,POST请求返回403错误而GET请求正常的问题。 根据用户描述,已经配置了跨域,说明基本CORS设置已经做了,但问题仅出现在POST请求,GET正常,这通常与CSRF保护有关,但Spring Cloud Gateway默认不启用CSRF。 不过,我们也要考虑其他可能性,例如网关路由配置、安全过滤器等。 分析: 1. 用户已经配置了跨域,所以基本CORS设置应该没有问题(如OPTIONS预检请求允许)。 2. 403错误通常表示服务器拒绝请求,可能的原因包括: - CSRF保护(但Gateway默认不开启) - 权限不足(例如,需要认证的请求未提供凭证) - 路由配置问题(例如,路由规则没有正确匹配POST请求) - 安全配置(例如,Spring Security配置了特定方法的拦截) 然而,用户提到配置了跨域,并且引用中提供了跨域配置的示例(包括application.yml和Java配置)。但问题仍然存在,我们需要更深入地检查。 考虑到Gateway的版本(4.2)以及问题仅发生在POST,我们可以从以下几个方面入手: 步骤1:检查跨域配置是否允许POST方法 在跨域配置中,确保allowedMethods包含了POST。例如: allowedMethods: "GET, POST, OPTIONS" 或者 "*" 步骤2:检查Gateway路由配置 确保路由配置正确,POST请求能够被正确路由到下游服务。 步骤3:检查是否有其他安全配置 虽然Gateway默认不启用CSRF,但如果在Gateway中集成了Spring Security,那么可能会启用CSRF保护。 步骤4:检查预检请求(OPTIONS)是否被正确处理 浏览器在发送POST跨域请求前,会先发送OPTIONS请求。如果OPTIONS请求没有得到正确的处理(比如返回403),那么POST请求就不会发送。 步骤5:检查请求头 跨域请求中,如果包含自定义头,需要在allowedHeaders中配置。另外,如果请求需要携带凭证(如cookie),则allowCredentials必须为true,并且allowedOrigins不能为"*",必须指定具体的域名。 根据引用中的配置示例,我们可以参考以下配置: 方案一:使用application.yml配置(如引用[1]和[2]) 注意:引用[1]中配置了OPTIONS方法,并且设置了allowCredentials为true,allowedOrigins指定了具体地址(这里不能写通配符*,因为allowCredentials为true时,origins必须明确指定)。 方案二:使用Java配置(如引用[3]) 同样,在Java配置中,我们允许了所有方法(包括POST),并且设置了允许所有头,允许所有来源(注意:生产环境应该指定具体来源)。 考虑到用户的问题,我们建议: 1. 检查跨域配置中是否允许POST方法(确保配置正确) 2. 特别注意:如果请求需要携带凭证(如cookie、token等),那么allowCredentials必须设置为true,并且allowedOrigins不能使用通配符"*",必须明确指定域名(如http://localhost:8080)。 3. 检查OPTIONS预检请求是否被正确处理。可以在Gateway中增加日志,查看OPTIONS请求是否被拦截。 如果以上配置都正确,那么问题可能不在跨域,而在于其他安全策略(例如Spring Security)或者路由配置。 另外,Spring Cloud Gateway 4.2(对应Spring Boot 3.x)中,跨域配置方式可能与旧版本有所不同。请确认配置文件中的关键字是否正确。例如,在Spring Boot 2.4及以上版本,建议使用allowedOriginPatterns代替allowedOrigins(因为allowedOrigins不支持通配符,而allowedOriginPatterns支持)。但是,当allowCredentials为true时,allowedOriginPatterns也不能使用通配符,必须指定具体域名(或者使用通配符子域,如http://*.example.com)。 参考引用[2]中的注释: # allowedOriginPatterns: "*" # spring boot2.4以后的配置 然而,当allowCredentials为true时,不能使用"*",必须明确指定。 因此,我们建议修改配置如下(application.yml): ```yaml spring: cloud: gateway: globalcors: add-to-simple-url-handler-mapping: true cors-configurations: '[/**]': allowedOriginPatterns: "http://localhost:*" # 允许来自localhost的任意端口,也可以根据实际情况调整 allowedMethods: "*" allowedHeaders: "*" allowCredentials: true maxAge: 3600 ``` 如果问题仍然存在,考虑在Gateway中排除OPTIONS请求的拦截(虽然跨域配置应该已经处理了),或者检查下游服务是否有安全限制。 另外,403错误也可能是由于下游服务对POST请求有特殊的安全要求(比如认证失败)。因此,需要确认请求是否携带了必要的认证信息(如token),并且下游服务是否接受。 总结解决方案: 1. 检查并修正跨域配置(特别注意allowCredentials和allowedOriginPatterns/allowedOrigins的搭配) 2. 检查OPTIONS请求是否正常(返回200),可以通过浏览器开发者工具查看网络请求。 3. 检查Gateway路由配置,确保POST请求被正确路由。 4. 检查是否有其他安全配置(如Spring Security)拦截了POST请求。 如果以上步骤无法解决,可以尝试在Gateway中暂时禁用所有安全配置(比如移除Spring Security依赖)来测试是否是跨域配置的问题。 另外,根据引用[3]和[4],也可以尝试使用Java配置的方式。以下是基于Spring Cloud Gateway 4.2的CorsConfig配置: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; @Configuration public class CorsConfig { @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 允许携带凭证 config.addAllowedOriginPattern("http://localhost:*"); // 设置允许的来源模式 config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setMaxAge(3600L); // 预检请求缓存时间 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } } ``` 注意:这里使用了setAllowCredentials(true)和addAllowedOriginPattern,确保两者兼容。 如果问题仍未解决,请提供更多信息,例如完整的网关配置、请求的详细信息(请求头、请求体等)以及日志。 相关问题: 1. 为什么在Spring Cloud Gateway中配置了跨域支持后,POST请求仍然返回403错误? 2. 如何正确配置Spring Cloud Gateway以允许携带凭证的跨域请求? 3.Spring Cloud Gateway中,allowedOrigins和allowedOriginPatterns有什么区别? 4. 如何诊断Spring Cloud Gateway中的403错误? </think> 针对 Spring Cloud Gateway 4.2 配置跨域后 POST 请求返回 403 错误的问题(GET 请求正常),以下是系统性解决方案: ### 一、核心问题定位 ```mermaid graph TD A[POST 403 / GET 正常] --> B{原因排查} B --> C1[跨域配置缺陷] B --> C2[CSRF保护拦截] B --> C3[路由匹配问题] C1 --> D1[检查allowedMethods] C2 --> D2[检查CSRF模块] C3 --> D3[验证路由规则] ``` ### 二、关键解决方案 #### 1. 完善跨域配置(修复 OPTIONS 处理) 在 `application.yml` 中补充 OPTIONS 请求支持: ```yaml spring: cloud: gateway: globalcors: add-to-simple-url-handler-mapping: true # 关键参数 cors-configurations: '[/**]': allowed-origin-patterns: - "http://localhost:*" # 使用模式匹配替代* allowed-methods: - GET - POST - PUT - DELETE - OPTIONS # 必须包含OPTIONS allowed-headers: "*" allow-credentials: true max-age: 360000 ``` > **关键点**: > - `add-to-simple-url-handler-mapping: true` 确保 OPTIONS 不被阻塞[^1][^2] > - 使用 `allowed-origin-patterns` 替代已弃用的 `allowed-origins` > - 显式声明 `OPTIONS` 方法(浏览器预检必需) #### 2. Java 配置方案(优先级更高) 创建配置类强化跨域处理: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; @Configuration public class GatewayCorsConfig { @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOriginPattern("http://localhost:*"); // 动态匹配端口 config.addAllowedMethod("OPTIONS"); // 重点补充 config.addAllowedMethod("POST"); config.addAllowedHeader("*"); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } } ``` #### 3. 路由规则验证 检查路由配置是否包含 POST 方法: ```yaml spring: cloud: gateway: routes: - id: service_route uri: lb://SERVICE-NAME predicates: - Path=/api/** filters: - name: TokenRelay # OAuth2场景需添加 metadata: http-methods: - GET - POST # 确保POST存在 ``` ### 三、深度排查步骤 1. **预检请求验证** 手动发送 OPTIONS 请求检查响应: ```bash curl -X OPTIONS http://gateway:port/api/endpoint \ -H "Origin: http://localhost:8080" \ -H "Access-Control-Request-Method: POST" -v ``` 正常应返回 `204 No Content` 并包含 `Access-Control-Allow-Methods: POST` 2. **网关日志诊断** 启用 DEBUG 日志查看请求处理链: ```yaml logging: level: org.springframework.cloud.gateway: DEBUG org.springframework.web.cors: TRACE ``` 3. **安全模块检查** 若集成了 Spring Security,需禁用 CSRF: ```java @Bean SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) { return http .csrf(csrf -> csrf.disable()) // Gateway通常无需CSRF .build(); } ``` ### 四、典型场景解决方案 | 场景 | 解决方案 | |-------------------------------|--------------------------------------------------------------------------| | 浏览器预检失败 | 确认 `add-to-simple-url-handler-mapping: true` [^1][^2] | | 携带认证凭证跨域 | `allow-credentials: true` + 精确设置 `allowed-origin-patterns` | | OAuth2 TokenRelay 失效 | 在路由过滤器添加 `TokenRelay` [^4] | | 下游服务拒绝 | 检查下游服务 CORS 配置(网关配置不覆盖下游) | > **生产环境建议**: > - 使用 `allowed-origin-patterns: "https://*.your-domain.com"` 替代通配符 > - 网关层统一处理跨域,下游服务禁用 CORS 避免配置冲突
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值