스프링 시큐리티를 공부하는 과정에서 공부하고 이해한 내용을 바탕으로 풀어보려고 한다.
현재 개인적으로 프로젝트를 진행 도중 회원가입을 하는 과정에서 가입하려는 이메일로 인증 과정을 거치게 하였다.
여기서 문제는 가입하려는 이메일로 인증을 다 받기도 전에 디비에 먼저 데이터를 넣어주다보니 로그인이 되버리는 현상이 생겼다.
시큐리티에서 로그인할 때 이메일 인증이 안되면 인증이 거부되어 튕기게 할 수 없을까?? 라는 의문점이 생겼다.
구글을 뒤적뒤적.. 닥치는데로 찾고 정리하고 공부하기 시작!
방법은 있었다! 지난번에 스프링 시큐리티에 관해서 정리를 한 적이 있는데 거기에 이어서 설명하고자 한다.
지난글을 보려면 여기를 확인하면 된다.
2020/11/19 - [Dev/Spring Boot] - 스프링 시큐리티 적용 (CSRF)
스프링 시큐리티는 기본적으로 SecurityFilterChain 들을 통해 여러 필터들을 거쳐간다.
로그인시 거쳐가는 필터는 UsernamePasswordAuthenticationFilter 이녀석을 통해 인증을 진행한다.
인증을 진행할 때 사용자 편의에 맞춰서 두개의 Handler를 제공하는데 AuthenticationFailureHandler와 AuthenticationSuccessHandler가 그것들이다.
AuthenticationFailureHandler : 이름에서 유추할 수 있듯이 로그인 인증하는 과정에서 실패할 경우의 동작
ex) 로그인 5회 실패시 사용자 계정 잠금, 예외를 통해 메시지를 뿌리기 등의 부가적인 작업
AuthenticationSuccessHandler : 이름에서 유추할 수 있듯이 로그인 인증하는 과정에서 성공한 이후의 동작
ex) 로그인 성공시 수행할 부가적인 작업
아래 직접 만들어보고 있는 프로젝트의 소스를 보면서 자세히 살펴보자!
시큐리티 설정파일(SecurityConfig.java) 파일 일부분
@Override
protected void configure(HttpSecurity http) throws Exception{
http
.csrf().disable()
.authorizeRequests()
// 페이지 권한 설정
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/","/user/signup", "/user/login", "/user/sendSignUpEmail", "/user/signUpConfirm","/study/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/user/login")
.defaultSuccessUrl("/")
.failureHandler(failureHandler()) // 이부분이 실패했을때 이 핸들러를 타겠다 라는 뜻
// .successHandler(successHandler()) // 이부분이 성공했을때 이 핸들러를 타겠다 라는 뜻
.permitAll()
.and() // 로그아웃 설정
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll();
http.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(false);
}
// 빈주입을 통해 AuthenticationFailureHandler를 탈경우에 내가 생성한 LoginFailHandler에서
// 추가적인 행동을 하겠다 라는 뜻의 선언이다.
@Bean
public AuthenticationFailureHandler failureHandler() {
return new LoginFailHandler();
}
필자는 별도의 예외처리를 통해 예외메시지를 지정하고 그 메시지를 화면을 loginFail이라는 뷰 화면에서 알림메시지로 던져준다.
- LoginFailHandler는 AuthenticationFailureHandler를 상속받게 되는데 이렇게되면 onAuthenticationFailure를 오버라이딩 해줘야 한다!
- 오버라이딩한 메서드는 AuthenticationException으로 하여 인증 실패시 제공되는 예외들이 있고, 각 예외마다 메시지를 던질수 있고 아래와같이 별도로 메시지를 변경해서 던질 수 있다.
- 대표적으로는 아래에서 선언한 것처럼 BadCredentialsException, DisabledException, LockedException 등이 있다.
- 아래의 소스에서 캡쳐한 추가 이미지를 참고하자.
LoginFailHandler.java 파일
package com.bootproj.pmcweb.Network.Request;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.extern.slf4j.XSlf4j;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.AccountExpiredException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Getter
@Setter
@Slf4j
public class LoginFailHandler implements AuthenticationFailureHandler {
private final String DEFAULT_FAILURE_URL = "/user/loginFail";
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
String errorMsg = null;
if(exception instanceof BadCredentialsException){
errorMsg = "아이디나 비밀번호가 맞지 않습니다. 다시 확인해 주세요.";
}else if(exception instanceof DisabledException) {
errorMsg = "계정이 비활성화 되었습니다. 관리자에게 문의하세요.";
}else if(exception instanceof LockedException){
log.info("이메일이 인증되지 않았습니다. 이메일을 확인해 주세요.");
errorMsg = "이메일이 인증되지 않았습니다. 이메일을 확인해 주세요.";
}else{
errorMsg = "알수없는 이유로 로그인에 실패하였습니다.";
}
request.setAttribute("errorMsg", errorMsg);
request.getRequestDispatcher(DEFAULT_FAILURE_URL).forward(request, response);
}
}
로그인을 하게될때의 과정을 이해한데로 다시한번 종합적으로 정리해보자면!
- 로그인을 하게되면 스프링 시큐리티에서 인증절차를 받기 위해 UserDetailsService를 상속받고 있는 서비스의 loadUserByUsername를 찾아간다.
- 인증하는 과정에서 예외가 발생하게 되면, 기본적으로 시큐리티에서 Fail시 처리하는 로직이 수행되지만, 필자는 위에서 별도로 만든 Handler를 통해 예외메시지를 던져주게 구현한 것이다.
- 이상 끝!
'Dev > Spring Boot' 카테고리의 다른 글
스프링부트가 제공하는 라이브러리 살펴보기! (0) | 2021.04.25 |
---|---|
스프링부트 공통 설정 그놈 Logback (0) | 2021.04.07 |
스프링 시큐리티 적용 (CSRF) (0) | 2020.11.19 |
@Valid로 유효성 체크하기 (0) | 2020.11.15 |
스프링 빈 주입방법과 템플릿 우선순위 (3) | 2020.10.15 |