2.스프링 시큐리티 [로그인 실패 핸들링하기]

dasd412·2022년 1월 31일
3

목적

로그인 실패를 하게 되면 “loginURL?error”라는 url로 바뀔 뿐, 사용자 입장에서는 무엇이 일어났는지 아무것도 알 수 없다.

따라서 로그인을 실패할 경우, 아래 html(mustache)처럼 실패 이유를 출력해주고 싶다.

...
<div class="loginFail">
      {{#error}}
        {{exception}}
      {{/error}}
 </div>
...

시큐리티 설정에 예외 핸들러 추가하기

로그인 실패와 관련된 예외들(비밀번호가 맞지 않는다던지...)들은 WebSecurityConfigurerAdapter 상속 클래스의 configure(HttpSecurity http) 메서드에 failureHandler()를 추가해야 예외를 캐치해낼 수 있다.

이 failureHandler() 메서드는 파라미터로 AuthenticationFailureHandler 인터페이스 구현체를 받는다. LoginFailHandler 를 빈으로 주입하고 있는데, 이는 AuthenticationFailureHandler 를 구현한 커스텀 클래스다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

		@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();

        http.authorizeRequests()
....
                .and()
			           ....
                .failureHandler(loginFailHandler())//로그인 실패 시 처리하는 핸들러 등록.
                .and()
...

    }

		@Bean
    public LoginFailHandler loginFailHandler(){
        return new LoginFailHandler();
    }
}

예외 핸들러 만들기

LoginFailHandler 는 로그인 실패를 핸들링하는 커스텀 핸들러다. 위에서 언급한대로 AuthenticationFailureHandler 를 implements해야 한다.

그런데 SimpleUrlAuthenticationFailureHandler 를 extends하면 AuthenticationFailureHandler 를 implements 한 것과 같게 된다.
그 이유는 SimpleUrlAuthenticationFailureHandler 가 해당 인터페이스를 구현한 것이기 때문에, 이를 상속하면 자동으로 해당 인터페이스를 구현한 것이 된다.

그렇다면 굳이 SimpleUrlAuthenticationFailureHandler 를 사용한 이유는 무엇일까?
바로 해당 클래스가 제공해주는 setDefaultFailureUrl() 메서드를 활용하기 위해서이다. 해당 메서드를 활용하면 로그인 실패 시 url을 지정해준다.

단, 해당 url은 컨트롤러에 매핑되어 있어야 된다.

public class LoginFailHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    //httpServletRequest -> request 에 대한 정보 , httpServletResponse -> response 에 대해 설정할 수 있는 변수
    //AuthenticationException e -> 로그인 실패 시 예외에 대한 정보를 담고 있음.
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        logger.info("login fail handler");

        String errorMessage;
        if (e instanceof BadCredentialsException || e instanceof InternalAuthenticationServiceException){
            errorMessage="아이디 또는 비밀번호가 맞지 않습니다.";
        }else if (e instanceof UsernameNotFoundException){
            errorMessage="존재하지 않는 아이디 입니다.";
        }
        else{
            errorMessage="알 수 없는 이유로 로그인이 안되고 있습니다.";
        }

        errorMessage= URLEncoder.encode(errorMessage,"UTF-8");//한글 인코딩 깨지는 문제 방지
        setDefaultFailureUrl("/loginForm?error=true&exception=" + errorMessage);
        super.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
    }


}

컨트롤러

setDefaultFailureUrl()이 호출되었을 때 지정되있는 url을 매핑해줄 컨트롤러 코드가 필요하다.

해당 코드에는 RequestParam 으로 넘겨진 error와 exeption을 모델에 담은 후, 뷰 리졸브해주고 있다.

@Controller
public class LoginController {

    @GetMapping("/loginForm")
    public String loginForm(@RequestParam(value = "error", required = false) String error,
                            @RequestParam(value = "exception", required = false) String exception, Model model) {
        model.addAttribute("error", error);
        model.addAttribute("exception", exception);
        logger.info("loginForm view resolve");
        return "/login/loginForm";
    }
}

구조

SimpleUrlAuthenticationFailureHandler 참고
https://docs.spring.io/spring-security/site/docs/4.2.6.RELEASE/apidocs/org/springframework/security/web/authentication/SimpleUrlAuthenticationFailureHandler.html

profile
아키텍쳐 설계와 테스트 코드에 관심이 많음.

0개의 댓글