Search code examples
spring-bootspring-securityspring-annotationswebsecurity

How to pass parameters from custom annotation to WebSecurityConfigurer in library


Hi we are building custom spring security library

we need to pass {"/v1","/v2"} paths through @EnableMySpringSecurity(excludePaths = {"/v1","/v2"}) which is present in the main project to library websecurity so we can ignore those endpoints from security

@EnableMySpringSecurity(excludePaths = {"/v1","/v2"})
@EnableWebMvc
public class WebAppConfiguration extends BaseWebAppConfiguration {

Websecurity Configuration from custom JAR

@EnableWebSecurity(debug = true)
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
public void configure(WebSecurity web){
 web.ignoring().antMatchers(excludePaths );

How to pass values that are passed from @EnableMYSpringSecurity to the webSecuirty web.ignoring.antMatchers

our annotation configuration

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EnableMySpringSecurity {
    String[] excludePaths() default {};
}

I have tried ApplicationStartupListener but problem with this is, it is initialized after websecuirty configuration

public class ApplicationStartupListener implements
        ApplicationListener<ContextRefreshedEvent> {

    private ApplicationContext context;

    private EnableMySSAnnotationProcessor processor;

    public ApplicationStartupListener(ApplicationContext context,
                                      EnableMySSAnnotationProcessor processor) {
        this.context = context;
        this.processor = processor;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        Optional<EnableMySpringSecurity> annotation =
                context.getBeansWithAnnotation(EnableMySpringSecurity.class).keySet().stream()
                        .map(key -> context.findAnnotationOnBean(key, EnableMySpringSecurity.class))
                        .findFirst();
        annotation.ifPresent(enableMySpringSecurity-> processor.process(enableMySpringSecurity));
    }
}

Solution

  • One way you can do this is with the @Import annotation:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Import(MyWebSecurityConfiguration.class)
    @EnableWebSecurity
    public @interface EnableMyWebSecurity {
        String[] paths() default [];
    }
    

    and then the ImportAware interface:

    @Configuration
    public class MyWebSecurityConfiguration implements ImportAware {
        private String[] paths;
    
        @Bean
        WebSecurityCustomizer paths() {
            return (web) -> web.ignoring().antMatchers(paths);
        }
    
        @Override
        public void setImportMetadata(AnnotationMetadata importMetadata) {
            EnableMyWebSecurity annotation = importMetadata
                .getAnnotations().get(EnableMyWebSecurity.class).synthesize();
            this.paths = annotations.paths();
        }
    
    }
    

    Note, by the way, that when you exclude paths, Spring Security cannot add security headers as part of the response. If you want those endpoints to be protected by Spring Security, but public, then consider instead:

    @Configuration
    public class MyWebSecurityConfiguration implements ImportAware {
        private String[] paths;
    
        @Bean
        @Order(1)
        SecurityFilterChain paths(HttpSecurity http) {
            http
                .requestMatchers((requests) -> requests.antMatchers(paths))
                .authorizeRequests((authorize) -> authorize
                    .anyRequest().permitAll()
                );
            return http.build();
        }
    
        @Override
        public void setImportMetadata(AnnotationMetadata importMetadata) {
            EnableMyWebSecurity annotation = importMetadata
                .getAnnotations().get(EnableMyWebSecurity.class).synthesize();
            this.paths = annotations.paths();
        }
    
    }
    

    The benefit of the second approach is that Spring Security won't require authentication, but will add secure response headers.