0

I'm trying to limit access to the Swagger User Interface (UI) to specific roles (e.g. Swagger API user, System Admin user, etc...)

I tried the answer in this SO question - Restrict access to Swagger UI

So my code class looks like

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * XLogger.
     */
    private static final XLogger LOG = XLoggerFactory.getXLogger(WebSecurityConfig.class);

    @Value("${my.property.allowedOrigins}")
    private String[] corsAllowedOrigins;

    @Value("${my.property.excludeUrlPattern}")
    private String[] excludeUrlPattern;

    @Autowired
    ApplicationContext applicationContext;

    /**
     * The AuthenticationSuccessHandler.
     */
    @Autowired
    HSSAuthenticationSuccessHandler authenticationSuccessHandler;

    /**
     * The runtime properties
     */
    @Autowired
    RuntimeProperties runtimeProperties;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        LOG.entry(http);

        String[] noAuthPermitAllPatterns = runtimeProperties.getApplicationProperties().getNoAuthPermitAllPatterns();

        http.authorizeRequests()
                .antMatchers("/swagger-ui/**", "/**/swagger-ui/**", "/v3/api-docs/**", "/**/v3/api-docs/**")
                .hasRole("ADMIN").antMatchers(noAuthPermitAllPatterns).permitAll().anyRequest().authenticated().and()
                .formLogin().failureHandler(authenticationFailureHandler()).successHandler(authenticationSuccessHandler)
                .loginPage("/saml/login").permitAll().and().logout().logoutSuccessUrl("/logoutSuccess").permitAll()
                .invalidateHttpSession(true).and().csrf().disable().cors()
                .configurationSource(corsConfigurationSource());
        http.addFilterBefore((MetadataGeneratorFilter) applicationContext.getBean("metadataGeneratorFilter"),
                ChannelProcessingFilter.class).addFilterAfter(
                        (FilterChainProxy) applicationContext.getBean("samlFilter"), BasicAuthenticationFilter.class);

        LOG.exit();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        LOG.entry();

        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList(corsAllowedOrigins));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT"));
        configuration.setAllowCredentials(true);
        // the below three lines will add the relevant CORS response headers
        configuration.addAllowedOrigin("*");
        configuration.addAllowedHeader("*");
        configuration.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);

        return LOG.exit(source);
    }

    @Override
    @Bean(name = "webAuthenticationManagerBean")
    public AuthenticationManager authenticationManagerBean() throws Exception {
        LOG.entry();
        return LOG.exit(super.authenticationManagerBean());
    }

    /**
     * Authentication Failure Handler
     *
     * @return The Authentication Failure Handler
     */
    @Bean(name = "authenticationFailureHandler")
    protected AuthenticationFailureHandler authenticationFailureHandler() {
        LOG.entry();

        String defaultFailureUrl = runtimeProperties.getApplicationProperties().getDefaultFailureURL();
        ExceptionMappingAuthenticationFailureHandler authFailureHandler = new ExceptionMappingAuthenticationFailureHandler();
        authFailureHandler.setDefaultFailureUrl(defaultFailureUrl);
        Map<String, String> mappings = new HashMap<>();
        mappings.put("org.springframework.security.authentication.InternalAuthenticationServiceException",
                defaultFailureUrl);
        mappings.put("org.springframework.security.saml.SAMLAuthenticationToken", defaultFailureUrl);
        mappings.put("org.springframework.security.authentication.AuthenticationServiceException", defaultFailureUrl);
        authFailureHandler.setExceptionMappings(mappings);

        return LOG.exit(authFailureHandler);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        LOG.entry(web);
        web.ignoring().antMatchers(excludeUrlPattern);
        LOG.exit();
    }
}

This still allows me to access the Swagger UI @ http://{localUrl}/swagger-ui/index.html?configUrl=/{localUrl}/v3/api-docs/swagger-config

Also, what's the difference between two configure( ) methods, where one uses HttpSecurity and the other uses WebSecurity? The excludeUrlPattern does include swagger patterns, so I wondered if that creates a conflict with what I'm trying to do.

I guess I'm expecting a 401 Unauthorized response. I'm totally new to Swagger, so any advice is appreciated!

Shar1er80
  • 9,001
  • 2
  • 20
  • 29
  • Did you see this? https://stackoverflow.com/questions/61859801/how-to-trim-swagger-docs-based-on-current-user-role-in-java-spring/61860729#61860729 – aksappy Jan 19 '21 at 21:10
  • @aksappy I don't think I'm wanting to "trim operations displayed by swagger-ui.html", I want to restrict the UI all together. – Shar1er80 Jan 19 '21 at 22:27
  • I'm questioning the the actual use case for such a change. A Swagger page is documentation for your API - surely a user that requires viewing the Swagger UI page would have needed to check the definition of your `/login` (or equivalent) endpoint to provide credentials which include the role you wish to check? Why does Basic Auth not suffice? – filpa Jan 20 '21 at 21:46

1 Answers1

0

The same way as the other endpoints. This will still let other download the swagger config, you could block /api-docs/** as well if you really need that level of control.

...
  .antMatchers("/swagger-ui/**") // For URI starting with swagger-ui
  .hasRole("USER")
  .antMatchers("/**/swagger-ui/**") // For URI that contains swagger-ui
  .hasRole("USER")
...
aksappy
  • 3,400
  • 3
  • 23
  • 49
  • My endpoint actually breaks down to something like http://localhost:{portnumber}/***/********/swagger-ui/index.html?configUrl=/***/********/v3/api-docs/swagger-config So I've tried what you recommended, and I've tried with the patterns of "/***/********/swagger-ui*/**". Both ways still allow access to the swagger ui. – Shar1er80 Jan 20 '21 at 14:31
  • @Shar1er80 I have updated the answer. Spring security looks for ant patterns which is documented here. https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/AntPathMatcher.html. The URL pattern in OP was taken the first time. – aksappy Jan 20 '21 at 21:03
  • I've updated my question to include more source code. I've also added your suggestion, but the swagger ui is still accessible. – Shar1er80 Jan 22 '21 at 15:17
  • Have you defined any security configuration in your application.properties? Is swagger url the only URL that has this problem? I can setup a demo working application in an hour or so, to show the working version of this. – aksappy Jan 22 '21 at 15:52
  • I'm modifying existing code, so I don't know what all is setup. Looking through the application.yaml I don't see anything that resembles security setups (e.g. Basic Auth, API Keys, Bearer Auth, etc...) – Shar1er80 Jan 22 '21 at 16:14