4/20/2018

(구버전) 스프링 게시판 만들기 #13. 로그인 기능 구현

'(구버전) 스프링 게시판 만들기'는 내용이 부족하다고 판단하여
스프링 게시판 만들기를 새로 작성하였습니다.

링크 및 참조용으로 현재 게시물은 남겨두겠지만,
가급적이면 새로운 스프링 게시판 만들기를 참조해주시기 바랍니다.

회원가입 기능이 완성되었으니, 로그인 기능도 추가합니다. 회원가입이 게시물 작성이라면, 로그인은 게시물 조회에 해당됩니다. 회원 아이디(게시물 제목)과 비밀번호(게시물 번호)가 동일한 정보를 가져온다고 생각하시면 됩니다.

현재의 회원정보입니다. 아이디가 kuzuro이며, 비밀번호가 1111로 되어있습니다.

이 회원의 닉네임(userName)을 가져오기 위해선 2가지의 조건이 필요하며, 그 두가지는 아이디(userId)와 비밀번호(userPass)입니다.

select문에서 조건 2개를 넣어서 정상적으로 원하는 결과가 나오는지 확인합니다.

매퍼에 쿼리문을 추가합니다.

<!-- 로그인 -->
<select id="login" resultType="com.kuzuro.domain.MemberVO">
 select
     userId, userName
 from
     myMember
 where userId = #{userId}
     and userPass = #{userPass}
</select>

DAO와 Service에 로그인 관련 메서드를 작성합니다.

컨트롤러에도 관련된 메서드를 작성합니다.

// 로그인
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(MemberVO vo, HttpServletRequest req) throws Exception {
 logger.info("post login");
 
 HttpSession session = req.getSession();
 
 MemberVO login = service.login(vo);
 
 if(login == null) {
  session.setAttribute("member", null);  
 } else {
  session.setAttribute("member", login);
 }
   
 return "redirect:/";
}

로그인이 실패하면 어떠한 값도 넘어오지 않으니 null이 되고, 성공하면 매퍼에 있는 쿼리문에 대한 결과가 넘어오게 됩니다.

조건문을 이용해 이 값이 있는지 없는지를 확인하고, 이에 맞게 세션 member에 값을 넣어줍니다. member에는 매퍼에 있는 쿼리문의 결과가 들어가 있습니다.

form에는 action="/member/login" 을 추가해서 post되는 주소를 정해줍니다. 하단에는 ${member.userName} 를 추가해서, 세션에 있는 member의 값을 출력합니다.

아직 로그인이 안되었기 때문에 세션 member의 값이 없으므로, 아무런 표시가 되지 않습니다.

올바른 아이디와 비밀번호를 입력하고 로그인 버튼을 클릭하면

member의 값이 잘 나옵니다.

세션만 보면 로그인이 된건데, 아직도 로그인 폼이 그대로 있는건 이상하니 수정합니다.

로그인 폼을 감싸는 조건문을 작성합니다.

member의 값이 null이라면 로그인이 되지 않았으니 로그인폼을 보여주고, member의 값이 null이 아니라면 로그인이 된것이니 로그인폼은 숨기고 텍스트를 보여줍니다.

로그인하지 않은 상태에서는 하단 텍스트가 보이지 않습니다.

로그인을 하면, 로그인 폼은 없어지고 텍스트만 표시됩니다.

이제 올바르지않은 아이디와 패스워드를 입력하여 로그인을 시도하면

아무런 반응이 없습니다. 로그인 정보가 틀려서 로그인이 실패했다면, 실패했다는 메시지라도 나와야하는데 아무것도 없습니다.

컨트롤러의 로그인 메서드에서, login값이 null인 경우에 값을 추가합니다.

rttr.addFlashAttribute("msg", false);

조건문에 의해 login이 값이 null이라면, msg라는 정보에 false라는 값이 들어가서 전송됩니다. 이 값은 다른 페이지로 이동하거나 새로고침을 하면 없어지는 일회용값입니다.

그리고 jsp에는 로그인이 실패했을 경우 표시될 텍스트를 입력합니다. 조건문을 사용하여 msg의 값이 false라면 표시하고, 아니라면 표시하지 않습니다.

잘못된 아이디와 패스워드를 입력하면, 이렇게 텍스트가 표시됩니다.

게시물 작성을 위한 write.jsp로 이동하여, 작성자 부분을 수정합니다.

로그인한 회원의 닉네임을 표시하고, 수정할 수 없도록 읽기전용(readonly)속성을 부여합니다. 이처럼 세션은 다른 메서드에 정보를 넘겨주지 않아도 그 값을 사용할 수 있습니다.

