ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Camel][Spring] 게시판 만들기 #10. 로그아웃, 로그인 유지 기능 구현
    Spring/게시판 만들기 2020. 7. 4. 19:40

    [Camel][Spring] 게시판 만들기 #10. 로그아웃, 로그인 유지 기능 구현

     

    본 게시판 만들기 프로젝트는 더블에스 Devlog Spring-MVC 를 참조하여 작성했음을 알려드립니다. 또한 개인적인 학습을 목적으로한 포스팅이기 때문에 완벽하지 않을 수 있음을 알려드립니다. 문제점이나 궁금한점은 댓글로 남겨주시면 감사하겠습니다. 프로젝트 생성에 앞서 이번 게시판 만들기 프로젝트는 이클립스를 사용하여 구현하였습니다. 

     

    저번 포스팅에서 회원가입과 로그인 기능을 구현해 보았는데 로그아웃 기능은 구현하지 않았었습니다. 이번 포스팅에서는 저번에 구현하지 않았던 로그아웃 기능을 구현하고, 추가적으로 로그인 유지 기능을 구현해보도록 하겠습니다. 

     

    우선 로그인 유지 기능을 먼저 구현해보겠습니다. 

     

    1. 로그인 유지 기능 구현 

    이전에 만든 로그인 페이지를 확인해보면 로그인 유지 기능을 구현하기 위해 checkbox가 있는 것을 확인할 수 있습니다. 로그인 유지 기능을 구현하기 위해서는 Cookie를 사용하겠으며, 사용자가 로그인한 후 Cookie를 생성하고 브라우저에게 전송하며, 다시 서버에 접속할 때 Cookie가 전달 되는지 확인해 보겠습니다.

     

    1-1. LoginInterceptor 수정

    이전 포스팅에서 작성했던 LoginInterceptor에서는 postHandle 메소드를 통해 HttpSession에 UserVO 객체를 저장하고 있습니다. 이 부분을 중간에 Cookie를 생성하고 HttpServletResponse 에 담아 전송하도록 아래와 같이 코드를 수정해주겠습니다. 

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
        HttpSession httpSession = request.getSession();
        ModelMap modelMap = modelAndView.getModelMap();
        Object userVO =  modelMap.get("user");
    
        if (userVO != null) {
            logger.info("new login success");
            httpSession.setAttribute(LOGIN, userVO);
            //response.sendRedirect("/");
    
            if (request.getParameter("useCookie") != null) {
                logger.info("remember me...");
                // 쿠키 생성
                Cookie loginCookie = new Cookie("loginCookie", httpSession.getId());
                loginCookie.setPath("/mypage");
                loginCookie.setMaxAge(60*60*24*7);
                // 전송
                response.addCookie(loginCookie);
            }
    
            Object destination = httpSession.getAttribute("destination");
            response.sendRedirect(destination != null ? (String) destination : "/mypage");
        }
    
    }

     

    1-2. 로그인 유지를 위한 DB 설정

    자동로그인의 처리는 Session과 Cookie를 이용하여 처리합니다. 로그인 유지 기능이 사용되는 경우는 HttpSession에 login이란 이름으로 보관된 객체가 없지만 loginCookie가 존재하고, 이 경우 Interceptor에서 설정한 7일의 기간 사이에 접속한 적이 있다는 것을 확인한 뒤 과거의 로그인 시점에 기록된 정보를 이용해 다시 HttpSession에 login이란 이름으로 UserVO 객체를 보관해주어야 합니다. 

     

     이러한 개념을 바탕으로 User가 loginCookie를 가지고 있다면 그 값이 과거 로그인한 시점의 SessionId 라는 것을 알 수 있으며, loginCookie 값을 사용해 DB에서 UserVO의 정보를 읽어온 뒤 이를 HttpSession에 보관하도록 하겠습니다. 

     

     User 테이블에는 이전에 미리 session과 관련된 column(session_key, session_limit)을 미리 생성해 두었습니다. 

     

    1-3. Persistence 계층 작성

    UserDAO 인터페이스에 sessionKey sessionLimit를 업데이트하는 메소드 loginCookie에 기록된 값으로 사용자의 정보를 조회하는 메서드를 추가하고, UserDAOImpl 클래스에서 구현을 해주도록 하겠습니다.

    // 로그인 유지 처리
    void keepLogin(String userId, String sessionId, Date sessionLimit) throws Exception;
    
    // 세션키 검증
    UserVO checkUserWithSessionKey(String value) throws Exception;
    // 로그인 유지 처리
    @Override
    public void keepLogin(String userId, String sessionId, Date sessionLimit) throws Exception {
      Map<String, Object> paramMap = new HashMap<String, Object>();
      paramMap.put("userId", userId);
      paramMap.put("sessionId", sessionId);
      paramMap.put("sessionLimit", sessionLimit);
    
      sqlSession.update(NAMESPACE + ".keepLogin", paramMap);
    }
    
    // 세션키 검증
    @Override
    public UserVO checkUserWithSessionKey(String value) throws Exception {
      return sqlSession.selectOne(NAMESPACE + ".checkUserWithSessionKey", value);
    }

    userMapper.xml 파일에는 아래와 같은 sql문을 추가해주도록 하겠습니다. 

    <update id="keepLogin">
        UPDATE tb_user
        SET session_key = #{sessionId}
            , session_limit = #{sessionLimit}
        WHERE user_id = #{userId}
    </update>
    
    <select id="checkUserWithSessionKey" resultMap="userVOResultMap">
        SELECT
            *
        FROM tb_user
        WHERE session_key = #{value}
    </select>

     

     

    1-4. Service 계층 작성

    UserService 인터페이스에 로그인 유지 메소드와 loginCookie로 회원정보를 조회하는 메소드를 추가하고 UserServiceImpl 클래스에서 구현하겠습니다. 

    void keepLogin(String userId, String sessionId, Date next) throws Exception;
    
    UserVO checkLoginBefore(String value) throws Exception;
    @Override
    public void keepLogin(String userId, String sessionId, Date sessionLimit) throws Exception {
        userDAO.keepLogin(userId, sessionId, sessionLimit);
    }
    
    @Override
    public UserVO checkLoginBefore(String value) throws Exception {
        return userDAO.checkUserWithSessionKey(value);
    }

     

    1-5. UserLoginController 수정

    UserLoginController의 loginPost 매핑 매소드를 아래와 같이 수정하도록 하겠습니다. 

    // 로그인 처리
    @RequestMapping(value = "/loginPost", method = RequestMethod.POST)
    public void loginPOST(LoginDTO loginDTO, HttpSession httpSession, Model model) throws Exception {
    
        UserVO userVO = userService.login(loginDTO);
    
        if (userVO == null || !BCrypt.checkpw(loginDTO.getUserPw(), userVO.getUserPw())) {
            return;
        }
    
        model.addAttribute("user", userVO);
    
        // 로그인 유지를 선택할 경우
        if (loginDTO.isUseCookie()) {
            int amount = 60 * 60 * 24 * 7;  // 7일
            Date sessionLimit = new Date(System.currentTimeMillis() + (1000 * amount)); // 로그인 유지기간 설정
            userService.keepLogin(userVO.getUserId(), httpSession.getId(), sessionLimit);
        }
    
    }

     

    1-6. 로그인 유지를 위한 Interceptor 작성

    Interceptor를 통해 사용자가 접속을 할 경우 로그인이 유지 되도록 처리해보겠습니다. RememberMeInterceptor 클래스를 Interceptor로 스프링이 인식할 수 있도록 servlet-context.xml에 아래와 같이 설정을 추가해줍니다.

    package com.cameldev.mypage.commons.interceptor;
    
    import javax.inject.Inject;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    import org.springframework.web.util.WebUtils;
    
    import com.cameldev.mypage.domain.UserVO;
    import com.cameldev.mypage.service.UserService;
    
    public class RememberMeInterceptor extends HandlerInterceptorAdapter {
    
        private static final Logger logger = LoggerFactory.getLogger(RememberMeInterceptor.class);
    
        @Inject
        private UserService userService;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            HttpSession httpSession = request.getSession();
            Cookie loginCookie = WebUtils.getCookie(request, "loginCookie");
            if (loginCookie != null) {
                UserVO userVO = userService.checkLoginBefore(loginCookie.getValue());
                if (userVO != null)
                    httpSession.setAttribute("login", userVO);
            }
    
            return true;
        }
    }
    <beans:bean id="loginInterceptor" class="com.cameldev.mypage.commons.interceptor.LoginInterceptor"/>
    	<beans:bean id="authInterceptor" class="com.cameldev.mypage.commons.interceptor.AuthInterceptor"/>
    	<beans:bean id="rememberMeInterceptor" class="com.cameldev.mypage.commons.interceptor.RememberMeInterceptor"/>
    	<interceptors>
            <interceptor>
                <mapping path="/user/loginPost"/>
                <beans:ref bean="loginInterceptor"/>
            </interceptor>
            <interceptor>
                <mapping path="/article/paging/search/write"/>
                <mapping path="/article/paging/search/modify"/>
                <mapping path="/article/paging/search/remove"/>
                <mapping path="/user/info"/>
                <beans:ref bean="authInterceptor"/>
            </interceptor>
            <interceptor>
                <mapping path="/**/"/>
                <beans:ref bean="rememberMeInterceptor"/>
            </interceptor>
        </interceptors>

     

    위의 과정을 모두 수행해주었다면 이제 로그인 유지 기능이 구현된 것을 확인할 수 있습니다. 로그인 유지를 체크해서 로그인한 뒤 브라우저를 종료하고 다시 접속하면 정상적으로 로그인이 유지되는 것을 확인할 수 있습니다. 

     

     

     

    2. 로그아웃 기능 구현 

    로그아웃 처리는 UserLoginController에서 login과 같이 저장된 정보를 삭제하고 invalidate()를 해주는 작업과 쿠키의 유효시간을 만료시키는 작업을 해주면 됩니다.

    // 로그아웃 처리
    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public String logout(HttpServletRequest request,
                         HttpServletResponse response,
                         HttpSession httpSession) throws Exception {
    
        Object object = httpSession.getAttribute("login");
        if (object != null) {
            UserVO userVO = (UserVO) object;
            httpSession.removeAttribute("login");
            httpSession.invalidate();
            Cookie loginCookie = WebUtils.getCookie(request, "loginCookie");
            if (loginCookie != null) {
                loginCookie.setPath("/");
                loginCookie.setMaxAge(0);
                response.addCookie(loginCookie);
                userService.keepLogin(userVO.getUserId(), "none", new Date());
            }
        }
    
        return "/user/logout";
    }

    그리고 logout.jsp를 생성하고 아래와 같이 별다른 내용없이 로그아웃 알림 메시지와 메인 페이지로 이동하는 코드를 작성해줍니다.

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <script>
        alert("로그아웃 되었습니다.");
        self.location = "/mypage";
    </script>
    </body>
    </html>

     

    이제 정상적으로 로그아웃이 되는지 확인해 보면 정상적으로 로그아웃 기능이 작동하는 것을 확인할 수 있을것입니다. 

     

     

    3. 추가사항 - 로그인 상태에서의 로그인 페이지, 회원가입 페이지 접근 제한

    로그인 상태에서는 더이상 로그인을 할 필요가 없고 회원가입 역시 필요가 없기 때문에 로그인 했을 경우 회원가입과 로그인에 제한을 해주도록 하겠습니다.

     

    위 기능을 구현하기 위해 LoginAfterInterceptor 를 생성해주고 아래와 같이 작성하겠습니다. 그리고 LoginAfterInterceptor클래스를 Interceptor로 인식하도록 servlet-context.xml에 아래와 같이 설정해줍니다.

    public class LoginAfterInterceptor extends HandlerInterceptorAdapter {
    
        private static final Logger logger = LoggerFactory.getLogger(LoginAfterInterceptor.class);
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            // 로그인 처리후 로그인페이지 or 회원가입 페이지로 이동할 경우
            HttpSession session = request.getSession();
            if (session.getAttribute("login") != null) {
                response.sendRedirect(request.getContextPath() + "/");
                return false;
            }
            return true;
        }
    }
    <beans:bean id="loginInterceptor" class="com.cameldev.mypage.commons.interceptor.LoginInterceptor"/>
    	<beans:bean id="authInterceptor" class="com.cameldev.mypage.commons.interceptor.AuthInterceptor"/>
    	<beans:bean id="rememberMeInterceptor" class="com.cameldev.mypage.commons.interceptor.RememberMeInterceptor"/>
    	<beans:bean id="loginAfterInterceptor" class="com.cameldev.mypage.commons.interceptor.LoginAfterInterceptor"/>
    	<interceptors>
            <interceptor>
                <mapping path="/user/loginPost"/>
                <beans:ref bean="loginInterceptor"/>
            </interceptor>
            <interceptor>
                <mapping path="/article/paging/search/write"/>
                <mapping path="/article/paging/search/modify"/>
                <mapping path="/article/paging/search/remove"/>
                <mapping path="/user/info"/>
                <beans:ref bean="authInterceptor"/>
            </interceptor>
            <interceptor>
                <mapping path="/**/"/>
                <beans:ref bean="rememberMeInterceptor"/>
            </interceptor>
            <interceptor>
                <mapping path="/user/login"/>
                <mapping path="/user/register"/>
                <beans:ref bean="loginAfterInterceptor"/>
            </interceptor>
        </interceptors>

     

     

    4. 포스팅을 마치며 

    이번 포스팅에서는 로그아웃 기능과 로그인 유지 기능을 구현해보았습니다. 다음 포스팅에서는 게시판의 첨푸파일 업로드에 대해 포스팅하도록하겠습니다. 

     

     

    다음포스팅

    2020/07/05 - [Spring/게시판 만들기] - [Camel][Spring] 게시판 만들기 #11. 첨부파일 업로드 기능 구현

     

    [Camel][Spring] 게시판 만들기 #11. 첨부파일 업로드 기능 구현

    [Camel][Spring] 게시판 만들기 #11. 첨부파일 업로드 기능 구현 본 게시판 만들기 프로젝트는 더블에스 Devlog Spring-MVC 를 참조하여 작성했음을 알려드립니다. 또한 개인적인 학습을 목적으로한

    cameldev.tistory.com

     

     

    글을 읽으시면서 잘못된 부분이나 궁금하신 사항은 댓글 달아주시면 빠른 시일내에 수정 및 답변하도록 하겠습니다. 

    댓글

Camel`s Tistory.