绕过CAS从Spring启动应用程序中获取/安全健康信息

时间:2016-11-25 07:57:29

标签: java spring-security spring-boot spring-boot-actuator

我有一个使用CAS WebSecurity的Spring Boot应用程序,以确保所有传入的未经过身份验证的请求都被重定向到一个公共登录页面。

@Configuration
@EnableWebSecurity
public class CASWebSecurityConfig extends WebSecurityConfigurerAdapter {

我希望通过执行器公开健康端点,并添加相关的依赖关系。我想绕过CAS检查这些/健康URL,它们将被监控工具使用,因此在configure方法中,我添加了:

http.authorizeRequests().antMatchers("/health/**").permitAll();

这有效,但现在我想进一步调整它:

  • 详细的健康状况(即"完整内容"根据文档)应仅对某些特定监控用户可访问,其资产在属性文件中提供。
  • 如果没有提供身份验证,那么"仅限状态"应该退还。

关注http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-monitoring.html#production-ready-health-access-restrictions,我已经配置了以下属性,以便它可以正常工作:

management.security.enabled: true
endpoints.health.sensitive: false

但我对如何配置凭据有疑问......在http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-monitoring.html#production-ready-sensitive-endpoints之后,我在配置文件中添加了:

security.user.name: admin
security.user.password: secret

但是它不起作用 - 当我没有放置这些属性时,我看不到日志中生成的密码。

所以我试图添加一些自定义属性,比如

healthcheck.username: healthCheckMonitoring
healthcheck.password: healthPassword

并将这些注入我的Security配置中,以便configureGlobal方法变为:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
                            CasAuthenticationProvider authenticationProvider) throws Exception {

    auth.inMemoryAuthentication().withUser(healthcheckUsername).password(healthcheckPassword).roles("ADMIN");
    auth.authenticationProvider(authenticationProvider);
} 

在configure方法中,我将URL模式的配置更改为:

   http.authorizeRequests()
        .antMatchers("/health/**").hasAnyRole("ADMIN")
        .and().httpBasic()
        .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and().csrf().disable();

使用该配置,我在验证时获得完整内容,但从逻辑上讲,当我未经过身份验证时,我无法获得任何状态(向上或向下),因为请求甚至无法覆盖端点:它被安全配置拦截并拒绝。

如何调整Spring Security配置以使其正常工作?我觉得我应该以某种方式链接配置,CAS配置首先允许请求完全基于URL,以便请求然后命中第二个配置,如果提供凭据将执行基本的http身份验证,或者让否则,请求会以未经身份验证的方式命中端点,这样我才能获得"状态"结果..但与此同时,如果我正确配置它,我认为Spring Boot可以正确管理它。

谢谢!

1 个答案:

答案 0 :(得分:0)

解决方案并不是很好,但到目前为止,这对我有用:

在我的配置中(仅相关代码):

@Configuration
@EnableWebSecurity
public class CASWebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    //disable HTTP Session management
    http
        .securityContext()
        .securityContextRepository(new NullSecurityContextRepository())
        .and()
        .sessionManagement().disable();

    http.requestCache().requestCache(new NullRequestCache());

    //no security checks for health checks
    http.authorizeRequests().antMatchers("/health/**").permitAll();

    http.csrf().disable();

    http
        .exceptionHandling()
        .authenticationEntryPoint(authenticationEntryPoint());

    http // login configuration
        .addFilter(authenticationFilter())
        .authorizeRequests().anyRequest().authenticated();
}
}

然后我添加了一个特定的过滤器:

@Component
public class HealthcheckSimpleStatusFilter  extends GenericFilterBean {

private final String AUTHORIZATION_HEADER_NAME="Authorization";

private final String URL_PATH = "/health";

@Value("${healthcheck.username}")
private String username;

@Value("${healthcheck.password}")
private String password;

private String healthcheckRole="ADMIN";

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    HttpServletRequest httpRequest = this.getAsHttpRequest(request);

    //doing it only for /health endpoint.
    if(URL_PATH.equals(httpRequest.getServletPath())) {

        String authHeader = httpRequest.getHeader(AUTHORIZATION_HEADER_NAME);

        if (authHeader != null && authHeader.startsWith("Basic ")) {
            String[] tokens = extractAndDecodeHeader(authHeader);
            if (tokens != null && tokens.length == 2 && username.equals(tokens[0]) && password.equals(tokens[1])) {
                createUserContext(username, password, healthcheckRole, httpRequest);
            } else {
                throw new BadCredentialsException("Invalid credentials");
            }
        }
    }
    chain.doFilter(request, response);
}

/**
 * setting the authenticated user in Spring context so that {@link HealthMvcEndpoint} knows later on that this is an authorized user
 * @param username
 * @param password
 * @param role
 * @param httpRequest
 */
private void createUserContext(String username, String password, String role,HttpServletRequest httpRequest) {
    List<GrantedAuthority> authoritiesForAnonymous = new ArrayList<>();
    authoritiesForAnonymous.add(new SimpleGrantedAuthority("ROLE_" + role));
    UserDetails userDetails = new User(username, password, authoritiesForAnonymous);
    UsernamePasswordAuthenticationToken authentication =
        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
    SecurityContextHolder.getContext().setAuthentication(authentication);
}

private HttpServletRequest getAsHttpRequest(ServletRequest request) throws ServletException {
    if (!(request instanceof HttpServletRequest)) {
        throw new ServletException("Expecting an HTTP request");
    }
    return (HttpServletRequest) request;
}

private String[] extractAndDecodeHeader(String header) throws IOException {
    byte[] base64Token = header.substring(6).getBytes("UTF-8");

    byte[] decoded;
    try {
        decoded = Base64.decode(base64Token);
    } catch (IllegalArgumentException var7) {
        throw new BadCredentialsException("Failed to decode basic authentication token",var7);
    }

    String token = new String(decoded, "UTF-8");
    int delim = token.indexOf(":");
    if(delim == -1) {
        throw new BadCredentialsException("Invalid basic authentication token");
    } else {
        return new String[]{token.substring(0, delim), token.substring(delim + 1)};
    }
}

}