SpringBoot에서 Interceptor 활용 세션 로그인 구현 방법

Spring 세션 로그인 개발

LoginInterceptor.java 생성

import com.chunjae.archive_cms.common.util.SessionUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (SessionUtil.isLogin()) {
            return true;

        } else {
            // 로그인이 되어있지 않은 경우 보여줄 페이지
            response.sendRedirect("/user/login");
            return false;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 로그아웃 후 뒤로가기 시 로그인했던 상태가 남아있지 않게 no-cache 설정
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1
        response.setHeader("Pragma", "no-cache"); // HTTP 1.0
        response.setDateHeader("Expires", 0); // Proxy
    }
}

HandlerInterceptor를 구현하고, preHandle 함수를 오버라이딩 해서 로그인 여부 체크를 체크합니다.
preHandle : 모든 컨트롤러 호출 전 실행되는 함수입니다.
postHandle : 컨트롤러 실행 후 호출되는 함수입니다.

WebConfig.java 생성

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") // 인터셉트를 적용할 페이지
                .excludePathPatterns("/", "/user/login", "/user/loginProc", "/user/logout") // 인터셉트를 제외할 페이지들
                .excludePathPatterns("/images/**", "/inc/**", "/html/**"); // 인터셉트를 제외할 webapp 이하 폴더들
    }
}

WebMvcConfigurer를 구현하여 인터셉터를 등록하면서, 인터셉터 적용/미적용 페이지를 설정합니다.

SessionUtil.java 생성 (필수X)

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpSession;
import java.util.Map;

@Slf4j
@Component
public class SessionUtil {

    @Bean
    public RequestContextListener requestContextListener() {
        return new RequestContextListener();
    }

    public static HttpSession getSession() {
        ServletRequestAttributes servletRequestAttribute = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        return servletRequestAttribute.getRequest().getSession();
    }

    public static void setAttribute(String key, Object attribute) {
        SessionUtil.getSession().setAttribute(key, attribute);
    }

    public static Map<String,Object> getLoginedUser() {
        HttpSession session = SessionUtil.getSession();
        return (Map<String, Object>) session.getAttribute("loginUser");
    }

    public static boolean isLogin () {
        return SessionUtil.getLoginedUser() != null;
    }

}

로그인 세션을 쉽게 관리하기 위한 유틸입니다.

Controller 예시