로그인한 상태에서 게시물 작성 페이지로 오면, 작성자 부분에 자동으로 값이 들어가 있습니다.

로그인하지 않았다면 작성자 부분은 비어있는 상태가 되고, 비어있다면 값이 null이 됩니다. 하지만 작성자의 값이 null이라면 에러가 발생하게 됩니다.

에러가 발생하지 않도록 중간에서 가로채서(interceptor)하는 방법과 화면에 직접 표시해서 이동하는 방법이 있는데, 후자로 진행하겠습니다.

컨트롤러에서 세션값을 가져와서 로그인여부를 확인합니다.

Object loginInfo = session.getAttribute("member");

if(loginInfo == null) {
 model.addAttribute("msg", false);
}

세션의 타입은 오브젝트이므로, 변수 loginInfo의 선언은 오브젝트(Object)로 해야합니다. loginInfo가 null이면 로그인 상태가 아니므로, msg에 false값을 넣어주게됩니다.

그후 msg에 대한 조건문을 넣어서 표시되는 화면을 제어합니다.

로그인하지 않은 상태에서 게시물 작성 페이지로 이동하면, 이렇게 텍스트만 표시됩니다.

로그인을 한 상태에서 게시물 작성 페이지로 이동하면 텍스트는 표시되지 않고, 작성자 부분에 로그인한 회원의 닉네임이 자동으로 입력됩니다.

지금까지 false(부정)만 사용했지만, 이렇게 문자열을 넣어서 사용할수도 있습니다.

문자열인 경우 작은 따옴표를 이용합니다.

로그인을 할 수 있다면, 당연히 로그아웃도 할 수 있어야합니다.

로그인했을 때 보여질 부분에, 로그아웃을 위한 링크를 추가합니다.

로그아웃은 데이터베이스에 접근할 필요가 없으며, 현재 가진 세션값을 제거해주기만 하면 되기 때문에 컨트롤러에서 작업이 끝납니다.

// 로그아웃
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpSession session) throws Exception {
 logger.info("get logout");
 
 session.invalidate();
   
 return "redirect:/";
}

로그인한 상태에서 로그아웃 링크가 보이고, 로그아웃 링크를 클릭하면

로그아웃이 됩니다.

