Search code examples
springspring-securityswagger-uispringfox

How to secure swagger UI with basic Authentication when using JWT Authentication


Dears

I am using spring fox with Spring Security. I have a custom JwrRequestFilter that extracts the jwt from the request and authenticates the user.

My issue is, that I need a basic popup authentication alert to appear when user hits /swagger-ui.html

here is my Security Config .configure() method:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .cors()
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .csrf().disable()
            .formLogin().disable()
            .httpBasic().disable()
            .exceptionHandling()
            .authenticationEntryPoint(new JwtAuthenticationEntryPoint())
            .and()
            .authorizeRequests()
            .antMatchers("/",
                    "/error",
                    "/favicon.ico",
                    "/**/*.png",
                    "/**/*.gif",
                    "/**/*.svg",
                    "/**/*.jpg",
                    "/**/*.html",
                    "/**/*.css",
                    "/**/*.js",
                    "/v2/api-docs",
                    "/configuration/ui",
                    "/swagger-resources/**",
                    "/configuration/security",
                    "/swagger-ui.html",
                    "/review/notify",
                    "/demo/**",
                    "/communication/**",
                    "/info/**",
                    "/images/***",
                    "/images/**/**",
                    "/webjars/**",
                    "/scrapper/**",
                    "/", "/actuator/**").permitAll()
            .antMatchers("/auth/**",
                    "/oauth2/**", "/internal/**")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .oauth2Login()
            .authorizationEndpoint()
            .baseUri("/oauth2/authorize")
            .authorizationRequestRepository(cookieAuthorizationRequestRepository())
            .and()
            .redirectionEndpoint()
            .baseUri("/oauth2/callback/*")
            .and()
            .userInfoEndpoint()
            .userService(customOAuth2UserService)
            .and()
            .successHandler(oAuth2AuthenticationSuccessHandler)
            .failureHandler(oAuth2AuthenticationFailureHandler);

    http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}

And the jwtRequestFilter:

 @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws ServletException, IOException {
    final String requestTokenHeader = request.getHeader(Constants.AUTHORIZATION_HEADER);
    String username = null;
    String jwtToken = null;
    // JWT Token is in the form "Bearer token". Remove Bearer word and get
    // only the Token
    if (requestTokenHeader != null && requestTokenHeader.startsWith(Constants.TOKEN_PREFIX)) {
        jwtToken = requestTokenHeader.substring(7);
        try {
            username = jwtTokenUtil.getEmailFromToken(jwtToken);
        } catch (IllegalArgumentException e) {
            log.warn("Unable to get JWT Token");
        } catch (ExpiredJwtException e) {
            log.warn("JWT Token has expired");
        }
    } else {
        log.warn("JWT Token does not begin with Bearer String");
    }

    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

        if (username.equals("scrapper-api")) {
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                    "scrapper-api", null, Arrays.asList());
            usernamePasswordAuthenticationToken
                    .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        } else {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            // if token is valid configure Spring Security to manually set
            // authentication
            if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                // After setting the Authentication in the context, we specify
                // that the current user is authenticated. So it passes the
                // Spring Security Configurations successfully.
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
    }
    chain.doFilter(request, response);
}

Spring fox version is 3.0 Spring framework version is 2.7.0


Solution

  • The solution was implementing a second WebSecurityConfigurerAdapter which will handle only swagger-ui/ paths

    @Configuration
    @Order(1)
    public class SecurityConfig2 extends WebSecurityConfigurerAdapter {
    
    @Autowired
    PasswordEncoder encoder;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/swagger-ui/**")
                .authorizeRequests()
                .anyRequest().hasRole("FORM_USER")
                .and()
                .httpBasic();
    }
    
    // If you want to use in-memory authentication for testing
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user")
                .password(encoder.encode("test"))
                .roles("FORM_USER");
    }
    
    
    }
    

    This is using the basic in-memory authentication which was enough for my case but can be extended to use UserDetailsService

    For more reference: How can I implement Basic Authentication with JWT authentication in Spring Boot?