티스토리 뷰

 

[ 1. 인터셉터 - 세션 트레킹 ( 로그인 / 로그아웃 / 인증 / 인가 / 자동 로그인 처리 ) ] (******)

 

[ + DTO는 전의 게시물 LoginDTO를 그대로 사용합니다. ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.domain;

import lombok.Data;


@Data
public class LoginDTO {
	
	private String userid;
	private String userpw;
	
	// + 참조타입의 기본값은 NULL이다.
	// + 따라서 체크박스의 check 여부의 기본값은 false로 지정하는 것이 좋다.
	private Boolean rememberMe = false;

} // end class

 

[ + SharedScopeKeys는 공유데이터에 바인딩할때 Key로 사용하기 위해서 만든 클래스입니다. ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.common;

public final class SharedScopeKeys {
	
	public static final String LOGIN_KEY = "__LOGIN_KEY__";
	public static final String RESULT = "__RESULT__";
	public static final String EXCEPTION = "__EXCEPTION__";
	public static final String USER_KEY = "__USER_KEY__";
	public static final String REMEMBER_ME_KEY = "__REMEMBER_ME__";
	
} // end class

 

[ 1 - 1. DAO 인터페이스 생성 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.persistence;

import org.zerock.myapp.domain.LoginDTO;
import org.zerock.myapp.domain.UserVO;
import org.zerock.myapp.exception.DAOException;

public interface UserDAO {
	
	// 1. 로그인 창에서 입력한 아이디와 암호에 매칭되는 사용자의 정보 획득
	public abstract UserVO selectUser(LoginDTO dto) throws DAOException;

} // end interface

 

[ 1 - 2. mapper에 sql문 등록 ]

 

더보기

[ + 코드 보기 ]

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.zerock.myapp.persistence.UserDAO">

    <select 
        id="selectUser" 
        resultType="org.zerock.myapp.domain.UserVO">

        <![CDATA[ 
            SELECT * 
            FROM tbl_user 
            WHERE userid = #{userid} AND userpw = #{userpw} 
        ]]>

    </select>

    <!-- ========================================================================================= -->

</mapper>

 

[ + 마이바티스 설정 파일에서 mapper 등록 ]

 

 

[ 1 - 3. DAO 구현 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.persistence;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.zerock.myapp.domain.LoginDTO;
import org.zerock.myapp.domain.UserVO;
import org.zerock.myapp.exception.DAOException;

import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@Repository
public class UserDAOImpl implements UserDAO {

	@Autowired
	private SqlSessionFactory sqlSessionFactory;
	
	@Override
	public UserVO selectUser(LoginDTO dto) throws DAOException {
		
		log.trace("selectUser({}) invoked.", dto);
		
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		try ( sqlSession ){
			
			String namespace = "org.zerock.myapp.persistence.UserDAO";
			String sqlId = "selectUser";
			String sql = namespace + "." + sqlId;
			
			return sqlSession.<UserVO>selectOne(sql, dto);
			
		} catch(Exception e) {
			throw new DAOException(e);
		} // try - with - resources
		
	} // selectUser

} // end class

 

[ 1 - 4. Service 인터페이스 생성 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.service;

import org.zerock.myapp.domain.LoginDTO;
import org.zerock.myapp.domain.UserVO;
import org.zerock.myapp.exception.DAOException;
import org.zerock.myapp.exception.ServiceException;

public interface UserService {

	// =====================================================================
	// 1. 로그인 창에서 입력한 아이디와 암호에 매칭되는 회원이 있는지 확인
	// =====================================================================
	// ReturnType을 Boolean이 아닌 UserVO로 하는 이유는,
	// 이 객체를 바로 로그인 성공 정보로 Session Scope에 바인딩시키기 위해서이다.
	public abstract UserVO login(LoginDTO dto) throws ServiceException;
	
} // end interface

 

[ 1 - 5. Service 구현 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zerock.myapp.domain.LoginDTO;
import org.zerock.myapp.domain.UserVO;
import org.zerock.myapp.exception.DAOException;
import org.zerock.myapp.exception.ServiceException;
import org.zerock.myapp.persistence.UserDAO;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;


@Log4j2
@AllArgsConstructor

@Service
public class UserServiceImpl implements UserService {
	
	// ===============================================================
	
	@Autowired
	private UserDAO userDAO;

	// ===============================================================
	// + 1. 로그인 처리
	// ===============================================================
	
	@Override
	public UserVO login(LoginDTO dto) throws ServiceException {
		
		log.trace("login({}) invoked.", dto);
		
		try {
			return this.userDAO.selectUser(dto);
		} catch ( DAOException e ) {
			throw new ServiceException(e);
		} // try - catch
		
	} // login
	
	// ===============================================================

} // end class

 

[ 1 - 6. 컨트롤러 생성 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.zerock.myapp.common.SharedScopeKeys;
import org.zerock.myapp.domain.LoginDTO;
import org.zerock.myapp.domain.UserVO;
import org.zerock.myapp.exception.ControllerException;
import org.zerock.myapp.service.UserService;

import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@RequestMapping("/user/")
@Controller
public class LoginController {
	
	@Autowired
	private UserService service;
	
	// ===============================================================================================================
	// + 1. 로그인 처리
	// ===============================================================================================================
	
	@PostMapping("/loginPost")
	// + @ModelAttribute 어노테이션을 통해 모델에 저장할 수 있다.
	public String loginPost ( /* @ModelAttribute("loginDTO") */ LoginDTO dto, Model model, RedirectAttributes rttrs ) 
			throws ControllerException {
		
		log.trace("loginPost({}, {}) invoked.", dto, model);
		// + loginPost(LoginDTO(userid=hohohoh, userpw=1234, rememberMe=true)) invoked.
		// + view에서 post로 정보가 들어온다.
		
		try {
			
			UserVO vo = this.service.login(dto);
			log.info("\t + vo : {}", vo);
			
			if( vo != null ) { 							// 성공했을 때
				
				model.addAttribute(SharedScopeKeys.LOGIN_KEY, vo);
				
				return "user/loginPost";
				// + redirect로 할 경우에는 Request Scope이 깨지기 때문에, model안의 vo가 나오지 못한다.
				
			} else { 									// 실패했을 때
				
				rttrs.addAttribute(SharedScopeKeys.RESULT, "Login Failed.");
				return "redirect:/user/login";
				
			} // if - else
			
		} catch (Exception e) {
			throw new ControllerException(e);
		} // try - catch
		
	} // loginPost
	
	// ===============================================================================================================
	// + 2. 로그 아웃 처리
	// ===============================================================================================================
	// + 로그 아웃 요청시에 수행되어야 할 일 : --> LogoutInterceptor가 수행
	// + 1. Session Scope 자체를 파괴시켜야 한다. ( 즉, session.invalidate() 메소드 수행 )
	// + 2. 현재 웹 브라우저에 할당된 이름( 세션 ID )도 삭제
	// ===============================================================================================================
	
	@GetMapping("/logout")
	public void logout( RedirectAttributes rttrs ) throws ControllerException {
		
		log.trace("logout() invoked.");
		// + 로그아웃은 LogoutInterceptor를 설정하기 위한 용도로만 사용될 뿐, 진짜 호출되지는 않는다.
		
	} // logout
	
	// ===============================================================================================================

} // end class

 

[ 1 - 7. 인터셉터 생성 ]

 

[ + 로그인 / 자동 로그인 처리 인터셉터 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.interceptor;

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

import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.zerock.myapp.common.SharedScopeKeys;
import org.zerock.myapp.domain.UserVO;

import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@Component
public class LoginInterceptor implements HandlerInterceptor {
	
	// ===================================================================================================
	// + 전제 :
	// + 로그인에 성공하면 UserVO를 Session Scope에 올려 놓을 예정이다.
	// ===================================================================================================
	// + /user/loginPost로 접속 -> preHandle -> LoginController의 로그인 처리 -> postHandle
	// + preHandle에서는 이전에 접속한 
	// ===================================================================================================

	// ===================================================================================================
	// + 1. preHandle : 로그인
	// ===================================================================================================
	// + Incoming Request가 Controller's Handler Method로 위임되기 직전에 가로 채는 부분
	// + Session Scope에 저장되어 있는 모든 정보를 파괴 수행
	// + (*주의*) 명시적으로 로그아웃 요청을 보내지 않는 이상, 세션 자체를 파괴해서는 안된다.
	// ===================================================================================================
	@Override
	public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
		
		log.info("========================================================");
		log.trace("1. preHandle(req, res, handler) invoked.");
		log.info("========================================================");
		
		// ===================================================================
		// 1. Session Scope에 접근할 수 있는 HttpSession 객체 획득
		// ===================================================================
		HttpSession session = req.getSession();
		
		// ===================================================================
		// 2. Session Scope에 UserVO 객체가 공유 되어 있으면 삭제 처리
		// ===================================================================
		UserVO vo = (UserVO) session.getAttribute(SharedScopeKeys.USER_KEY);
		// + 현재 Session Scope에 UserVO가 있는지, 로그인 되어있는 상태인지 확인
		
		if ( vo != null ) {
			session.removeAttribute(SharedScopeKeys.USER_KEY);
			log.info("\t + Remove UserVO : {}", vo);
		} else {
			log.info("\t + No UserVO found in Session Scope");
		} // if : null이 아니라면, UserVO가 Session Scope에 올려진 상태이기에 삭제해버린다.
		
		return true;
	} // preHandle
	
	// ===================================================================================================
	// + 2. postHandle : 로그인 성공 증빙 + 자동 로그인 처리
	// ===================================================================================================

	// + 만약 컨트롤러에서 예외가 발생하면, 아래의 callback 메소드는 실행되지 않는다.
	@Override
	public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler,
			ModelAndView modelAndView) throws Exception {
		
		log.info("========================================================");
		log.trace("2. postHandle(req, res, handler, {}) invoked.", modelAndView);
		log.info("========================================================");
		
		// ===================================================================
		// 1. 로그인 성공 증빙 데이터 바인딩
		// ===================================================================
		
		// + 매개변수인 modelAndView에 Model 상자 안에 UserVO 객체가 있는지 확인해 보고 ( 즉, 로그인 결과 )
		// + 만약 성공했다면, 이 UserVO 객체를 Session Scope에 있는 로그인 성공증빙으로 올려 놓기로 한다.
		
		// ===================================================================
		// 1 ) modelAndView 객체 안에 UserVO가 있는지 확인
		// ===================================================================
		ModelMap modelMap = modelAndView.getModelMap();
		UserVO vo = (UserVO) modelMap.getAttribute(SharedScopeKeys.LOGIN_KEY);
		
		// ===================================================================
		// 2 ) UserVO가 있다면( 로그인 성공 시 ) Session Scope에 UserVO를 올려 놓는다. ( 로그인 성공 증빙 )
		// ===================================================================
		if ( vo != null ) { // + 성공했을 때
			
			// + 현재 Session Scope에 접근하기 위해 HttpSession 객체 생성
			HttpSession session = req.getSession();
			
			// + Session Scope에 공유 데이터로 로그인 성공 증빙 데이터를 바인딩
			session.setAttribute(SharedScopeKeys.USER_KEY, vo);
			
			// ===================================================================
			// 2. 자동 로그인 처리
			// ===================================================================
			
			// ===================================================================
			// 1 ) 자동 로그인(rememberMe) 옵션의 on / off 여부 확인
			// ===================================================================
			boolean isRememberMeOption = checkRememberMeOption(req);
			log.info("\t + isRememberMeOption : {}", isRememberMeOption);
			
			if ( isRememberMeOption ) { // + 자동 로그인 기능 적용
				
				// + Response Message의 Header에 쿠키를 저장해서 보낸다.
				// + 이때 쿠키값으로는 현재 브라우저의 이름인 세션ID를 저장하자!
				// + (*주의*) 세션 ID는 UUID이다.
				
				String sessionId = session.getId();
				Cookie rememberMeCookie = new Cookie(SharedScopeKeys.REMEMBER_ME_KEY, sessionId);
				// + 쿠키는 전부 문자열이기에, 숫자로 보일지라도 문자열이다.
				// + public Cookie(String name, String value)
				
				rememberMeCookie.setMaxAge( 1 * 60 * 60 * 24 * 7 ); // 1주일
				// + 쿠키의 유효기간( 자동 로그인 유효기간 ) 설정
				// + .setMaxAge의 단위는 초이다.
				
				rememberMeCookie.setPath("/");
				
				// ===================================================================
				// + 쿠키 확인
				// ===================================================================
				log.info("\t + rememberMeCookie - 1. name : {}", rememberMeCookie.getName());
				log.info("\t + rememberMeCookie - 2. value : {} ", rememberMeCookie.getValue());
				log.info("\t + rememberMeCookie - 3. path : {} ", rememberMeCookie.getPath());
				log.info("\t + rememberMeCookie - 4. MaxAge : {} ", rememberMeCookie.getMaxAge());
				
				res.addCookie(rememberMeCookie);
				// + 응답 메세지의 'set-cookie' 헤더에 자동설정된다.
				
			} // inner if
			
		} // if : 성공했다면

	} // postHandle
	
	// ===================================================================================================
	// + 3. afterCompletion : 주로 자원 해제하는데 사용
	// ===================================================================================================
	// + afterCompletion은 view처리가 끝난 후 User에게 응답이 나가기 직전에 인터셉트하게 되는데,
	// + 만약 처리할게 없다면, 삭제해도 된다.
	// ===================================================================================================

	@Override
	public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception e)
			throws Exception {
		
		log.info("========================================================");
		log.trace("3. afterCompletion(req, res, handler, handler, e) invoked.");
		log.info("========================================================");
		
	} // afterCompletion
	
	// ===================================================================================================
	// + 4. 자동 로그인 on / off 체크 여부 확인 메소드
	// ===================================================================================================
	
	private boolean checkRememberMeOption(HttpServletRequest req) {
		
		log.trace("checkRememberMeOption(req) invoked.");
		
		String rememberMe = req.getParameter("rememberMe");
		log.info("\t + rememberMe : {}",rememberMe);
		// + 전송 파라미터 중에서 rememberMe로 들어온 값을 변수 rememberMe에 저장
		
		return rememberMe != null;
		// + rememberMe가 null이 아니면 true를 반환하고,
		// + rememberMe가 null이라면 false를 반환한다.
		
	} // checkRememberMeOption
	
	// ===================================================================================================

} // end class

 

[ + 인증 / 인가 처리 인터셉터 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.interceptor;

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

import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.zerock.myapp.common.SharedScopeKeys;
import org.zerock.myapp.domain.UserVO;

import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@Component
public class AuthInterceptor implements HandlerInterceptor { // 인증, 인가 처리
	
	// ===================================================================================================
	// + 1. preHandle
	// ===================================================================================================
	// + 게시판에 관련된 모든 요청에 대해서 인증된 사용자(브라우저)인지를 가장 먼저 체크하고
	// + 만약, 인증되지 않은 사용자(웹 브라우저)라면, 로그인을 하도록 로그인 창으로 리다이렉트해야 한다,
	// + 위의 로직은 모든 인증 기능에서 가장 먼저 수행되어야 할 "보안 로직"이다.
	// ===================================================================================================
	
	@Override
	public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
		
		log.info("========================================================");
		log.trace("1. preHandle(req, res, handler) invoked.");
		log.info("========================================================");
		
		// =======================================================================
		// 1. 현재 요청 URI를 전송한 사용자(웹 브라우저)의 인증여부 확인
		// =======================================================================
		
		HttpSession session = req.getSession();
		log.info("\t + Session ID : {}", session.getId() );
		
		UserVO vo = (UserVO) session.getAttribute(SharedScopeKeys.USER_KEY);
		// + Session Scope에 있는 공유 데이터 중 로그인 성공 데이터를 찾는다.
		
		if ( vo != null ) { // + 인증된 사용자(웹 브라우저)라면...
			
			log.info("\t + Already Authenticated : {}", vo);
			
			return true;
			// + 요청을 컨트롤러의 헨들러로 넘긴다.
			
		} else { // + 인증되지 않은 사용자(웹 브라우저)라면...
			
			log.info("\t + NO Authenticated User");
			
			res.sendRedirect("/user/login");
			// + 로그인 창으로 리다이렉트해버린다.
			
			return false;
			// + 요청을 컨트롤러의 헨들러로 넘기지 않는다.
			// + 인증되지 않은 사용자의 경우에는 게시물로 접근이 불가능해야 한다.
			
		} // if - else
		
	} // preHandle
	
	// ===================================================================================================
	// + 2. postHandle
	// ===================================================================================================

	// + 만약 컨트롤러에서 예외가 발생하면, 아래의 callback 메소드는 실행되지 않는다.
	@Override
	public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler,
			ModelAndView modelAndView) throws Exception {
		
		log.info("========================================================");
		log.trace("2. postHandle(req, res, handler, {}) invoked.", modelAndView);
		log.info("========================================================");

	} // postHandle
	
	// ===================================================================================================
	// + 3. afterCompletion
	// ===================================================================================================

	@Override
	public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception e)
			throws Exception {
		
		log.info("========================================================");
		log.trace("3. afterCompletion(req, res, handler, handler, e) invoked.");
		log.info("========================================================");
		
	} // afterCompletion
	
} // end class

 

[ + 로그아웃 처리 인터셉터 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.interceptor;

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

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.zerock.myapp.common.SharedScopeKeys;

import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@Component
public class LogoutInterceptor implements HandlerInterceptor {
	
	// ===================================================================================================
	// + 1. preHandle
	// ===================================================================================================
	
	@Override
	public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
		
		log.info("========================================================");
		log.trace("1. preHandle(req, res, handler) invoked.");
		log.info("========================================================");
		
		// + 1. 현재 브라우저에 할당된 Session Scope를 모두 파괴하고, ( 즉, 로그인 성공증빙도 파괴 )
		// + 웹 브라우저에 할당된 이름( Session ID )도 무효화시킨다.
		HttpSession session = req.getSession();
		String sessionId = session.getId();
		log.info("\t + sessionID : {}", sessionId);
		
		session.invalidate();
		log.info("\t + Current Session Scope Destroyed.");
		
		
		req.setAttribute(SharedScopeKeys.RESULT, "Signed Out Successfully");
		// + rttrs.addAttribute(key, value)와 동일한 기능을 한다.
		
		res.sendRedirect("/user/login");
		// + 최종적으로 로그인 화면 창으로 리다이렉트 수행
		// + Redirect할 경우에는 Session Scope이 파괴된다는 것을 잊지 말아야 한다.
		// + 그렇기에, 로드인 페이지에서는 Signed Out Successfully 메시지를 얻어낼 수 없게 된다.
		
		return false;
		// + 로그아웃 요청을 컨트롤러의 핸들러로 요청을 보내지 않는다. (***)
		// + 그러나, URI는 필요하기에 컨트롤러의 메소드를 삭제하면 인터셉터도 수행이 불가능하다.
		// + 로그인 컨트롤러의 로그아웃 메소드는 단순히 형식으로만 존재하게 된다.
		
	} // preHandle

} // end class

 

[ 1 - 8. 인터셉터 등록 ]

 

더보기

[ + 코드 보기 ]

	<!-- 인터셉터 등록 방법 (****) -->
	<interceptors>
    
		<interceptor>
			<mapping path="/user/loginPost" />
			<beans:ref bean="loginInterceptor" />
		</interceptor>
		
		<interceptor>
			<mapping path="/user/logout" />
			<beans:ref bean="logoutInterceptor" />
		</interceptor>
		
		<interceptor>
			<mapping path="/board/*" />
			<beans:ref bean="authInterceptor" />
		</interceptor>

	</interceptors>

 

 

 

 

728x90
댓글
«   2024/09   »
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
최근에 올라온 글
Total
Today
Yesterday
공지사항