본문으로 바로가기

로그인에 성공했을 때의 부가작업도 해보려고 한다. 마찬가지로 Spring Security에서 제공하는 AuthenticationSuccessHandler 인터페이스를 구현할 것이다. 따라서 실패 핸들러를 구현할 때와 비슷하다고 생각하며 된다. 그리고 해당 인터페이스에서 오버라이드하는 메서드에서도 3가지의 객체가 넘어오는데, 실패 핸들러와는 조금의 차이가 있을 뿐이다.


HttpServletRequest 객체: 웹에서 넘어온 Request 값을 가지고 있는 객체

HttpServletResponse 객체: 출력을 정의할 수 있는 객체

Authentication 객체: 인증에 성공한 사용자의 정보를 가지고 있는 객체


AuthenticationFailureHandler 인터페이스에는 로그인 실패 정보를 가지고 있었다면, AuthenticationSuccessHandler 인터페이스에는 로그인 성공 정보를 가지고 있다. 차이가 많이 나는 것이 아니기 때문에 큰 어려움은 없을 것이다. 다만, 성공 핸들러를 구현할때에는 기본적으로 해야하는 것이 있다. 로그인에 성공 후 이동할 URL을 지정하여 지정한 URL로 이동해야 한다. 로그인에 성공했을 때 이동할 곳은 다 다르다. 로그인 버튼을 눌러 직접 로그인화면으로 갔다면 성공 후엔 메인 화면으로 가야 할 것이고, 권한이 필요한 곳에 접근하여 강제로 로그인 화면으로 이동했다면 성공 후엔 그 권한이 필요했던 곳으로 가야한다. 그리고 혹시나 있을 에러 세션을 지우는 작업도 해줘야 한다. 기본적으로 2가지를 작업하고 다른 추가적인 부가 작업을 해주려고 한다.


1. 로그인 성공시, 어떤 URL로 Redirect 할 지 결정

2. 로그인 실패 에러 세션 지우기

3. 로그인 성공시, 실패 카운터 초기화


그리고 나는 여기서 추가적으로 마지막 접속 날짜를 갱신하는 로직을 추가했다. 생략한 이유는 실패 카운터 초기화 작업과 다를 게 없다. 그렇기 때문에 방문 카운터를 증가시키거나 방문자 수를 업데이트 하는 등 실패 카운터 초기화 작업의 과정을 보면서 자신이 원하는 부가 작업을 추가하면 된다.


파라미터 설정

LoginSuccessHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    
    private String loginidname;
    private String defaultUrl;
 
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
    }
 
    public String getLoginidname() {
        return loginidname;
    }
 
    public void setLoginidname(String loginidname) {
        this.loginidname = loginidname;
    }
 
    public String getDefaultUrl() {
        return defaultUrl;
    }
 
    public void setDefaultUrl(String defaultUrl) {
        this.defaultUrl = defaultUrl;
    }
 
}
cs


Security 설정

context-security.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<security:form-login
    username-parameter="loginId"
    password-parameter="loginPwd"
    login-processing-url="/login"
    login-page="/secu/loginPage"
    authentication-failure-handler-ref="loginFailureHandler"
    authentication-success-handler-ref="loginSuccessHandler"
/>
 
<bean id="loginSuccessHandler" class="tody.common.handler.LoginSuccessHandler">
    <property name="loginidname" value="loginId"/>
    <property name="defaultUrl" value="/"/>
</bean>
cs

- 07행: authentication-success-handler-ref 속성을 추가해준다. 해당 핸들러를 통해 default url이 설정되기 때문에 default-target-url 속성은 필요가 없어진다.

- 10행: AuthenticationSuccessHandler 인터페이스를 구현할 클래스를 <bean> 등록해준다.

- 11, 12행: 사용할 파라미터 값들을 설정해준다.



1. 로그인 성공시, 어떤 URL로 Redirect 할 지 결정

