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
.
@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?
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.