superflux
superflux

Reputation: 137

Spring Security Redirecting After Successful Authentication

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;
  }
}

Upvotes: 2

Views: 2826

Answers (1)

superflux
superflux

Reputation: 137

It looks like if you set continueChainBeforeSuccessfulAuthentication in your AbstractAuthenticationProcessingFilter implementation to true, you can delay the redirection. Using your own success handler implementation will completely stop the redirect behavior. 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);
    this.setAuthenticationSuccessHandler((request, response, authentication) -> {});
}

The other approach would be to implement a different Filter such as OncePerRequestFilter or a GenericFilterBean to handle the authentication yourself.

Upvotes: 1

Related Questions