Gateway + Security 一直提示 Please sign in原因

543 阅读4分钟

背景:

为SpringCloud微服务添加了Actuator依赖,为的是解决微服务项目启动顺序中,在上个服务的状态变为启动成功"UP"状态后,在启动下一个项目,不然nacos心跳检测会有异常报错。

可是在添加完 Actuator 并项目稳定运行后,甲方说通过渗透发现可以直接通过域名访问actuator下的其他所有接口, 例如

  • /env
  • /info
  • /mappings
  • /shutdown (很危险)
  • ...
// 我们所期望的获取服务状态
curl "http://xxx/actuator/health"

// 返回
{
  "status": "UP"
}

虽然我们只想要一个health功能,但是和health一起的还有很多其他支持方法,同时health我们也不想加上权限,所以想出来了一个应该比较好的解决样子。

期望

1 /actuator/health 可直接调用拿取返回值,无需验证

2 /actuatr/xxx 不可访问,或者需要登录

3 不影响其他业务使用,例如/login/xx 接口等

实现

通过查询类似功能一般是 Actuator + Security 结合使用,Gateway本身是控制不了 Actuator 相关接口的

所以该功能其实是Spring Cloud Gateway + Spring Actuator + Spring Security

1 gateway 中添加依赖

<!-- actuator 健康检测 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2 application 配置

# health 和 jms有相关关系
# - health 默认会返回服务里所有的健康(中间件等等)
# - jms 配置如下后,返回的值就相对简约,只有status
management.health.jms.enabled=false

# 账号密码
security.user.name=admin
security.user.password=xxx

# 开放的接口,禁用的接口
management.endpoints.web.exposure.include=health
management.endpoints.web.exposure.exclude=

# 不确定,似乎是因为我的webflux的原因导致冲突,推荐我添加该配置项
spring.main.allow-bean-definition-overriding=true

如果未禁用jms,health 返回的信息格式

{
  "status": "UP",
  "components": {
    "jms": {
      "status": "UP",
      "details": {
        "provider": "ActiveMQ"
      }
    }
  }
}

3 gateway 配置 SecurityConfig。 不适用于Gateway!!!!后续会说(适用于其他微服务)

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                // 允许无认证访问 /actuator/health
                .antMatchers("/actuator/health").permitAll()
                // 剩余的 /actuator/** 都不许访问
                .antMatchers("/actuator/**").denyAll()
                .and()
                .httpBasic().disable()
                // 禁用默认的登录表单
                .formLogin().disable();
    }

}

该代码书写完成后,项目启动,无论访问什么url,都会跳转到 /login 页面

image.png

通过查询和不断的注释代码,排查问题,发现是 Security 自己拥有一套属于自己的认证系统

他的用户名称是"user"

密码会在项目打印台打印出来

而自己配置了SecurityConfig还会去走 Security 的认证逻辑的原因就是SecurityConfig没被注册成功

打印详细日志并询问Gpt得到以下结论

o.s.w.s.adapter.HttpWebHandlerAdapter : [75e2975a-3] HTTP GET "/xx/xx?A=1" 
o.s.w.s.adapter.HttpWebHandlerAdapter : [75e2975a-3] Completed 302 FOUND 
o.s.w.s.adapter.HttpWebHandlerAdapter : [75e2975a-4] HTTP GET "/login" 
o.s.w.s.adapter.HttpWebHandlerAdapter : [75e2975a-4] Completed 200 OK

从你的日志来看,问题仍然是 Spring Security 的默认行为,**当请求未通过认证时,会将用户重定向到默认的 `/login` 页面**。这在 WebFlux 环境中也是类似的。下面我们详细分析和解决问题。

你的项目中提到了 **Spring Gateway**。在 Gateway 中,
Spring Security 使用的是基于 **WebFlux** 的 `SecurityWebFilterChain`,
而不是传统的 `WebSecurityConfigurerAdapter`。

因此,`WebSecurityConfigurerAdapter` 的配置可能未完全生效,或者 Gateway 的默认行为覆盖了你的配置。

所以我们也就知道了Config没有注册成功的原因

我尝试将该配置 copy 到一个下属微服务中,通用的配置在其他服务里项目运行没有问题,实现了我所想要的效果,那么问题确认是 该方法在 gateway 中不适用,不适用的原因肯定是因为 SecurityConfig。

通过询问ChatGpt得到以下回复:

image.png

得出结论:Gateway中不可使用WebSecurityConfigurerAdapter,不生效

修改代码


@Configuration
public class SecurityConfig {
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
                // 关闭 csrf 校验
                .csrf().disable()
                .authorizeExchange()
                // 允许 /actuator/health 可不认证
                .pathMatchers("/actuator/health").permitAll() 
                // 禁止 /actuator/**,直接拒绝
                .pathMatchers("/actuator/**").denyAll()      
                // 其他所有请求都允许通过
                .anyExchange().permitAll()
                .and()
                // 禁用 HTTP Basic 认证
                .httpBasic().disable()                     
                // 禁用表单登录
                .formLogin().disable();                    
        return http.build();
    }

至此,成功

依旧存在的问题:

1 A B 两个微服务中如果都开启了 Security,会导致fegin调用产生403异常。

而我们只开放了gateway端口,其他都是内网访问,所以除了Gateway的都没有添加 Security

2 文件上传暂时会有 “An expected CSRF token cannot be found” 异常,问题代看 Spring Security 默认会对 POST 请求进行 CSRF 校验