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?