Issue
I am trying to add access control to a set of api endpoints and the problem I am running into is that the service is redirecting to /
regardless of whether the original request was /api/apple
or /api/orange
. I currently have a filter set up to read a custom http header to do the authentication and the filter I am using is extended from AbstractAuthenticationProcessingFilter. The documentation is saying that it is intended for the AbstractAuthenticationProcessingFilter
to redirect to a specific url upon successful authentication, but this is not the behavior I want for an api. I think I may be using the wrong Filter, but I don't know which one I should be using. Can I get some help on what I may be doing wrong and what I should be doing?
Filter Chain Configuration:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
AuthenticationManager customAuthenticationManager(PreAuthProvider preAuthProvider) {
return new ProviderManager(List.of(preAuthProvider));
}
@Bean
SessionAuthFilter customAuthFilter(AuthenticationManager authManager, CustomUserDetails userDetails) {
return new SessionAuthFilter(
new OrRequestMatcher(
new AntPathRequestMatcher("/apple/**"),
new AntPathRequestMatcher("/orange/**")
),
authManager,
userDetails);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, SessionAuthFilter authFilter) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.accessDeniedHandler(new AccessDeniedHandlerImpl())
.and()
.formLogin().disable()
.httpBasic().disable()
.authorizeRequests()
.antMatchers(
"/",
"/error",
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/actuator/**"
).permitAll()
.antMatchers(GET, "/apple").hasAuthority("getApples")
.antMatchers(GET, "/orange").hasAuthority("getOranges")
.anyRequest().authenticated()
.and()
.addFilterBefore(authFilter, AbstractPreAuthenticatedProcessingFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
Filter Implementation:
public class SessionAuthFilter extends AbstractAuthenticationProcessingFilter {
private final CustomUserDetails userDetails;
protected SessionAuthFilter(RequestMatcher requestMatcher, AuthenticationManager authenticationManager,
CustomUserDetails userDetails) {
super(requestMatcher, authenticationManager);
this.userDetails = userDetails;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
var sessionToken = request.getHeader("SessionToken") != null ? request.getHeader("SessionToken").trim() : null;
var user = userDetails.loadUserByUsername(sessionToken);
var authentication = new PreAuthenticatedAuthenticationToken(user.getUsername(), user.getPassword(),
user.getAuthorities());
authentication.setAuthenticated(user.isCredentialsNonExpired());
authentication.setDetails(userDetails);
SecurityContextHolder.getContext().setAuthentication(authentication);
return this.getAuthenticationManager().authenticate(authentication);
}
}
Authentication Provider:
@Component
@Slf4j
public class PreAuthProvider implements AuthenticationProvider {
private boolean throwExceptionWhenTokenRejected;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!this.supports(authentication.getClass())) {
return null;
} else {
log.debug(String.valueOf(LogMessage.format("PreAuthenticated authentication request: %s", authentication)));
if (authentication.getPrincipal() == null) {
log.debug("No pre-authenticated principal found in request.");
if (this.throwExceptionWhenTokenRejected) {
throw new BadCredentialsException("No pre-authenticated principal found in request.");
} else {
return null;
}
} else if (authentication.getCredentials() == null) {
log.debug("No pre-authenticated credentials found in request.");
if (this.throwExceptionWhenTokenRejected) {
throw new BadCredentialsException("No pre-authenticated credentials found in request.");
} else {
return null;
}
} else if (!authentication.isAuthenticated()) {
throw new InsufficientAuthenticationException("Session token likely no longer valid.");
}
return authentication;
}
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(PreAuthenticatedAuthenticationToken.class);
}
public void setThrowExceptionWhenTokenRejected(boolean throwExceptionWhenTokenRejected) {
this.throwExceptionWhenTokenRejected = throwExceptionWhenTokenRejected;
}
}
Solution
It looks like if you set continueChainBeforeSuccessfulAuthentication
in your AbstractAuthenticationProcessingFilter
implementation to true, you can stop the redirection. I only needed to modify the filter constructor which came out to be:
protected SessionAuthFilter(RequestMatcher requestMatcher, AuthenticationManager authenticationManager,
CustomUserDetails userDetails) {
super(requestMatcher, authenticationManager);
this.userDetails = userDetails;
this.setContinueChainBeforeSuccessfulAuthentication(true);
}
The other approach would be to implement a different Filter such as OncePerRequestFilter or a GenericFilterBean to handle the authentication yourself.
Answered By - superflux
Answer Checked By - Mary Flores (JavaFixing Volunteer)