-1

I am currently trying to create a fullstack app, with Angular 14 and spring boot, i am stack with authentication. my problem is that i use my own form to get the password and the username from the user, then trying to authenticate in the backend, i created an Authentication Filter, in which i override the attemptAuthentication() method, which recives a JSON object containing the username and password, Then i test if the username exists if not i throw UserNotFoundException , if the password is wrong i throw BadCredentialsException then if everything went well i return an authentication object, here is the method:

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//        JSON body authentication
        try {
            System.err.println("attempting authentication");
            LoginBody loginBody = new ObjectMapper().readValue(request.getInputStream(), LoginBody.class);
            AppUser user = this.userService.loadUserByUsername(loginBody.getUsername());
            if (user == null) {
                throw new UserNotFoundException("No user with this username") {
                };
            }
            if ( user.getPassword().equals(passwordEncoder.encode(loginBody.getPassword()))) {
                throw new BadCredentialsException("Bad credentials") {
                };
            }
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginBody.getUsername(),loginBody.getPassword()));
        } catch (Exception e) {
            System.err.println(e.getMessage());
            throw new AuthenticationException(e.getMessage()) {
            } ;
        }

i have created an exeption handler which works fine for my controller methods whith have the endpoint /api/... , but not for the authentication with the endpoint /auth/login, all it returns is the HTTP status 403 (forbidden) like in this image

enter image description here

here is my exception handler class

package com.webapps.Focus.exceptions;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class UserExceptionController {

    @ExceptionHandler(value = UserNotFoundException.class)
    public ResponseEntity<Object> exception(UserNotFoundException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(value = BadCredentialsException.class)
    public ResponseEntity<Object> exception(BadCredentialsException exception) {
        return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
    }


}

I appreciate your help.

mouse
  • 15
  • 2

2 Answers2

1

I had the same problem. It happened because of anyRequest().authenticated() in Security Configuration: "/error" page is blocked too. So u should write something like this: authorizeHttpRequests(auth -> auth.requestMatchers("/error").permitAll() or authorizeHttpRequests().requestMatchers("/error").permitAll() as you wish.

Toni
  • 3,296
  • 2
  • 13
  • 34
Saubul
  • 11
  • 2
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 10 '22 at 22:21
  • This is actually a very helpful answer. That's what I figured out too, but i don't understand why is this happening. Or why this solution cannot be found in tutorials. – C-Shark Jun 27 '23 at 20:08
0

According to this article, Exceptionhandler doesn't handle spring security exceptions, like AuthenticationException, hence nothing except UNAUTHORIZED status is shown as an answer, one solution is to create a customized implementation for AuthenticationFailureHandler interface, then override onAuthenticationFailureonAuthenticationFailure() method, in which you use your own exception handling like in this example:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component("userAuthFailureHandler")
public class UserAuthenticationFailureHandler implements AuthenticationFailureHandler {


    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception)
                                        throws IOException, ServletException {
        try {
            Map<String, String> status = new HashMap<>();
            status.put("status", HttpStatus.UNAUTHORIZED.toString());
            status.put("value", HttpStatus.UNAUTHORIZED.value() + "");
            status.put("reason", HttpStatus.UNAUTHORIZED.getReasonPhrase());
            status.put("error", exception.getMessage());
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            new ObjectMapper().writeValue(response.getOutputStream(), status);
        }catch (Exception e) {
            throw e;
        }
    }

}

Then in SecurityConfig class, consider injecting a bean with Qualifier("userAuthFailureHandler") , then set the attribute AuthenticationFailureHandler of your AuthenticationFilter to that bean:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {


   ...
   
    private AuthenticationFailureHandler failureHandler;

    private AuthenticationEntryPoint authEntryPoint;
    public SecurityConfig(...
                          @Qualifier("delegatedAuthenticationEntryPoint") AuthenticationEntryPoint authEntryPoint,
                          @Qualifier("userAuthFailureHandler")AuthenticationFailureHandler failureHandler) {
                          
       ...
       
        this.authEntryPoint = authEntryPoint;
        this.failureHandler = failureHandler;
    }



    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//      configure the stateless authentication
        http.csrf().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);


...


        JWTAuthenticationFilter authenticationFilter = new JWTAuthenticationFilter(authenticationManagerBean(), userService, passwordEncoder);
        authenticationFilter.setFilterProcessesUrl("/auth/login");
        authenticationFilter.setAuthenticationFailureHandler(this.failureHandler);
        http.addFilter(authenticationFilter);
        http.addFilterBefore(new JWTAuthorisationFilter(), UsernamePasswordAuthenticationFilter.class);

       
//        allow security exceptions handling to component with qualifier delegatedAuthenticationEntryPoint
        http.exceptionHandling().authenticationEntryPoint(authEntryPoint);

    }


    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

Then delegate security exception handling to your ow implementation of AuthenticationEntryPoint like below

//This class will help handle security exceptions that couldn't be handled by ControllerAdvice
@Component("delegatedAuthenticationEntryPoint")
public class DelegatedAuthenticationEntryPoint implements AuthenticationEntryPoint {



    private HandlerExceptionResolver resolver;

    public DelegatedAuthenticationEntryPoint(  @Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
        this.resolver = resolver;
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        resolver.resolveException(request, response, null, authException);
    }

}
mouse
  • 15
  • 2