Spring Security - Specify which X509 can access which specific endpoint

Viewed 1060

Small question regarding how to use Spring Security to specify which client certificate can access what specific predefined endpoint, please.

By predefined endpoint, I mean the web application has default endpoint (not those I defined via @RestController) such as the actuator endpoint /actuator/health, /actuator/prometheus, or the Spring Cloud Config endpoints, such as /config/myservice/ There is no possibility to @PreAuthorize.

I would like to just specify which client certificate can access which endpoint, such as:

  • Client certificate with UID=Alice can access /actuator/health and /config/myservice.
  • Client certificate with UID=Bob can access /actuator/prometheus

There are many examples online on, How to extract X509 certificate:

But how to configure it in the application, i.e., this sort of mapping of which certificate can access what?

Thank you

1 Answers

@Setu gave you the essential information to solve the problem.

Please, consider the following code adapted from the one that can be found in one of the articles you cited:

@SpringBootApplication
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class X509AuthenticationServer extends WebSecurityConfigurerAdapter {
  ...

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
        // Define the mapping between the different endpoints
        // and the corresponding user roles
        .antMatchers("/actuator/health").hasRole("ACTUATOR_HEALTH")
        .antMatchers("/actuator/prometheus").hasRole("ACTUATOR_PROMETEUS")
        .antMatchers("/config/myservice").hasRole("CONFIG_MYSERVICE")
        // Please, adjust the fallback as appropriate
        .anyRequest().authenticated()
      .and()
        // Configure X509Configurer (https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configurers/X509Configurer.html)
        .x509()
          .subjectPrincipalRegex("CN=(.*?)(?:,|$)")
          .userDetailsService(userDetailsService())
    ;
  }

  @Bean
  public UserDetailsService userDetailsService() {
    // Ideally this information will be obtained from a database or some
    // configuration information
    return new UserDetailsService() {

      @Override
      public UserDetails loadUserByUsername(String username) {

        Objects.requireNonNull(username);

        List<GrantedAuthority> authorities = null;
        switch (username) {
          // Match the different X509 certificate CNs. Maybe you can use the
          // X509 certificate subject distinguished name to include the role in
          // some way and obtain it directly with the subjectPrincipalRegex
          case "Alice":
            authorities = AuthorityUtils
              .commaSeparatedStringToAuthorityList("ROLE_ACTUATOR_HEALTH, ROLE_CONFIG_MYSERVICE");
            break;

          case "Bob":
            authorities = AuthorityUtils
              .commaSeparatedStringToAuthorityList("ROLE_ACTUATOR_PROMETHEUS");
            break;

          default:
            throw new UsernameNotFoundException(String.format("User '%s' not found!", username));
        }

        return new User(username, "", authorities);
      }
    };
  }
}

Please, adapt the code as you need to meet your actual endpoints and users.

Related