javaspringspring-bootopenapispringdoc-openapi-ui

OpenAPI3 show methods based on Role via Spring Boot


I added this dependency to my Spring Boot application

 <dependency>
      <groupId>org.springdoc</groupId>
      <artifactId>springdoc-openapi-ui</artifactId>
      <version>1.4.3</version>
      <type>pom.sha512</type>
     </dependency>

I then was able to open : https://localhost:8443/v3/api-docs

The browser does ask me for my credentials, and as long as I enter the user/password right it works, but it shows me ALL the methods that are available globally. I would like only the methods the user has rights to, to show up in the api docs.

For a specific method is use this tag to authorize my call: @PreAuthorize("hasRole('USER') OR hasRole('ADMIN')")

This is my web security config class:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.inMemoryAuthentication()
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user").password(new BCryptPasswordEncoder().encode("blabl")).roles("USER")
                .and()
                .withUser("admin").password(new BCryptPasswordEncoder().encode("blabla")).roles("ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .anyRequest().authenticated()
                .and()
                .httpBasic();
    }
}

Solution

  • Basically, the way to do this is to utilize OperationCustomizer which you can then create conditional logic that excludes endpoints from the docs based on role:

    import org.springdoc.core.customizers.OperationCustomizer;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.web.method.HandlerMethod;
    import io.swagger.v3.oas.models.Operation;
    
    @Configuration
    public class OpenApiConfig
    {
    
        @Bean
        public OperationCustomizer operationCustomizer()
        {
            return (Operation operation, HandlerMethod handlerMethod) ->
            {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (authentication != null && authentication.isAuthenticated())
                {
                    PreAuthorize preAuthorize = handlerMethod.getMethodAnnotation(PreAuthorize.class);
                    if (preAuthorize != null)
                    {
                        String[] requiredRoles = preAuthorize.value().replace("hasRole('", "").replace("')", "").split(" OR ");
    
                        boolean hasRole = false;
                        for (GrantedAuthority authority : authentication.getAuthorities())
                        {
                            for (String role : requiredRoles)
                            {
                                if (authority.getAuthority().equals("ROLE_" + role))
                                {
                                    hasRole = true;
                                    break;
                                }
                            }
                            if (hasRole)
                            {
                                break;
                            }
                        }
    
                        if (!hasRole)
                        {
                            return null; // Exclude the operation if the user does not have any of the required roles
                        }
                    }
                } else
                {
                    System.out.println("No authenticated user found.");
                    return null;
                }
                return operation;
            };
        }
    }