import com.chunjae.archive_cms.common.util.SessionUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private SessionUtil sessionUtil;

    @GetMapping("/login")
    public String getLoginPage() throws Exception {
        return "user/login";
    }

    @ResponseBody
    @PostMapping("/loginProc")
    public Map<String, Object> login(@RequestParam("id") String id, @RequestParam("pw") String pw) throws Exception {
        Map<String, Object> resultMap = new HashMap<>();

        Boolean loginSuccess = false;

        try {
            // 유저 정보 조회
            Map<String, Object> loginUser = userService.login(id, pw);

            if(loginUser != null) {
                sessionUtil.setAttribute("loginUser", loginUser);

                loginSuccess = true;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            resultMap.put("success", loginSuccess);
        }

        return resultMap;
    }

    @ResponseBody
    @PostMapping("/logout")
    public Map<String, Object> logout() {
        Map<String, Object> resultMap = new HashMap<>();

        try {
            // 세션 무효화, 세션의 모든 속성 삭제
            SessionUtil.getSession().invalidate();
            resultMap.put("success", true);
        } catch (Exception e){
            resultMap.put("success", false);
        }

        return resultMap;
    }

    @GetMapping("/pwChange")
    public String getPwChangePage() throws Exception {
        return "user/pwChange";
    }

    @ResponseBody
    @PostMapping("/pwCheck")
    public Map<String, Object> pwCheck(@RequestParam("id") String id, @RequestParam("pw") String pw) {
        Map<String, Object> resultMap = new HashMap<>();

        Boolean success = false;

        try {
            // 기존 비밀번호 체크
            Map<String, Object> userName = userService.pwCheck(id, pw);

            if(userName != null) {
                success = true;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            resultMap.put("success", success);
        }

        return resultMap;
    }

    @ResponseBody
    @PostMapping("/pwChangeProc")
    public Map<String, Object> pwChangeProc(@RequestParam("id") String id, @RequestParam("new_pw") String new_pw) {
        Map<String, Object> resultMap = new HashMap<>();

        Boolean success = false;

        try {
            // 비밀번호 변경
            Integer updateResult = userService.pwChange(id, new_pw);

            if(updateResult > 0) {
                success = true;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            resultMap.put("success", success);
        }

        return resultMap;
    }
}

세션 로그인, 로그아웃, 비밀번호 변경 등의 기능을 구현하는 컨트롤러 예시입니다.

SessionUtil 없이 세션에 값 저장하는 방법

@ResponseBody
@PostMapping("/saveSessionData")
public Map<String, Object> saveSessionData(HttpSession httpSession, @RequestParam HashMap<String, String> param) {
    Map<String, Object> resultMap = new HashMap<>();

    try {
        // 세션에 파라미터 값 저장
        httpSession.setAttribute("키", param.get("값"));
        resultMap.put("success", true);
    } catch (Exception e){
        resultMap.put("success", false);
    }

    return resultMap;
}

HttpSession 객체를 사용하여 직접 세션에 값을 저장해도 됩니다.

Service 예시

import com.chunjae.archive_cms.common.util.EncryptUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;

@Slf4j
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private EncryptUtil encryptUtil;

    public Map<String, Object> login(String id, String pw) throws Exception {
        pw = encryptUtil.getSHA512(pw);
        return userMapper.login(id, pw);
    }

    public Map<String, Object> pwCheck(String id, String pw) throws Exception {
        pw = encryptUtil.getSHA512(pw);
        return userMapper.pwCheck(id, pw);
    }

    public Integer pwChange(String id, String new_pw) throws Exception {
        new_pw = encryptUtil.getSHA512(new_pw);
        return userMapper.pwChange(id, new_pw);
    }

}

Mapper 예시

import org.apache.ibatis.annotations.Mapper;

import java.util.Map;

@Mapper
public interface UserMapper {
    Map<String, Object> login(String id, String pw) throws Exception;

    Map<String, Object> pwCheck(String id, String pw) throws Exception;

    Integer pwChange(String id, String new_pw) throws Exception;
}

Mybatis 쿼리 예시

<mapper namespace="com.chunjae.archive_cms.user.UserMapper">

    <select id="login" parameterType="java.util.HashMap" resultType="java.util.HashMap">
        SELECT
             id
            ,name
            ,role_cd
            ,dept_name
        FROM
            유저테이블명
        WHERE
            id = #{id}
          AND
            pw = #{pw}
          AND
            delete_yn = 'N'
    </select>

    <select id="pwCheck" parameterType="java.util.HashMap" resultType="java.util.HashMap">
        SELECT
            name
        FROM
            유저테이블명
        WHERE
            id = #{id}
          AND
            pw = #{pw}
    </select>

    <update id="pwChange" parameterType="java.util.HashMap">
        UPDATE
            유저테이블명
        SET
            pw = #{new_pw}
        WHERE
            id = #{id}
    </update>
</mapper>

로그인 화면 예시

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<body>
  <div class="wrap bg-gray h100">
    <jsp:include page="../common/layout/loginHeader.jsp"/>
    <div class="container">
      <div class="inner">
        <div class="login">
          <div class="box-login">
            <span class="title">로그인</span>
              <div class="form-input">
                <form id="loginForm" name="loginForm">
                <input id="id" name="id" type="text" placeholder="아이디" value="${cookie.userId.value}">
                <input id="pw" name="pw" type="password" placeholder="비밀번호">
                <input id="idSaveYn" name="idSaveYn" type="checkbox" name="" <c:if test="${cookie.idSaveYn.value eq 'Y'}">checked="true"</c:if>>
                <label for="idSaveYn">아이디 저장</label>
                </form>
              </div>
              <button class="btn size01" onclick="login();">로그인</button>
          </div>
          <em>※ 아이디/비밀번호가 기억나지 않을 경우, 담당자에게 문의하시길 바랍니다.<br>담당자: 고길동 (02-0000-0000)</em>
        </div>
      </div>
    </div>
  </div>
</body>

<script>
  // 엔터 키 이벤트 발생 시 로그인
  document.addEventListener("keyup", function(event) {
    if (event.key === 'Enter') {
      login();
    }
  });

  function login() {
    const formData = new FormData(document.getElementById("loginForm"));

    if(!document.getElementById('id').value) {
      alert("아이디를 입력해주세요.");
      return;
    }

    if(!document.getElementById('pw').value) {
      alert("비밀번호를 입력해주세요.");
      return;
    }

    // 로그인 처리
    fetch('/user/loginProc', {
      method: 'POST',
      body: formData
    }).then((response) => response.json())
      .then((data) => {
        if(data.success == true) {

          // CookieUtil.js 사용하여 365일간 쿠키 저장
          if ($("#idSaveYn").prop("checked") === true) {
            setCookie("userId", $("#id").val().trim(), 365);
            setCookie("idSaveYn", "Y", 365);
          } else {
            deleteCookie("userId");
            deleteCookie("idSaveYn");
          }

          // 메인으로 이동
          location.href = "/";

        }else {
          alert("아이디/비밀번호를 다시 한번 입력해주세요.");
        }

      })
      .catch((error) => {
        alert("로그인 할 수 없습니다. 관리자에게 문의해주세요.");
      })
  }
</script>
</html>

로그인 화면 JSP 예시입니다.

CookieUtil.js 예시

function setCookie(name, value, exp) {
    const date = new Date();
    date.setTime(date.getTime() + exp * 24 * 60 * 60 * 1000);
    document.cookie = name + '=' + value + ';expires=' + date.toUTCString() + ';path=/';
}

function getCookie(name) {
    var value = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
    return value? value[2] : null;
}

function deleteCookie(name) {
    // 만료일을 과거로 변경하여 쿠키 삭제
    document.cookie = name + '=; expires=Thu, 01 Jan 1999 00:00:10 GMT;path=/;';
}

아이디 저장 기능 구현 시 사용하는 Javascript 쿠키 저장 유틸 예시입니다.

비밀번호 변경 화면 예시

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<body>
<div class="wrap bg-gray h100">
  <jsp:include page="../common/layout/simpleHeader.jsp"/>
  <div class="container">
    <div class="inner">
      <div class="inquiry">
        <div class="title-wrap">
          <span class="title">비밀번호 수정</span>
          <span class="txt">새로운 비밀번호를 입력해주세요.</span>
        </div>
        <div class="box-pwinquiry">
          <div class="form-input">
            <form id="pwForm" name="pwForm">
            <div>
              <span class="sub-tit">기존 비밀번호</span>
              <input id="pw" name="pw" type="password">
            </div>
            <div>
              <span class="sub-tit">새 비밀번호</span>
              <input id="new_pw" name="new_pw" type="password" onchange="pwSameCheck();">
            </div>
            <div>
              <span class="sub-tit">비밀번호 확인</span>
              <input id="new_pw_check" name="new_pw_check" type="password" onchange="pwSameCheck();">
              <em class="error" style="display:none;">* 비밀번호를 다시 한번 입력해주세요.</em>
            </div>
            </form>
          </div>
        </div>
        <button class="btn size01" onclick="pwChange();">확인</button>
      </div>
    </div>
  </div>
</div>
<script>

  function pwSameCheck() {
    const pw = document.getElementById('new_pw').value;
    const new_pw = document.getElementById('new_pw_check').value;

    if(pw === new_pw) {
      $('.error').attr("style", "display:none;");
    }else {
      $('.error').attr("style", "display:inline-block;");
    }
  }

  function pwChange() {
    const formData = new FormData(document.getElementById("pwForm"));

    if(!document.getElementById('pw').value) {
      alert("기존 비밀번호를 입력해주세요.");
      return;
    }

    const new_pw = document.getElementById('new_pw').value;
    const new_pw_check = document.getElementById('new_pw_check').value;

    if(!new_pw) {
      alert("새 비밀번호를 입력해주세요.");
      return;
    }

    if(!new_pw_check) {
      alert("비밀번호 확인을 입력해주세요.");
      return;
    }

    formData.append("id", "${sessionScope.loginUser.id}");

    let errMsg = "비밀번호를 수정할 수 없습니다. 관리자에게 문의해주세요.";

    // 기존 비밀번호 확인
    fetch('/user/pwCheck', {
      method: 'POST',
      body: formData
    }).then((response) => response.json())
      .then((data) => {

        // 기존 비밀번호로 유저 조회되면
        if(data.success == true) {

          if(new_pw !== new_pw_check) {
            alert("새 비밀번호, 비밀번호 확인이 다릅니다.");
            return;
          }

          // 비밀번호 변경 처리
          fetch('/user/pwChangeProc', {
            method: 'POST',
            body: formData
          }).then((response) => response.json())
            .then((data) => {

              // 비밀번호 변경 완료
              if(data.success == true) {
                alert("비밀번호가 변경되었습니다.");

                // 메인으로 이동
                location.href = "/";

              }else {
                alert(errMsg);
              }

            })
            .catch((error) => {
              alert(errMsg);
            })

        }else {
          alert("기존 비밀번호를 다시 확인해주세요.");
        }

      })
      .catch((error) => {
        alert(errMsg);
      })
  }
</script>
</body>
</html>

비밀번호 변경 화면 JSP 예시입니다.

메뉴 헤더 예시

<c:if test="${sessionScope.loginUser.role_cd eq 0}">
<li>
    <a href="/user">사용자 관리</a>
</li>
</c:if>

로그인 유저 권한에 따라 헤더 메뉴를 비활성화하는 예시입니다.

세션 만료 시간 설정

server.servlet.session.timeout=10800 #3시간 (초단위)

SpringBoot는 application.properties 파일에 세션 만료 시간을 설정할 수 있습니다.

Leave a comment