Jak radzić sobie z UsernameNotFoundException?

Wiosną zabezpieczenia, gdy nazwa użytkownika nie została znaleziona, implementacja UserDetailsService generuje UsernameNotFoundException. Na przykład w ten sposób:

   @Override
   @Transactional
   public UserDetails loadUserByUsername(java.lang.String username) throws UsernameNotFoundException {
       logger.info("Load user by username: {}", username);
       User user = userRepository.findUserByUsername(username).orElseThrow(
                   () -> new UsernameNotFoundException("User Not Found with -> username or email: " + username));

       return UserPrinciple.build(user);
   }

Chciałbym utworzyć niestandardową odpowiedź REST „Nie znaleziono użytkownika”. Jak powinienem złapać / obsłużyć ten wyjątek? Zaimplementowałem metodę handlera w implementacji WebSecurityConfigurerAdapter handler:

  private static void handleException(HttpServletRequest req, HttpServletResponse rsp, AuthenticationException e)
           throws IOException {
       PrintWriter writer = rsp.getWriter();
       writer.println(new ObjectMapper().writeValueAsString(new AuthResponse("", null, null, null, null,
               "Authentication failed.", false)));
       rsp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
   }

Ale ta metoda powinna czekać na wyjątek AuthenticationException, który podczas działania ma typ wyjątku java.lang.NullPointerException, więc nie jestem w stanie rzutować ani pobrać początkowego UsernameNotFoundException.

Każda rada będzie mile widziana. Pozdrawiam serdecznie :).

1
user5562650 20 grudzień 2019, 00:32
Zajrzyj tutaj
 – 
Dirk Deyne
20 grudzień 2019, 00:39

3 odpowiedzi

Najlepsza odpowiedź

Warstwa bezpieczeństwa ma pierwszeństwo przed wszystkim w kontrolerach i @ControllerAdvice. Stąd @ControllerAdvice nie jest opcją, ponieważ UsernameNotFoundException, która jest podklasą AuthenticationException jest rzucana podczas uwierzytelniania, przez co obsługa wyjątków w @ControllerAdvice jest nieosiągalna.

Możesz użyć @ControllerAdvice i ResponseEntityExceptionHandler tylko wtedy, gdy rzucasz UsernameNotFoundException do kontrolera lub innych ziaren, do których odwołują się kontrolery.

Oto moja sugestia - zaimplementuj AuthenticationFailureHandler i użyj go z AuthenticationFilter, którego używasz do konfiguracji zabezpieczeń. Spring Boot Security zawiera około 4 interfejsy obsługi dla problemów związanych z bezpieczeństwem

  1. AccessDeniedHandler – rozwiązuje problemy, takie jak sytuacja, w której użytkownik nie ma wymaganych ról.
  2. AuthenticationEntryPoint - rozwiązuje problemy, takie jak gdy użytkownik próbuje uzyskać dostęp do zasobu bez odpowiednich elementów uwierzytelniających.

  3. AuthenticationFailureHandler - rozwiązuje problemy, takie jak brak odnalezienia użytkownika (np. UsernameNotFoundException) lub inne wyjątki zgłoszone wewnątrz dostawcy uwierzytelniania. W rzeczywistości obsługuje to inne wyjątki uwierzytelniania, które nie są obsługiwane przez AccessDeniedException i AuthenticationEntryPoint.

  4. AuthenticationSuccessHandler — to pomaga robić takie rzeczy, jak przekierowanie po pomyślnym uwierzytelnieniu użytkownika.

Zobacz poniższe przykładowe fragmenty kodu dotyczące implementacji wszystkich 4 interfejsów. Dostosuj je do swojego gustu.

  1. AccessDeniedHandler wdrożenie
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
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.io.OutputStream;

@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {

        Map<String,Object> response = new HashMap<>();
        response.put("status","34");
        response.put("message","unauthorized api access");

        //httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        OutputStream out = httpServletResponse.getOutputStream();
        ObjectMapper mapper = new ObjectMapper();
        mapper.writerWithDefaultPrettyPrinter().writeValue(out,response);
        //mapper.writeValue(out, response);

        out.flush();
    }
}
  1. AuthenticationEntryPoint Implementacja
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
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.io.OutputStream;

@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

        Map<String,Object> response = new HashMap<>();
        response.put("status","34");
        response.put("message","unauthorized access");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        OutputStream out = httpServletResponse.getOutputStream();
        ObjectMapper mapper = new ObjectMapper();
        mapper.writerWithDefaultPrettyPrinter().writeValue(out, response);
        out.flush();
    }
}
  1. Implementacja AuthenticationFailureHandler
package com.ibiller.webservices.security;

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.io.OutputStream;


