이번엔 로그인처리를 해보려고 한다.
로그인기능은 클라이언트가 아이디와 비밀번호를 입력하면,
아이디와 비밀번호가 맞는지 확인하고,
맞다면 세션쿠키를 부여해서 그 회원의 권한에 맞게 뷰페이지나 글작성 ,관리자페이지 등을
들어갈 수 있고, 조작할 수 있게 구분해주는 작업이라고 할 수 있겠다.
이번엔 아이디와 비밀번호가 맞는지 확인하고, 세션쿠키를 부여하는데 까지 해보려고 한다.
[스프링] Session과 Cookie , HttpSession :: 간편 웹프로그래밍 (tistory.com)
그 전에 세션과 쿠키, 세션을 부여해주는 HttpSession에 대해 정리해봤다.
public class LoginVO {
private String memberId;
private String memberPw;
private boolean useCookie;
//getter&setter생성
}
일단 로그인창에서 클라이언트가 입력한 ID와 Pw값을 받아올 VO를 생성해준다.
영속계층(DAO)
DAO
public MemberVO login(LoginVO loginVO)throws Exception;
DAOImpl
@Override
public MemberVO login(LoginVO loginVO)throws Exception{
System.out.println("DAOloginVO"+loginVO.getMemberPw());
return sqlsession.selectOne("memberMapper.login", loginVO);
}
서비스계층
Serivce
public MemberVO login(LoginVO loginVO)throws Exception;
ServiceImpl
@Override
public MemberVO login(LoginVO loginVO)throws Exception{
return memberDAO.login(loginVO);
}
Controller
@RequestMapping(value="/loginView",method= RequestMethod.GET)
public String loginView(@ModelAttribute("loginVO")LoginVO loginVO,HttpServletRequest request,Model model) throws Exception{
logger.info("loginView");
Map<String, ?> inputFlashMap = RequestContextUtils.getInputFlashMap(request);
if(null != inputFlashMap) {
model.addAttribute("msg",(String) inputFlashMap.get("msg"));
}
return "/member/loginView";
}
@RequestMapping(value="/login" , method= RequestMethod.POST)
public void loginPost(LoginVO loginVO, HttpSession httpSession,Model model)throws Exception{
logger.info("loginVO"+loginVO.getMemberId());
MemberVO memberVO = memberService.login(loginVO);
logger.info("Pw"+memberVO);
if( memberVO == null || !BCrypt.checkpw(loginVO.getMemberPw(), memberVO.getMemberPw())) {
return;
}
model.addAttribute("member", memberVO);
}
로그인페이지로 이동할 컨트롤러와, 로그인처리 로직의 파라미터를 주고받을 컨트롤러이다.
먼저, loginView의 InputFlashMap은 저번글에서 구현했던 회원가입 기능중, 회원가입완료 후 로그인페이지로
리다이렉트했을때 가입완료창을 띄우기위해 넣은 코드이다.
rttr.addFlashAttribute("msg", "가입이 완료되었습니다.");
return "redirect:/member/loginView";
//회원가입 컨트롤러의 일부
리다이렉트 addAttribute를 하면 url뒤에 파라미터가 추가되어 설정한 페이지로 이동하게 되는데,
addFlashAttribute는 url뒤에 파라미터가 붙지 않고, 일회성메세지로서 사라지게 된다.
어쨋든 url에 파라미터가 붙는 RedirectAttribute 인터페이스의 addAttribute기 때문에,
HttpServletRequest를 파라미터로 받으면 해당 값을 받을 수 있겠다.
RequestContextUtils의 스프링공식문서의 내용이다.
그 후 나머지로직은 간단하니 설명을 줄이겠다.
msg라는 key로 뷰에서 alert의 매개변수로 msg를 주면 가입이 완료되었다는 창이 뜬다.
이제 로그인 로직이다.
LoginVO로 로그인페이지에서 입력한 ID와 PW를 파라미터로 받아온다.
로그인 Mapper이다.
<select id="login" resultMap="MemberVOResultMap">
select * from mp_member where member_id = #{memberId}
</select>
<resultMap id="MemberVOResultMap" type="kr.co.vo.MemberVO">
<result property="memberId" column="MEMBER_ID"/>
<result property="memberPw" column="MEMBER_PW"/>
<result property="memberName" column="MEMBER_NAME"/>
<result property="memberEmail" column="MEMBER_EMAIL"/>
<result property="memberJoinDate" column="MEMBER_JOIN_DATE"/>
<result property="memberImg" column="MEMBER_IMG"/>
<result property="memberPoint" column="MEMBER_POINT"/>
</resultMap>
비밀번호는 암호화했기 때문에, ID로 계정값을 받아오는 쿼리를 짰다.
mybatis의 select문을 사용하면 항상 resultType을 설정해줘야 한다.
select문의 결과로 resultMap을 사용했는데, 원래 사용하던데로 resultType=[VO위치] 로 설정했으나
이상하게?? 값이 매칭이 안되서 resultMap으로 직접 매칭을 해줬다.
로그인처리가 안되길래, 컨트롤러 , 서비스 , DAO 전부 logger.info를 달아서 값을 확인해보니
로그인페이지에서 입력한 값이 잘 넘어오고 있었다.
조금 생각을 해보니 DB에는 member_id 이렇게 저장을 해놓고 , VO는 memeberId로 해놓은
기본적인 실수였다..
그래서 ResultMap을 이용해 매핑시켜줬다.
(컨트롤러)
어쨋든 loginVO를 매개변수로, 나온 결과값을 memberVO로 받는다.
null이거나, 입력한 비밀번호와 DB의 암호화로 저장된 비밀번호를 BCrypt.checkpw로 비교해서 맞지 않으면 리턴한다.
/member/login이 그대로 리턴될것이고,
WEB-INF/views/member폴더에 login.jsp를 만들어서 다시 로그인창으로 돌아가도록 만들었다.
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<script>
alert("아이디와 비밀번호를 확인해주세요.");
self.location = "/member/loginView";
</script>
</body>
</html>
문제가 없다면 member라는 키로 로그인Id에 맞는 정보들을 memberVO에 담는다.
여기서 인터셉터를 사용해서 로그인처리를 하려고 한다(세션부여)
물론 컨트롤러에서 ID와 PW를 확인 후 , 세션부여를 해도 되지만
컨트롤러에선 파라미터와 관련된 처리를 하고, 인터셉터에서 세션과 관련된 처리를 하도록 나누어봤다.
본인이 편한대로 하면 될 것 같다.
src/main/java 아래에 kr.co.commons.interceptor패키지에 LoginInterceptor.java 클래스를 생성했다.
package kr.co.commons.interceptor;
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.ui.ModelMap;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class LoginInterceptor extends HandlerInterceptorAdapter {
private static final String LOGIN = "login";
private static final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("interceptor postHandel");
HttpSession httpSession = request.getSession();
ModelMap modelMap = modelAndView.getModelMap();
Object memberVO = modelMap.get("member");
if (memberVO != null) {
logger.info("new login success");
httpSession.setAttribute(LOGIN, memberVO);
response.sendRedirect("/board/list");
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession httpSession = request.getSession();
// 기존의 로그인 정보 제거
if (httpSession.getAttribute(LOGIN) != null) {
logger.info("clear login data before");
httpSession.removeAttribute(LOGIN);
}
return true;
}
}
제일 먼저 extends 되있는 HandlerInterceptorAdapter란?
*****
PreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- 컨트롤러(즉 RequestMapping이 선언된 메서드 진입) 실행 직전에 동작.
- 반환 값이 true일 경우 정상적으로 진행이 되고, false일 경우 실행이 멈춥니다.(컨트롤러 진입을 하지 않음)
- 전달인자 중 Object handler는 핸들러 매핑이 찾은 컨트롤러 클래스 객체입니다.
- PostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
- 컨트롤러 진입 후 view가 랜더링 되기 전 수행이 됩니다.
- 전달인자의 modelAndView을 통해 화면 단에 들어가는 데이터 등의 조작이 가능합니다.
- afterComplete(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
- 컨트롤러 진입 후 view가 정상적으로 랜더링 된 후 제일 마지막에 실행이 되는 메서드입니다.
- afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
- Servlet 3.0부터 비동기 요청이 가능해짐에 따라 비동기 요청 시 PostHandle와 afterCompletion메서드를 수행하지 않고 이 메서드를 수행하게 됩니다. (Spring에서 제공함)
인터셉터는 DispatcherServlet이 컨트롤러를 호출하기 전이나 후에 응답을 가공할 수 있는 필터 역할을 할 수 있다.
인터셉터와 필터의 차이는 여기서 따로 설명하진 않겠다.
핸들러 인터셉터를 등록하지 않았다면 바로 컨트롤러에 진입하지만 하나이상의 핸들러 인터셉터를 지정했을 때는 순서에 따라 인터셉터를 먼저 거친후에 컨트롤러를 호출한다.
코드로 돌아와서,
HttpSession httpSession = request.getSession();
-request의 getSession 메서드는 서버에 생성된 세션이 있다면 반환하고, 없다면 새 세션을 반환해준다.
ModelMap modelMap = modelAndView.getModelMap();
Object memberVO = modelMap.get("member");
-modelAndView의 getModelMap의 공식문서 내용이다
기본인스턴스를 반환하는 ModelAndView의 메서드이다.
ModelAndView를 매개변수로 받았다. 이것은 무엇일까.
posetHandle은 컨트롤러의 처리가 끝난 후 개입되는 인터셉터이다.
/login 컨트롤러에서, 마지막줄에 model.addAttribute로 member라는 key에 memberVO의 데이터를 담았다.
@Controller 어노테이션으로 사용한
Controller 인스턴스는, 필요한 비즈니스로직을 호출하여 처리 결과와(모델) 이동할 View의 정보를 DispatherServlet에 반환한다. 이 정보들은 스프링MVC가 제공하는 ModelAndView인스턴스로 취급된다.
인터셉터는 디스패쳐와 컨트롤러의 사이에서 개입한다.
그래서 컨트롤러에서 model로 담은 member의 정보를 ,
매개변수로 ModelAndView를 받은 postHandle 인터셉터에서 받을 수 있는 것이다.
그 인스턴스값를 ModelMap에 담는다.
Model과 ModelMap은 인터페이스이냐, 클래스이냐의 차이로 구분할 수 있겠다.
ModelMap.get("member") member의 키에 담긴 정보를 memberVO에 담아준다.
그 후 memberVO가 null값이 아니면,
로그를 출력하고 LOGIN이라는 key에 memberVO의 정보들을 담아서 세션값에 넣어준다.
마지막 response.sendRedirect는 지정한 url로 클라이언트의 브라우저를 리다이렉트 시킨다.
스프링의 redirect와 동일한 기능이지만, 인터셉터에선 해당메소드를 반드시 사용해야 리다이렉트 된다.
이제 인터셉터를 모두 구현했으니, 스프링이 인터셉터로 인식할 수 있도록 servlet.xml에 코드를 작성해주자.
web.xml에 DIspatcherservlet의 위치로 지정된 xml에 작성해주면 된다.
(WEB-INF - spring - appServlet - servlet-context.xml)
<beans:bean id="loginInterceptor" class="kr.co.commons.interceptor.LoginInterceptor"/>
<interceptors>
<interceptor>
<mapping path="/member/login"/>
<beans:ref bean="loginInterceptor"/>
</interceptor>
</interceptors>
뷰의 일부분 ( 상단 include 부분)
<c:if test="${not empty login}">
<li class="dropdown user user-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<img src="/${login.memberImg}" class="user-image" alt="User Image">
<span class="hidden-xs">${login.memberName}</span>
</a>
<ul class="dropdown-menu">
<li class="user-header">
<img src="/${login.memberImg}" class="img-circle" alt="User Image">
<p>${login.memberName}
<small>
가입일자 : <fmt:formatDate value="${login.memberJoinDate}" pattern="yyyy-MM-dd"/>
</small>
</p>
</li>
<li class="user-body">
<div class="row">
<div class="col-xs-4 text-center">
<a href="#">게시글</a>
</div>
<div class="col-xs-4 text-center">
<a href="#">추천글</a>
</div>
<div class="col-xs-4 text-center">
<a href="#">북마크</a>
</div>
</div>
</li>
<li class="user-footer">
<div class="pull-left">
<a href="${path}/member/info" class="btn btn-default btn-flat"><i
class="fa fa-info-circle"></i><b> 내 프로필</b></a>
</div>
<div class="pull-right">
<a href="${path}/member/logout" class="btn btn-default btn-flat"><i
class="glyphicon glyphicon-log-out"></i><b> 로그아웃</b></a>
</div>
</li>
</ul>
</li>
</c:if>
<c:if test="${empty login}">
<li>
<a href="/member/loginView" class="btn btn-primary btn-icon-split mb-3 mr-3" style="width:auto; height:50px; margin-top:10px; margin-right:5px; text-align:center;">
<span class="icon text-white-50">
<i class="fas fa-flag"></i>
</span>
<span class="text">로그인</span>
</a>
<a href="/member/registerView" class="btn btn-success btn-icon-split mb-3 mr-3" style="width:auto; height:50px; margin-top:10px;">
<span class="icon text-white-50">
<i class="fas fa-check"></i>
</span>
<span class="text">회원가입</span>
</a>
</li>
</c:if>
세션쿠키의 login이라는 key에 담긴 select문의 결과를 이용해,
로그인시와 비로그인시, 로그인했을때 계정에 관련된 정보를 표현할 수 있다.
대충 해놓은 코드인데, 일단 이런식으로 사용한다는 것을 표현하고 많이 손 볼 예정이다.
결과
두번째 사진 (로그인) 시 JSESSIONID 라는 세션쿠키가 생성된 것을 볼 수 있다.
'SPRING > IceWater Community' 카테고리의 다른 글
[스프링] 자동로그인 기능 구현 - 로그인4 (0) | 2021.09.17 |
---|---|
[스프링]로그인 권한설정과(인터셉터) 로그인,로그아웃 전 페이지 기억 기능 - 로그인 3 (1) | 2021.09.16 |
[스프링]회원가입(ajax 유효성 검사,비밀번호 암호화) - 로그인1 (0) | 2021.09.10 |
[스프링]게시판 조회수,댓글수 순 정렬과 게시글 10개,20개씩 보기 구현 (3) | 2021.09.07 |
[스프링] 다중 게시판 구현 (CRUD) (1) | 2021.09.06 |