0

I am using Okta to handle authentication and authorization in a Spring Boot REST(ful) API Resource Server. I started from an example they provide here. To handle users, I implemented a filter to store an entity (in my relational database) containing the uid claim before each request, since every route of my application requires authentication. Here is the code for the filter:

@Component
@RequiredArgsConstructor
public class AppUserRegistrationFilter extends OncePerRequestFilter {

    private final AppUserService appUserService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        
        appUserService.checkAppUserRegistration(jwtAuthenticationToken);

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

Here is the code for the web configuration class:

@Configuration
@RequiredArgsConstructor
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final AppUserService appUserService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt();
        http
                .cors();
        http
                .addFilterAfter(new AppUserRegistrationFilter(appUserService), BearerTokenAuthenticationFilter.class);

        Okta.configureResourceServer401ResponseBody(http);
    }

}

The AppUserRepository has nothing special, it just extends JpaRepository<AppUser, Long> and AppUser is the entity that contains the uid and email strings and a couple of LocalDateTime fields for the registration and last access time. Finally, here is the code for the method from AppUserService implementation class:

@Override
public void checkAppUserRegistration(JwtAuthenticationToken jwtAuthenticationToken) {
    String uid = (String) jwtAuthenticationToken.getTokenAttributes().get("uid");
    Optional<AppUser> appUserOptional = appUserRepository.findByUid(uid);
    LocalDateTime now = LocalDateTime.now();
    // By default, the name maps to the sub claim according to Spring Security docs
    String email = jwtAuthenticationToken.getName();
    if (appUserOptional.isEmpty()) {
        AppUser newUser = AppUser.builder()
                .uid(uid)
                .email(email)
                .registrationDateTime(now)
                .lastAccessDateTime(now)
                .build();
        appUserRepository.save(newUser);
    } else {
        AppUser appUser = appUserOptional.get();
        if (!appUser.getEmail().equals(email)) {
            // User changed email address, update accordingly
            appUser.setEmail(email);
        }
        appUser.setLastAccessDateTime(now);
        appUserRepository.save(appUser);
    }
}

If I start the frontend app, which is this one, I login with the user I created from the Okta developer dashboard and I check the "messages" section, in the backend I see that the user is correctly created but then the filter is immediately invoked again and if I check the database I see that the registration date and the last access date differ.

Why does the filter get called twice? Is this a normal behaviour?

Naman
  • 27,789
  • 26
  • 218
  • 353

1 Answers1

2

Spring Boot will automatically register any Spring bean that extends Filter with the Servlet Container.

See Spring Boot documentation:

Registering Servlets, Filters, and Listeners as Spring Beans

Any Servlet, Filter, or servlet *Listener instance that is a Spring bean is registered with the embedded container.

Since your AppUserRegistrationFilter class is annotated with @Component, Spring will automatically create a singleton bean for the class, and it is therefore auto-registered with the Servlet container.

When you call http.addFilterAfter(new AppUserRegistrationFilter(appUserService), BearerTokenAuthenticationFilter.class);, you are manually creating another instance of the class, and manually registering the filter with the Servlet container.

Hence your code has registered two different instances of the class at the same time.

Solution: Don't call addFilterAfter() or remove the @Component annotation.

Andreas
  • 154,647
  • 11
  • 152
  • 247