@Component
public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler
{
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse httpServletResponse,
                                        AuthenticationException ex) throws IOException, ServletException
    {

        Map<String,Object> response = new HashMap<>();
        response.put("status","34");
        response.put("message","unauthorized access");

        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        OutputStream out = httpServletResponse.getOutputStream();
        ObjectMapper mapper = new ObjectMapper();
        mapper.writerWithDefaultPrettyPrinter().writeValue(out, response);
        out.flush();
    }
}
  1. Implementacja AuthenticationSuccessHandler
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class RestSuccessHandler implements AuthenticationSuccessHandler {

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        Set<String> roles = 
          AuthorityUtils.authorityListToSet(authentication.getAuthorities());
        if (roles.contains("ROLE_ADMIN")) {
            //do something
        }

    }
}

To jest konfiguracja bezpieczeństwa, która rozszerza WebSecurityConfigurerAdapter, która łączy wszystko razem.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        prePostEnabled = true,
        securedEnabled = true,
        jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


    private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
            new AntPathRequestMatcher("/v1/**"),new AntPathRequestMatcher("/admin/**")
    );

    AuthenticationProvider provider;

    public SecurityConfiguration(final AuthenticationProvider authenticationProvider) {
        super();
        this.provider=authenticationProvider;
    }

    @Override
    protected void configure(final AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(provider);
    }


    @Override
    public void configure(final WebSecurity webSecurity) {
        webSecurity.ignoring().antMatchers("/info/**");//url that will be ignored
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler())
               .authenticationEntryPoint(authenticationEntryPoint())
                .and()
                .authenticationProvider(provider)
                .addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers("/v1/**").hasRole("API")
                .antMatchers("/admin/**").hasAnyRole("SUPER_ADMIN","ADMIN")
                .and()
                .csrf().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .logout().disable();
    }

    @Bean
      AuthenticationFilter authenticationFilter() throws Exception {
        final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationSuccessHandler(successHandler());
        filter.setAuthenticationFailureHandler(authenticationFailureHandler());
        return filter;
    }

    @Bean
    RestAccessDeniedHandler accessDeniedHandler() {
        return new RestAccessDeniedHandler();
    }

    @Bean
    RestAuthenticationEntryPoint authenticationEntryPoint() {
        return new RestAuthenticationEntryPoint();
    }

    @Bean
    RestAuthenticationFailureHandler authenticationFailureHandler(){
        return new RestAuthenticationFailureHandler();
    }

    @Bean
    RestSuccessHandler successHandler(){
        return new RestSuccessHandler();
    }
}
7
Wilson 10 maj 2020, 10:25
Czy można to zaktualizować? Obecnie konstruktor, którego używasz do konfiguracji obiektu AuthenticationFilter, jest przestarzały w obecnej wersji Spring Security. Obecnie nowym konstruktorem jest konstruktor AuthenticationFilter(AuthenticationManager, AuthenticationConverter). dokumenty. spring.io/spring-security/site/docs/current/api/org/…
 – 
tom_mai78101
2 grudzień 2020, 22:36

Nie znam struktury Twojego projektu, ale typowym rozwiązaniem w tym przypadku jest użycie mechanizmu @ControllerAdvice (oddzielna klasa lub w kontrolerze):

@ControllerAdvice
public class CustomExceptionHandler {

    @ExceptionHandler(value = UsernameNotFoundException.class)
    public ResponseEntity handle(final UsernameNotFoundException exception) {
        ...//set headers, response attributes and response body
    }
}
1
Dmitry Ionash 20 grudzień 2019, 00:41
Dziękuję, postaram się to zrealizować. :)
 – 
user5562650
20 grudzień 2019, 01:43

W klasie dziedziczącej po UsernamePasswordAuthenticationFilter musisz nadpisać metodę unsuccessfulAuthentication Wywołuje metodę superklasy, ale przekierowuje do innego kontekstu błędu, co powoduje aktywację filtra autoryzacji. Zamiast tego po prostu wypełnij informacje o żądaniu zgodnie z oczekiwaniami klienta (w moim przypadku Json)

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse res, AuthenticationException failed) throws IOException, ServletException {
    res.addHeader("Access-Control-Allow-Origin", "*");
    res.setStatus(HttpServletResponse.SC_OK);
    ObjectMapper mapper = new ObjectMapper();
    ObjectNode message = mapper.createObjectNode();
    message.put("success", false);
    message.put("message", "Invalid credentials");
    String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(message);

    PrintWriter out = res.getWriter();
    res.setContentType("application/json");
    res.setCharacterEncoding("UTF-8");
    out.print(json);
    out.flush();
}
0
JuanCamilo Achury Bueno 5 styczeń 2021, 13:18