[Spring Security] 로그인 성공 실패시 사용자 ip 로그 저장

 WebAuthenticationDetails webAuthenticationDetails = ((WebAuthenticationDetails) authentication.getDetails());
    String ipAddr = webAuthenticationDetails.getRemoteAddress();

Spring Security에서는 WebAuthenticationDetailsgetRemoteAddress()를 이용해  클라이언트의 ip를 알아낼 수 있다.
하지만 getRemoteAddress만으로는 실제 클라이언트의 헤더값 ip정보를 정확히 알아낼 수 없다.
 
그렇기에 클라이언트의 정확한 ip를 알아내기 위해서는 HttpServletRequest가 담긴 메소드 파라미터를 사용해야 한다.

 

1. Security Config 설정

    @Autowired
    private  UserAuthenticationSuccessHandler userAuthenticationSuccessHandler;
    
    @Autowired
    private  UserAuthenticationFailHandler userAuthenticationFailHandler;
    
    @Override
	public void configure(HttpSecurity http) throws Exception {
		http.formLogin().loginPage("/login")
        .failureHandler(userAuthenticationFailHandler)
        .successHandler(userAuthenticationSuccessHandler)
	}

Security configure 설정에 failureHandler()successHandler()를 추가한다.
로그인에 성공하거나 실패에 따라서 UserAuthenticationSuccessHandlerUserAuthenticationFailHandler를 이용해 로직을 구분해 사용을 한다.

 

2. 로그인 성공 시 onAuthenticationSuccess

@Component
public class UserAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

	final Logger logger = LoggerFactory.getLogger(getClass());
    private RequestCache requestCache = new HttpSessionRequestCache();
	private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
	
	@Autowired
	private SecurityService securityService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        logger.info("getSuccessHandler start");
        Object principal = authentication.getPrincipal();
        UserDetails userDetails = (UserDetails) principal;

        String memId = userDetails.getUsername();
        String ipAddr = FnUtil.getClientIP(request);

        LoginLog loginLog = new LoginLog();
        loginLog.setLoginId(memId);
        loginLog.setLoginIp(ipAddr);
        loginLog.setLoginYn("T");
        loginLog.setRegDate(Calendar.getInstance().getTime());
        securityService.save(loginLog);  
        setDefaultTargetUrl("/main");
      
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        if(savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();        
            redirectStrategy.sendRedirect(request, response, targetUrl);
        }else{
        	redirectStrategy.sendRedirect(request, response, getDefaultTargetUrl());
        }

        super.onAuthenticationSuccess(request, response, authentication);
    }
}

로그인에 성공하면 SimpleUrlAuthenticationSuccessHandler에 있는 onAuthenticationSuccess 메소드를 오버라이드해 사용한다.
사용자 정보는 authentication.getPrincipal()을 이용하고 ip정보는 HttpServletRequest를 이용해 구한다. 

사용자가 로그인이 되지 않은 채로 로그인이 필요한 페이지에 접속을 시도하면 스프링 시큐리티에서는 로그인페이지로 강제 이동되게 된다.

로그인 후 본래 로그인을 하려고 했던 페이지로 이동을 시키려면 requestCache.getRequest(request, response)을 이용해 리다이렉트 시켜주면 된다.

public static String getClientIP(HttpServletRequest request) {
	    String ip = request.getHeader("X-Forwarded-For");

	    if (ip == null) {
	        ip = request.getHeader("Proxy-Client-IP");
	    }
	    if (ip == null) {
	        ip = request.getHeader("WL-Proxy-Client-IP");
	    }
	    if (ip == null) {
	        ip = request.getHeader("HTTP_CLIENT_IP");
	    }
	    if (ip == null) {
	        ip = request.getHeader("HTTP_X_FORWARDED_FOR");
	    }
	    if (ip == null) {
	        ip = request.getRemoteAddr();
	    }

	    return ip;
	}

ip는 프록시나 로드 밸런스 서버의 ip주소만을 담고 있어 실제  ip주소를 정확히 가지고 오지 못할 때가 있다.
그렇기에 클라이언트의 실제 ip를 구하기 위해 유틸을 만들어서 구한다.
request.getHeader("X-Forwarded-For")는 HTTP 요청 헤더 중의 하나로 일반적으로 클라이언트의 ip 주소를 식별하는 데 사용 된다. 
 

3. 로그인 실패시 onAuthenticationFailure

@Component
public class UserAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {

	final Logger logger = LoggerFactory.getLogger(getClass());
	
	@Autowired
	private SecurityService securityService;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException ,ServletException {

    	logger.info("getFailHandler start");
    	String ipAddr = FnUtil.getClientIP(request);
        String memId = request.getParameter("username");

  		LoginLog loginLog = new LoginLog();
  		loginLog.setLoginId(memId);
  		loginLog.setLoginIp(ipAddr);
  		loginLog.setLoginYn("F");
  		loginLog.setIntegerIp(ip);
  		loginLog.setRegDate(Calendar.getInstance().getTime());
 		securityService.save(loginLog);
        
        String errorMessage;
        if (exception instanceof BadCredentialsException || exception instanceof InternalAuthenticationServiceException){
            errorMessage="아이디 또는 비밀번호가 틀립니다.";
        }else if (exception instanceof UsernameNotFoundException){
            errorMessage="존재하지 않는 사용자 아이디 입니다.";
        }
        else{
            errorMessage="알 수 없는 이유로 로그인이 안되고 있습니다.";
        }
        errorMessage= URLEncoder.encode(errorMessage,"UTF-8");
        setDefaultFailureUrl("/login?error=true&exception=" + errorMessage);
        
        super.onAuthenticationFailure(request, response, exception);

    }
}

로그인에 실패하면 SimpleUrlAuthenticationFailureHandler에 있는onAuthenticationFailure 메소드를 오버라이드해 사용한다.
로그인에 실패할 시 메시지를 표현하고 싶다면 AuthenticationException을 이용해 로그인페이지로 에러메시지를 던져줄 수 있다.
 
 

로그인 내역 입력 결과

테이블을 확인해 보면 사용자의 로그인 시간, 아이디, ip, 로그인 성공여부 등이 정확하게 입력된 것을 확인해 볼 수 있다.