로그인에 성공하고 난 후, 가야 할 이동 경로는 어떻게 로그인 화면에 접근했느냐에 따라 다르다. 또한 어떻게 설정해주냐에 따라 달라질 수도 있다. 로그인에 성공하면 무조건 메인 화면으로 간다던지, 로그인 성공 화면을 띄워준다 던지 등등 어떤 경우에 어떤 url로 보낼 지 결정하고 만드는 게 좋다. 나는 2가지의 경우로 나누어 redirect 시킬 것이다.


- 인증 권한이 필요한 페이지에 접근했을 때

- 직접 로그인 페이지로 접근했을 때


먼저 인증 권한이 필요한 페이지에 접근하게 되면, 로그인 화면을 띄우기 전에 필요한 정보들을 세션에 저장하게 된다. spring security에서 제공하는 사용자의 요청을 저장하고 꺼낼 수 있는 RequestCache 인터페이스를 이용해, 사용자 요청 정보들이 들어 있는 SavedRequest 클래스 객체를 세션에 저장하게 된다. 그럼 우리는 RequestCache 객체를 생성해 SavedRequest 객체를 가져와서 로그인 화면을 보기 전에 방문했던 URL 정보를 가져오면 된다.


다음으로는 직접 로그인 페이지로 접근했을 때 즉, 인증 권한을 위해 로그인 화면을 자동으로 띄워주는 것이 아니라 직접 로그인 url을 통해 이동했을 경우에는 세션에 저장되지 않기 때문에 SavedRequest 객체로는 URL 정보를 가져올 수 없다. 이런 경우에는 HttpServletRequest 객체의 getHeader 메소드를 이용해서 REFERER 헤더 값을 읽어 올 수 있다. REFERER 헤더 값은 이전 페이지 URL을 가져오는 데 가장 보편적인 방법이다. 하지만 로그인에 관한 작업을 할 때에는 REFERER 헤더 값을 이용하는 걸 추천하지 않는다.


로그인 화면에서 <form> 태그로 action을 지정했었다. 로그인 버튼을 누르게 되면 해당 action 을 통해 로그인 처리 과정이 이루어질 것이다. 여기서 문제가 발생한다. 이렇게 로그인에 성공하게 되면 REFERER 헤더 값은 어떤 url이 저장되어 있을까? 바로 로그인 화면 url이 저장되어 있다. 따라서 직접 로그인 했을 때 redirect 로 REFERER 헤더 값을 지정한다면, 로그인에 성공하게 되도 다시 로그인 화면을 보여주게 된다. 그렇기 때문에 REFERER 헤더 값을 사용하는 것은 로그인 처리에서는 별로 좋지 못하다. 따라서 우리는 지정된 화면으로 이동하게 만들어 줄 것이다. security 설정에서 defalut-target-url 을 설정한 적이 있다. 로그인 성공시 이동할 url을 설정했는데, 이 url로 이동하게 해주면 된다.


LoginSuccessHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
        
    private RequestCache requestCache = new HttpSessionRequestCache();
    private RedirectStrategy redirectStratgy = new DefaultRedirectStrategy();
    
    ...
 
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        
        ...
        
        resultRedirectStrategy(request, response, authentication);
        
    }
 
    ...
    
    protected void resultRedirectStrategy(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        
        if(savedRequest!=null) {
            String targetUrl = savedRequest.getRedirectUrl();
            redirectStratgy.sendRedirect(request, response, targetUrl);
        } else {
            redirectStratgy.sendRedirect(request, response, defaultUrl);
        }
        
    }
}
cs

- 03행: RequestCache 객체를 생성한다.

- 04행: RedirectStratgy 객체를 생성한다.

- 14행: redirect url 작업을 위한 메서드를 호출한다.

- 23행: RequestCache 객체를 통해 SavedRequest 객체를 가져온다. 

- 25행: 세션에 이동할 url의 정보가 담겨져 있을때 즉, 인증 권한이 필요한 페이지에 접근했을 경우를 뜻한다.

