javaspring-bootspring-security

Spring Security - Specify which X509 can access which specific endpoint


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:

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?


Solution

  • @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.