Spring boot Security custom filter does not invoke

296 Views Asked by At

I am developing an application following microservice architecture using spring boot 3.2.0 and Java 17. I have one service - AuthService for authentication purpose. Users' login request will be routed by API gateway to AuthService and get a JWT token upon providing valid user credentials.

Now, user has a valid JWT token. By putting this token in request header in Bearer token format user can make http request to any other services (i.e. employeeService, hospitalService..; i will refer these services as OtherService). I don't want to burden OtherService with the token validation codes. Rather, there is an API endpoint at AuthService -> /auth/validate. This endpoint will validate the jwt token and returns a cusotm UserDetails object.

In a nutshell, the workflow is something like this - if user want to see list of employees, first user will provide username, password. This will be validated by AuthService and returns a JWT token. then, with this token user will make request to employeeService. There will be filter in EmployeeService which will check if there is any Authoriztion token in the request header and make rest call to AuthService to get a UserDetail object from it.

[P.S. I am new to spring security; I am not sure if this approach is ideal or not]

Bellow, I am providing the code portion of EmployeeService.

  1. ApplicationSecurity
@RefreshScope
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class ApplicationSecurity {

    private final AppSecurityFilter appSecurityFilter;


    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        return http
                .csrf(AbstractHttpConfigurer::disable)
                .cors(AbstractHttpConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterBefore(appSecurityFilter, UsernamePasswordAuthenticationFilter.class)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/health")
                        .permitAll()
                        .anyRequest()
                        .authenticated())
                .build();
    }
}

Note: I have not defined any AuthenticationManager or AuthenticationProvider bean here since I am not validating or getting user object from the db here in EmployeeService rather calling AuthService to do this from the AppSecurityFilter.

2. AppSecurityFilter

@AllArgsConstructor
@Component
public class AppSecurityFilter extends OncePerRequestFilter {

    private static String AUTH_SERVER_VALIDATE_TOKEN_URL = "http://AUTH-SERVICE/auth/api/v1/validate";

    private RestTemplate restTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        final String header = request.getHeader(AUTHORIZATION);

        if (isEmpty(header) || !header.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        ResponseEntity<AuthResponseDto> authResponse = // get userDetails form authService via restTeamplate post call

        UserResponseDto user = authResponse.getBody().getUserResponseDto();

        SecurityContextHolder.getContext().setAuthentication(
                new UsernamePasswordAuthenticationToken(
                        user,
                        null,
                        user.getAuthorities()
                )
        );

        filterChain.doFilter(request, response);
    }
}

So, the issue I am facing is; even though I set Authorization token in the request header; EmployeeService application redirects the request to spring's default login page. Furthermore, the AppSecurityFilter never invokes.

What I am missing here, and what are the necessary steps to make this work?

2

There are 2 best solutions below

0
Erfan Ahmed On BEST ANSWER

As @FabioFranco mentioned, I didn't miss anything in the security configuration. The problem was occurring due to @RefreshScope annotation added in the ApplicationSecurity class. (p.s. I didn't add this annotation in the question at first thinking that this wasn't related to the issue).

Adding my findings here if someone stumble upon this type of issue in future.

After investing the log, I found that if @RefreshScope is added then there were two separate logs of DefaultSecurityFilterChain at the time of application startup -

line1: o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [
    DisableEncodeUrlFilter, 
    WebAsyncManagerIntegrationFilter,
    SecurityContextHolderFilter,
    HeaderWriterFilter,
    LogoutFilter,
    AppSecurityFilter,  <------------------- my custom filter added
    RequestCacheAwareFilter,
    SecurityContextHolderAwareRequestFilter,
    AnonymousAuthenticationFilter,
    SessionManagementFilter,
    ExceptionTranslationFilter,
    AuthorizationFilter
]

line2: o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [
    DisableEncodeUrlFilter,
    WebAsyncManagerIntegrationFilter,
    SecurityContextHolderFilter,
    HeaderWriterFilter,
    CorsFilter,
    CsrfFilter,
    LogoutFilter,
    UsernamePasswordAuthenticationFilter,
    DefaultLoginPageGeneratingFilter,
    DefaultLogoutPageGeneratingFilter,
    BasicAuthenticationFilter,
    RequestCacheAwareFilter,
    SecurityContextHolderAwareRequestFilter,
    AnonymousAuthenticationFilter,
    ExceptionTranslationFilter,
    AuthorizationFilter
]

As you can see, the immediate second line of the log doesn't have the custom filter I have configured.

If I removed the @RefreshScope annotation from the class, there was only one line of log printed for DefaultSecurityFilterChain -

DefaultSecurityFilterChain : Will secure any request with [
    DisableEncodeUrlFilter,
    WebAsyncManagerIntegrationFilter,
    SecurityContextHolderFilter,
    HeaderWriterFilter,
    LogoutFilter,
    AppSecurityFilter,  <------------------ my custom filter
    RequestCacheAwareFilter,
    SecurityContextHolderAwareRequestFilter,
    AnonymousAuthenticationFilter,
    SessionManagementFilter,
    ExceptionTranslationFilter,
    AuthorizationFilter
]

And by removing @RefreshScope from the ApplicationSecurity class, everything works as expected.

Why I added @RefreshScope at the first place? I was reading some properties form the ConfigServer.

Why adding @RefreshScope makes spring security to fallback to default configuration? I haven't figured that out yet.

2
Fabio Franco On

It looks like your service has the role of a resource server in an oauth2 workflow. Is that correct? If so, I'd recommend following the instructions on the official spring security docs on how to authorize resources with a bearer token.

Furthermore, the way spring security docs recommend us to add filters to the security filter chain has a little caveat you should follow.

Also at a first glance I couldn't see what you are missing, but if you provide a bit more context, such as spring boot version as well as configuration files, maybe it's possible to provide more helpul insights.