게시물 수정
  1. 로그인 기능에서 세션 입력을 컨트롤러에 위치시키셨는데, 세션을 서비스에 위치시키지 않고 왜 컨트롤러에 위치시키는지 이유가 궁금합니다. 컨트롤러를 클라이언트로받은 요청을 서비스로 전달하고 로직이 수행된 뒤의 결과를 표시해줄 view와 연결시켜 주는 기능 정도로만 생각하고 있었는데, 서비스에서 구현하면 안 되는 걸까요?

    답글삭제
    답글
    1. 안녕하세요?

      말씀하신대로 구현하는 방법이 가장 좋은 방법이라고 생각합니다.
      DAO는 데이터베이스와의 연결만 담당하고
      Service는 비지니스.. 그러니까 잡다한(?) 업무를하고
      Controller는 Service와 View를 이어주는 다리 역할과 일부 데이터를 가공하는 역할을 하는거죠.

      그러므로, 말씀하신대로 로그인에 대한 처리는 서비스에서 해결하는게 올바른 방법이며, 저도 서비스에서 구현하는 방식을 사용합니다.


      그럼에도 본문에서 컨트롤러에 구현하게 된 이유는... 분량 조절에 실패한데다 게시물을 미완성인채로 마무리지어버린 이유가 가장 큽니다-_-;

      원래라면 컨트롤러에 집중되어있는 로그인을 포함한 여러가지 코드들을, 각각의 역할에 맞도록 서비스로 밀어내면서 그 이유를 언급하려고 했는데, 그러기 전에 그냥 얼버무리듯 마무리 지었거든요.

      처음부터 서비스에 작성하지 않고 컨트롤러에 코드가 집중되어있는 이유는, 독학으로 JSP와 스프링을 공부하면서 제대로 이해를 못한 상태에서 하다보니 자연스럽게(?) 컨트롤러에 코드가 집중되는 버릇이 생겼었습니다.

      이 게시물을 쓰기 한참전에 그 버릇은 고쳤지만, 그땐 그랬었는데~ 하면서 혼자 킥킥대며 작성하다가 도중에 미완성으로 마무리 지은채 끝나버려서 이렇게 되었네요.

      하하-_-;

      삭제
    2. 그런 어른들의 사정(!)이 있었군요. 코딩 베껴 쓰기 하며 예제 완주 한 번 했을 때는 잘 안 보였는데, 제 프로젝트 시작하면서 다시 보니까 저 부분이 궁금하더라고요. 답변해주셔서 궁금증이 풀렸습니다! 감사합니다! 좋은 예제 올려주셔서 감사하다는 말씀도 드립니다! 계속 읽으면서 참고하고 있어요. 좋은 밤, 좋은 주말 되시길! ^^

      삭제
  2. 익명8/22/2019

    안녕하세요 이 게시글 보면서 공부하고있습니다.
    궁금한게 Mapper에서는 분명 데이터베이스와 직접관련된(같은) 문장들을 넣어주고있는데
    DAO와 Service의 정확한 역할은 무엇인가요?
    Mapper와 어떤 관련이 있는지 설명좀 부탁드리겠습니다 ㅠㅠ
    스프링이 처음이라

    답글삭제
    답글
    1. Controller는 view(화면)과 Service를 연결해줍니다. 연결해주면서 필요한 경우 데이터의 가공도 합니다.
      Service는 실제로 작업이 이루어지는 역할이며, Controller와 DAO를 연결해줍니다.
      DAO는 데이터 베이스에 접속하는 역할입니다.

      View는 실제로 식사를 하는 식탁
      Controller는 주방에서 식탁으로 음식을 가져가는 서빙 직원
      Service는 실제로 조리하는 조리실
      DAO는 재료를 냉장고에서 조리실로 가져오는 과정
      DB는 냉장고입니다.

      DAO는 1개의 메서드당 1개의 데이터 베이스 접속을 기본으로하며, 이렇게하는 이유는 유지/보수를 쉽게 하기 위함입니다. DAO없이 Service에서 DB접속을 한다면, Service의 메서드 하나에서 다수의 DB접속이 이루어질텐데, 나중에 DB를 추가/변경/삭제할 경우 일일이 찾아서 변경해야하므로, DB의 각 테이블(또는 역할)에 맞게 DAO를 분류시켜둔겁니다. 결론적으론 작성/유지/보수를 명확하고 용이하게 하기 위함이라 보시면 됩니다.

      Mapper는 DAO에 쿼리를 그대로 넣어도 무관하나, 위처럼 작성/유지/보수를 명확하고 용이하게 하기 위함입니다.

      삭제
  3. 잘보고 공부하고있어요. 질문이 있는데 write.jsp부분을 수정할때 c:if로 감싸는 부분있잖아요. 이부분을 하다가 제가 name=writer 이것을 name=write로 오타 치니까 null값이면 안된다고 에러났구 고쳐서 된건 알겠는데 이것이 게시판db의 writer더라구요. 이게 어떻게 연계가 된건지 궁금합니다!

    답글삭제
    답글
    1. 안녕하세요? 특별히 연계가 된다기보다, 테이블의 컬럼명과 VO의 변수명을 동일하게 사용하고 있어서 그런겁니다.

      물론 테이블의 컬럼명과 VO의 변수명을 다르게 사용해도 무관합니다.(이때 약간의 추가 작업이 필요합니다)
      테이블 컬럼명과 VO 변수명이 같을 때 생기는 이점은, 지나가는 사람이 봐도 이게 어느 테이블의 어느 컬럼에서 오는건지 알 수 있기 때문에 접근성이 좋다는거지요.

      삭제
  4. 안녕하세요 스프링을 공부한 지 얼마 되지 않아서 궁금한 게 있는데요
    보통 폼에서 전송하는 아이디 값과 패스워드값을 memberVO.setMemberId(request.getParameter("memberId")); 뭐 이런 식으로 빈즈에 파라미터 값을 받지 않아도 form에서의 아이디값과 패스워드의 파라미터값을 알아서 가져오는 건가요??????

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      폼에서 전송한 데이터를 컨트롤러가 받을 때, 폼에서 전송한 데이터 타입과 컨트롤러가 받는 데이터 타입이 일치하는 경우 자동으로 입력이 됩니다.

      반대로 폼에서 전송한 데이터 타입과 컨트롤러가 받는 데이터 타입이 일치하지 않는 경우, 하나하나 매칭시켜주어야합니다.

      즉, 데이터 타입이 일치할 경우 말씀하신대로 알아서 가져올 수 있습니다.

      삭제
  5. home.jsp 부분에 member가 자꾸 null이 되서 로그인이 되지가 않네요.

    몇번을 봐도 똑같이 했는데 자꾸 로그인이 되지가 않아서 실례를 무릅쓰고 여쭤봅니다..

    (아마 그래도 제가 틀려서 그렇겠지요..)

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      컨트롤러에서 세션에 데이터를 넣었을 때, 정상적으로 넣어졌는지 확인해보시고(디버그, 콘솔 출력등을 이용)
      정상적으로 넣었는데도 null이 나온다면... jsp파일 상단에 session=false 가 있을테니 이걸 지워주시면 될 것 같습니다.

      삭제
    2. 헐 진짜네요! 너무 감사합니다!!

      삭제
    3. 쓰발.. 3시간걸렸네요 ㅠㅠ 이게 답이었구나.... ㅠㅠㅠㅠㅠ

      삭제
  6. 익명3/12/2020

    저는 왜 404페이지가 나오는지 모르겠어요, 그리고 작성자님께서는 아무 페이지도 없는 logalhost:8080에 들어가도 404가 안뜨시나요? 저는 homecontorller를 지운 상태이고 /login 이나 /home을 해도 계속 404페이지만 뜹니다. 왜그럴까요..

    답글삭제
  7. WARN : org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/%3Cc:url%20value=] in DispatcherServlet with name 'appServlet'
    WARN : org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/%3Cc:url%20value=] in DispatcherServlet with name 'appServlet'
    INFO : com.board.controller.MemberController - get join
    WARN : org.springframework.web.servlet.PageNotFound - Request method 'GET' not supported

    컨트롤러 작성후 실행하니 이런 오류가 발생합니다.. 해결법이 있을가요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      에러 메시지를 보시면, 매핑된 url이 없다는걸 볼 수 있습니다.
      주소가 매핑된 컨트롤러가 없거나, 컨트롤러 어노테이션이 없거나, 매개변수 null 처리를 안 한 상태에서 null이 들어온 경우로 보입니다.

      삭제
    2. 에고... ㅠㅠ 2틀동안 고칠려고 노력해봣는데 아무리해도 안되네요... 혹시.. 실례가 안된다면 코드 보내드리면 봐주실수 잇을가요 ㅠㅠ

      삭제
    3. 안녕하세요? 방문해주셔서 감사합니다.

      늦게 확인해서 죄송합니다.

      해당 부분의 코드가 따로 있는게 아니고... 컨트롤러의 어노테이션이 빠져있거나
      프론트에서 요청한 url이 컨트롤러에 없는것 같습니다.

      삭제
  8. 작성자 이름을 직접 작성하게 되있는데,

    작성자 이름이 자동으로 입력 되게 하려면
    쿼리를 어떻게 참조해야 하나요 ?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      로그인하면 작성자 이름이 자동으로 입력되도록 하는게 본문의 내용입니다

      삭제
  9. 익명6/30/2021

    안녕하세요 보면서 공부하고 있는 초보입니다. 제가 궁금한 점은 매퍼인데요 매퍼 쿼리문에 유저아이디 비밀번호를 입력한 값으로 전송해야하니까 파라미터 타입이 필요하다고 생각했는데 파라미터 타입이 없어도 되는 이유를 알 수 있을까요??

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      말씀하신대로 파라미터 타입을 넣어주는게 가장 올바른 방법입니다. 왜냐하면, 저 코드들은 어찌되었든 사람이 보는거니까요.

      그럼에도 생략을 해도 가능한 이유는... 매퍼에 들어오는 데이터와 매퍼 내부에서 사용하는 변수명이 같다면 스프링이 자동으로 매칭시켜서 넣어주게됩니다.

      이것 때문에 파라미터 타입을 넣지 않아도 사용이 가능하지만, 지나가는 다른 개발자가 보면 혼동할 수 있으니 명시..그러니까 파라미터 타입을 넣어주는게 가장 바람직합니다.

      삭제
  10. 안녕하세여
    컨트롤단 session member 값을 넣어주고 jsp P 태그로 $ 이렇게하는데 로그인은 성공은하는데 출력이 안되는데 혹시 왜그런지 여쭤봐두될까요 ....

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      게시물 본문 처럼 ${member.name}로 출력이 안된다면...
      가장 의심(?)되는걸로는, jsp파일 최상단에 session을 false 시키는 코드가 있는지 확인해보세요.

      이클립스(=sts)에서 jsp파일을 생성하면 기본값으로 새션을 사용하지 않도록 되어있는 경우가 있더군요.

      삭제