掘金 后端 ( ) • 2024-04-07 15:10

背景

最近被市安全办公室通知,A服务的/actuator/*接口存在安全隐患

问题现象

可公网且未身份验证的访问/actuator/*接口,env中包含非常多的系统信息,包括数据库连接,properties等。

解决思路

注意:网上很多文章都是通过添加 spring security进行拦截,但是我们的项目是微服务节点,并没有使用它。 理由如下:

  1. 框架太重,如果仅做一个basic验证简直杀鸡用牛刀;
  2. 存在隐患。因为微服务本身不需要,贸然引用很有可能出现其他异常。

所以,我们选择自己实现功能。思路如下:

  1. 添加Web拦截器
  2. Web拦截器中去判断url和检查Authorization请求头
  3. 根据配置文件,判断Authorization的内容是否有效,无效则返回错误信息

解决方案(代码)

  1. 编写拦截器

代码描述:编写一个拦截器,用于处理basic身份验证信息是否有效。

public class ActuatorEndpointInterceptor implements Ordered, HandlerInterceptor {

    private final static String BASIC_USERNAME_PASSWORD = "username:password";

    protected String basicAuth() {
        return BASIC_USERNAME_PASSWORD;
    }


    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String authValue = request.getHeader(GatewayParseRequestHeadersConstant.AUTHORIZATION);
        if (StringUtils.isNotBlank(authValue)) {
            String basic = authValue.replace("Basic", "");
            String encode = Base64Util.decode2String(basic.trim());
            return encode.equals(this.basicAuth());
        }
        response.getOutputStream().write("Illegal Request,Authorization error".getBytes(StandardCharsets.UTF_8));
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        return false;
    }

}
  1. 配置Spring Actuator Configuration
@RequiredArgsConstructor
@Configuration
@ConditionalOnClass(InvalidEndpointRequestException.class)
public  class WebActuatorMvcEndpointConfiguration extends WebMvcEndpointManagementContextConfiguration {

    /**
     * spring ActuatorEndpointInterceptor 身份拦截器
     */
    @Bean
    @ConditionalOnMissingBean
    public ActuatorEndpointInterceptor actuatorEndpointInterceptor() {
        return new ActuatorEndpointInterceptor();
    }

    @Bean
    public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
                                                                         ServletEndpointsSupplier servletEndpointsSupplier,
                                                                         ControllerEndpointsSupplier controllerEndpointsSupplier,
                                                                         EndpointMediaTypes endpointMediaTypes,
                                                                         CorsEndpointProperties corsProperties,
                                                                         WebEndpointProperties webEndpointProperties,
                                                                         Environment environment,
                                                                         ObjectProvider<ActuatorEndpointInterceptor[]> interceptors
    ) {
        WebMvcEndpointHandlerMapping webMvcEndpointHandlerMapping = super.webEndpointServletHandlerMapping(webEndpointsSupplier, servletEndpointsSupplier, controllerEndpointsSupplier, endpointMediaTypes, corsProperties, webEndpointProperties, environment);
        ActuatorEndpointInterceptor[] ifAvailable = interceptors.getIfAvailable();
        if (ifAvailable != null) {
            webMvcEndpointHandlerMapping.setInterceptors(Arrays.stream(ifAvailable).toArray());
        }
        return webMvcEndpointHandlerMapping;
    }
}

image.png 代码描述:SpringActuator 使用的WebMvcEndpointHandlerMappingRequestMappingInfoHandlerMapping的之类,专门为SpringActuator配置。它默认的Bean是没有任何拦截器相关的配置,如下图:

image.png 但是实际WebMvcEndpointHandlerMapping有一个setInterceptors的方法。 所以上述第二段代码,就是改了这部分逻辑。

冷知识

  1. WebMvcEndpointHandlerMapping的拦截器不用配置includePath和excludePath。
  2. basic身份验证,实际上可以通过返回特定的数据直接让浏览器弹出一个框输入账号密码。像这样

image.png

Spring Web拦截器让浏览器自动弹出账号密码框 - 掘金 (juejin.cn)