Search code examples
springspring-securityspring-bootspring-boot-actuator

Define basic-security users, passwords and roles in application.yml


I am looking for a way to secure methods with the @Secured annotation of Spring Boot. For about 10-15 users, I wouldn't want to connect to a database and obtain the users and their authorities/roles from there but rather store them locally in the profile-specific application.yml file. Is there a concept in Spring Boot supporting this idea? All I could find so far works with the basic security actuator ('org.springframework.boot:spring-boot-starter-security') and looks like this:

security:
  basic:
    enabled: true
  user:
    name: admin
    password: admin
    role: EXAMPLE

However, I'm still able to access a method annotated with @RolesAllowed("READ") even though I would assume that user admin shouldn't have access to said method. My SecurityConfiguration looks like this:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Profile("secure")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .fullyAuthenticated()
                .and()
                .httpBasic();

        http.sessionManagement()
                .sessionFixation()
                .newSession();

        http.csrf().disable();
        http.headers().frameOptions().disable();

    }
}

Eventually this might be a different issue but maybe it's important for my own understanding.

I'm wondering how I could specify multiple users with different passwords and different roles in my application.yml and annotate methods to ensure only authorized users can access the methods.


Solution

  • It can be achieved with custom ConfigurationProperties:

    @ConfigurationProperties("application")
    public class ApplicationClients {
    
        private final List<ApplicationClient> clients = new ArrayList<>();
    
        public List<ApplicationClient> getClients() {
            return this.clients;
        }
    
    }
    
    @Getter
    @Setter
    public class ApplicationClient {
        private String username;
        private String password;
        private String[] roles;
    }
    
    @Configuration
    @EnableConfigurationProperties(ApplicationClients.class)
    public class AuthenticationManagerConfig extends
            GlobalAuthenticationConfigurerAdapter {
    
        @Autowired
        ApplicationClients application;
    
        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
            for (ApplicationClient client : application.getClients()) {
                auth.inMemoryAuthentication()
                        .withUser(client.getUsername()).password(client.getPassword()).roles(client.getRoles());
            }
        }
    
    }
    

    and then you can specify the users in your application.yml:

    application:
      clients:
        - username: rw
          password: rw
          roles: READ,WRITE
        - username: r
          password: r
          roles: READ
        - username: w
          password: w
          roles: WRITE
    

    don't forget to add spring-boot-configuration-processor to your build.gradle:

    compile 'org.springframework.boot:spring-boot-configuration-processor'
    

    Update April 2018

    For Spring Boot 2.0, I use the following class:

    @Configuration
    @EnableGlobalMethodSecurity(securedEnabled = true)
    @EnableWebSecurity
    @ConditionalOnWebApplication
    @EnableConfigurationProperties(ApplicationClients.class)
    @RequiredArgsConstructor
    @Slf4j
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        private final ApplicationClients application;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                .authorizeRequests()
                .requestMatchers(EndpointRequest.to("health")).permitAll()
                .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR")
                .antMatchers("/rest/**").authenticated()
                .antMatchers("/soap/**").authenticated()
                .and()
                .cors()
                .and()
                .httpBasic();
        }
    
        @Bean
        public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
            final InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            log.info("Importing {} clients:", application.getClients().size());
    
            application.getClients().forEach(client -> {
                manager.createUser(User.withDefaultPasswordEncoder()
                    .username(client.getUsername())
                    .password(client.getPassword())
                    .roles(client.getRoles())
                    .build());
                log.info("Imported client {}", client.toString());
            });
    
            return manager;
        }
    }
    

    Please keep in mind that User.withDefaultPasswordEncoder() is marked deprecated because of security-concerns.