- 26행: savedRequest 객체를 통해 getRedirectUrl (로그인화면을 보기 전에 갔던 url)을 가져온다.

- 28행: 직접 로그인 화면으로 이동했을 경우를 뜻한다.

- 27, 29행: 화면 이동을 위해 redirectStratgy 의 sendRedirect 를 재정의해준다. 각각에 알맞은 url 정보를 넣어주면 된다.


spring security에서 자동적으로 해주던 부분을 커스터마이징 해주면서 우리가 직접 경로를 설정하게 된 것이다. 만약에 권한에 따라 서로 다른 페이지로 이동하게 하고 싶으면 인증에 성공한 사용자의 권한을 이용해서 원하는 url로 보내주면 된다. 어떨 때 어디로 보낼 지 먼저 정해놓고 시작한다면 어렵지 않게 원하는대로 커스터마이징할 수 있을 것이다.



2. 로그인 실패 에러 세션 지우기

로그인을 하는 과정에서 한번만에 로그인에 성공할 수도 있지만, 실패를 한 후 로그인에 성공하는 경우도 있다. 이처럼 로그인에 실패하는 상황이 한번이라도 발생한다면, 에러가 세션에 저장되어 남아있게 된다. 로그인에 성공했다고 하지만 이렇게 세션에 에러가 남겨진 채로 넘어갈 수는 없다. 따라서 로그인 성공 핸들러에서 에러 세션을 지우는 작업을 해줘야 한다.


LoginSuccessHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
    Authentication authentication) throws IOException, ServletException {
 
        clearAuthenticationAttributes(request);
 
    }
 
    protected void clearAuthenticationAttributes(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if(session==nullreturn;
        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    }
 
}
cs

- 05행: 에러 세션을 지우는 메서드를 실헹한다.

- 10행: 세션을 받아온다.

- 11행: 세션이 null 즉, 세션에 에러가 없다면 그냥 return 된다.

- 12행: WebAttributes.AUTHENTICATION_EXCEPTION 이름 값으로 정의된 세션을 지운다.



3. 로그인 성공시, 실패 카운터 초기화

로그인 실패 핸들러에서 로그인에 실패 시, 실패 카운터를 증가시켰다. 그런데 실패 카운터가 증가된 상태로 로그인에 성공하게 되면, 다음번에 로그인할 때는 더 적은 실패 기회를 얻게 될 것이다. 따라서 로그인을 시도할 때마다 같은 시도 횟수를 주려고 한다면, 로그인에 성공할 때 실패 카운터를 초기화해야 한다. 


LoginSuccessHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Resource(name="userSer")
private UserService userSer;
 
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
    Authentication authentication) throws IOException, ServletException {
        
    String username = request.getParameter(loginidname);
        
    userSer.resetFailureCnt(username);
 
    ...
        
}
cs

Service 영역

1
2
3
4
5
6
7
8
//UserService
void resetFailureCnt(String username);
 
//UserServiceImpl
@Override
public void resetFailureCnt(String username) {
    userDAO.updateFailureCountReset(username);
}
cs

UserDAO.java

1
2
3
public void updateFailureCountReset(String username) {
    sqlSession.update("user.updateFailureCountReset", username);
}
cs

user_sql.xml

1
2
3
4
5
6
7
8
9
10
<update id="updateFailureCountReset">
    <![CDATA[
        UPDATE
            user
        SET
            FAILURE_CNT = 0
        WHERE
            ID = #{ loginId}
    ]]>
</update>
cs


이런 로직 작업은 정말 껌일 것이다. 그래서 따로 설명하지 않고 로직이 어디에 들어가야 하는 지만 잘 알아두면 언제든지 하고 싶은대로 커스텀 할 수 있다. 프로젝트를 실행시켜서 테스트를 해보자. 실패 카운터도 초기화 되고, url도 생각한 것처럼 이동되면 완성된 것이다!


GitHub

참고 : 사랑이 고픈 프로그래머..