로그인에 실패 했을 경우, 어떤 이유로 실패했는지 사용자가 정확하게 이해할 수 있게 직접 오류 메세지를 커스터마이징할 수 있다. Spring Security 에서 관련 인터페이스를 제공한다.
로그인 실패와 관련된 예외들은 WebSecurityConfigurerAdapter
상속 클래스(SecurityConfig
)의 configure(HttpSecurity http)
메서드에 failureHandler()
를 추가해야 예외를 캐치할 수 있다.
AuthenticationFailureHandler
인터페이스 구현체를 받는다.@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
/* 로그인 실패 핸들러 의존성 주입 */
private final AuthenticationFailureHandler customFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.failureHandler(customFailureHandler) /* 로그인 실패 핸들러 */
...
}
}
로그인 실패를 핸들링하는 커스텀 핸들러,
AuthenticationFailureHandler
인터페이스를 구현함.
AuthenticationFailureHandler
인터페이스를 implements해야 한다.
이 때 AuthenticationFailureHandler
인터페이스를 구현한 구현체 SimpleUrlAuthenticationFailureHandler
를 extends 한다.
굳이 SimpleUrlAuthenticationFailureHandler 를 사용한 이유
해당 클래스가 제공해주는 setDefaultFailureUrl()
메서드를 활용하기 위함. 해당 메서드를 활용하면 로그인 실패 시 url을 지정해준다.
해당 url은 컨트롤러에 매핑되어 있어야 한다.
onAuthenticationFailure
메소드를 오버라이딩하여 예외처리, 에러 메시지 띄워준다.
이 때 에러 메시지의 한글 인코딩이 깨지는 것을 방지하기 위해
errorMessage = URLEncoder.encode(errorMessage, "UTF-8"); 처리
package com.jy.config.auth;
import java.io.IOException;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
public class CustomAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler{
/*
* HttpServletRequest : request 정보
* HttpServletResponse : Response에 대해 설정할 수 있는 변수
* AuthenticationException : 로그인 실패 시 예외에 대한 정보
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String errorMessage;
if(exception instanceof BadCredentialsException) {
errorMessage = "아이디 또는 비밀번호가 맞지 않습니다. 다시 확인해주세요.";
} else if (exception instanceof InternalAuthenticationServiceException) {
errorMessage = "내부 시스템 문제로 로그인 요청을 처리할 수 없습니다. 관리자에게 문의하세요. ";
} else if (exception instanceof UsernameNotFoundException) {
errorMessage = "존재하지 않는 계정입니다. 회원가입 후 로그인해주세요.";
} else if (exception instanceof AuthenticationCredentialsNotFoundException) {
errorMessage = "인증 요청이 거부되었습니다. 관리자에게 문의하세요.";
} else {
errorMessage = "알 수 없는 오류로 로그인 요청을 처리할 수 없습니다. 관리자에게 문의하세요.";
}
errorMessage = URLEncoder.encode(errorMessage, "UTF-8"); /* 한글 인코딩 깨진 문제 방지 */
setDefaultFailureUrl("/auth/login?error=true&exception="+errorMessage);
super.onAuthenticationFailure(request, response, exception);
}
}
https://www.inflearn.com/questions/289866 참고..
CustomAuthenticationFailureHandler는 super 클래스를 호출하지 않아도 상관없다.
다만 실패 이후 이동할 페이지를 지정할 경우 상위클래스에게 위임하는 것이 더 좋기 때문에 super.onAuthenticationFailure 처리
실패와 관련된 여러가지 후속 처리를 해주기 때문.... 이라고 하는데 완벽히 이해가 가진 않는다
CustomAuthFailureHandler
에서 setDefaultFailureUrl()
이 호출되었을 때 지정되있는 url을 매핑해줄 컨트롤러 ...
/* 로그인 */
@GetMapping("/auth/login")
public String login(@RequestParam(value = "error", required = false) String error,
@RequestParam(value = "exception", required = false) String exception,
Model model) {
/* 에러와 예외를 모델에 담아 view resolve */
model.addAttribute("error", error);
model.addAttribute("exception", exception);
return "/user/user-login";
}
<div th:if="error">
<p id="valid" style="color: red; font-size:12px;"><a th:text="${exception}"></a></p>
</div>
error가 true라면 파라미터 exception의 값을 사용자에게 보여준다.
즉, CustomAuthFailureHandler
에서 커스텀한 errormessage
를 사용자에게 보여주어 오류 메시지를 정확히 전달한다.