I have AuthenticatedUser class which extends org.springframework.security.core.userdetails.User. It is constructed from JWT.
Every request from the browser has JWT attached to the request. The pages are either protected or public. If a page is protected my @AuthenticationPrincipal AuthenticatedUser principal
is populated but if the if the page is not protected my @AuthenticationPrincipal AuthenticatedUser principal
is not populated. And the problem arises here. Now I have a case where I want to get the @AuthenticationPrincipal AuthenticatedUser principal
even when the page is not protected. But now in unprotected pages my @AuthenticationPrincipal AuthenticatedUser principal
appears to be null, even though I send JWT.
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
private final List<String> protectedPaths = List.of(
"/secret/*/**",
"/private"
);
// here if I add the public into the list principal is populated but the anon users cannot access the public link. If I don't add the public link then principal appears to be null.
@Bean
@Order(2)
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.securityMatchers(customizer -> customizer.requestMatchers(protectedPaths.toArray(new String[0])))
.csrf().disable()
.authorizeHttpRequests(requests -> requests.anyRequest().authenticated())
.oauth2ResourceServer()
.jwt(customizer -> customizer.jwtAuthenticationConverter(new UserAuthenticationTokenConverter()));
return http.build();
}
}
And my controller is as following:
@GetMapping(path = "/public/{id}")
public ListingDetailedView
findById(@PathVariable String id, @AuthenticationPrincipal AuthenticatedUser principal) {
// here principal is null
return listingService.findBySearchId(id, principal);
}
How I can populate the principal on not protected pages?
Edit: My other security filter is as follows which handles only one end-point:
@Configuration
@RequiredArgsConstructor
public class PartnerResourceServerConfig {
private final PartnerUserDetailsService userDetailsService;
private final AuthenticationFailureHandler failureHandler;
@Bean
@Order(1)
public SecurityFilterChain partnerSecurityFilterChain(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
var authenticationManager = authManagerBuilder.build();
http.securityMatcher("/hidden/**")
.authorizeHttpRequests(customizer -> customizer.anyRequest().authenticated())
.authenticationManager(authenticationManager)
.addFilterBefore(partnerApiKeyAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sessionManagementCustomizer -> sessionManagementCustomizer
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
PartnerApiKeyAuthenticationFilter partnerApiKeyAuthenticationFilter(AuthenticationManager authenticationManager) {
var filter = new PartnerApiKeyAuthenticationFilter("/hidden/**");
filter.setAuthenticationFailureHandler(failureHandler);
filter.setAuthenticationManager(authenticationManager);
return filter;
}
}
Additional classes:
@NoArgsConstructor
public class UserAuthenticationTokenConverter implements Converter<Jwt, AuthenticationToken> {
@Override
public AuthenticationToken convert(Jwt source) {
var authenticatedUser = AuthenticatedUser.from(source);
return new AuthenticationToken(source, authenticatedUser);
}
}
AuthenticationToken:
public class AuthenticationToken extends AbstractAuthenticationToken {
private final Jwt jwt;
private final AuthenticatedUser authenticatedUser;
public AuthenticationToken(Jwt jwt, AuthenticatedUser authenticatedUser) {
super(List.of());
this.jwt = jwt;
this.authenticatedUser = authenticatedUser;
}
@Override
public Object getCredentials() {
return this.jwt;
}
@Override
public Object getPrincipal() {
return authenticatedUser;
}
@Override
public boolean isAuthenticated() {
return true;
}
}
Simplified AuthenticatedUser:
@Getter
public class AuthenticatedUser extends User {
private final String id;
private final UserRole role;
private final String email;
private final boolean isPhoneVerified;
private AuthenticatedUser(String id, UserRole role, String email, String token,
boolean isPhoneVerified) {
super(id, token, true, true, true, true, List.of());
this.id = id;
this.role = role;
this.email = email;
this.isPhoneVerified = isPhoneVerified;
}
@SuppressWarnings("unchecked")
public static AuthenticatedUser from(Jwt jwt) {
var id = jwt.getSubject();
var roleClaim = jwt.getClaimAsString("type");
var userRole = UserRole.forValue(roleClaim);
var userEmail = jwt.getClaimAsString("email");
var isPhoneVerifiedString = jwt.getClaimAsBoolean("phone_number_verified");
var isPhoneVerified = isPhoneVerifiedString != null ? isPhoneVerifiedString : false;
return new AuthenticatedUser(id, userRole, userEmail, jwt.getTokenValue(), isPhoneVerified);
}
}