본문으로 바로가기

로그인

쿠키와 세션을 이용하여 로그인을 한다. 로그인의 상태에 따라 화면단에 보여지는 것도 달리 할 것이다.



1) index.jsp

1
2
3
<li>
    <a href="/login">로그인</a>
<li>
cs



2) LoginCommand.java

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
31
32
33
34
35
package tody.lovely.util;
 
import javax.persistence.Entity;
import javax.validation.constraints.NotEmpty;
 
@Entity
public class LoginCommand {
    
    @NotEmpty(message="아이디를 입력해주세요.")
    private String id;
 
    @NotEmpty(message="비밀번호를 입력해주세요.")
    private String pw;
    private boolean rememberId;
    
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getPw() {
        return pw;
    }
    public void setPw(String pw) {
        this.pw = pw;
    }
    public boolean isRememberId() {
        return rememberId;
    }
    public void setRememberId(boolean rememberId) {
        this.rememberId = rememberId;
    }
 
}
cs

Hiberante를 썼다. 여기는 null만 검증하면 되는 거라서 hibernate를 썼다.



3) LoginController.java

1
2
3
4
5
6
7
8
9
10
11
12
    @RequestMapping(value="/login", method=RequestMethod.GET)
    public ModelAndView loginForm(LoginCommand loginCommand,
                    @CookieValue(value="REMEMBER", required=false) Cookie rememberCookie) throws Exception {
        
        if(rememberCookie!=null) {
            loginCommand.setId(rememberCookie.getValue());
            loginCommand.setRememberId(true);
        }
        
        ModelAndView mv = new ModelAndView("user/login/loginForm");
        return mv;
    }
cs

- 3행 : 아이디 기억을 위한 Cookie 부분이다.

- 5~8행 : Cookie가 있으면(아이디 기억을 했으면) Cookie에 있는 id와 rememberId의 값을 보낸다.



4) loginForm.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="panel-body">
   <form:form role="form" commandName="loginCommand" action="/login" method="post">
        <fieldset>
            <div class="form-group">
                <form:input type="text" class="form-control" placeholder="ID" path="id"/>
            </div>
            <div class="form-group">
                 <form:password class="form-control" placeholder="Password" path="pw"/>
            </div>
            <div class="checkbox">
                <label>
                    <form:checkbox path="rememberId"/>아이디 기억
                </label>
            </div>
                <button type="submit" class="btn btn-lg btn-success btn-block">로그인</button>
        </fieldset>
    </form:form>
</div>
cs



5) 확인

짠!



6) AuthInfo.java

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
31
32
33
34
35
package tody.lovely.util;
 
//회원 정보 세션 유지
public class AuthInfo {
    
    private String id;
    private String name;
    private int grade;
    
    public AuthInfo(String id, String name, int grade) {
        this.id = id;
        this.name = name;
        this.grade = grade;
    }
    
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getGrade() {
        return grade;
    }
    public void setGrade(int grade) {
        this.grade = grade;
    }
 
}
cs

회원 정보 세션을 유지해주는 기능을 할 것이다. UserVO를 그대로 사용하게 되면 보안성에 취약할 뿐만 아니라, 불필요한 정보까지 유지가 된다. 그래서 따로 세션 정보 유지를 위해 만들었다.

- 10~14행 : 로그인이 성공하면서, 저장된다.



7) UserVO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package tody.lovely.vo;
 
import java.util.Date;
 
public class UserVO {
    
    private int IDX;
    private String ID;
    private String EMAIL;
    private String NAME;
    private String PASSWORD;
    private int GRADE;
    private Date REGDATE;
        
    //비밀번호 확인
    public boolean matchPassword(String pw) {
        return this.PASSWORD.equals(pw);
    }
 
    /* Getter, Setter 생략 */
}
cs

- 16~18행 : 비밀번호 확인을 위해서 추가했다.



8) LoginController.java

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
31
32
33
34
35
    @Resource(name="userService")
    private UserService userSer;
 
    @RequestMapping(value="/login", method=RequestMethod.POST)
    public ModelAndView loginSuccess(@Valid LoginCommand loginCommand, BindingResult bindingResult,
                                    HttpSession session, HttpServletResponse response) throws Exception {
 
        if(bindingResult.hasErrors()) {
            ModelAndView mv = new ModelAndView("user/login/loginForm");
            return mv;
        }
        
        try {
            
            AuthInfo authInfo = userSer.loginAuth(loginCommand);
            session.setAttribute("authInfo", authInfo);
            
            Cookie rememberCookie = new Cookie("REMEMBER", loginCommand.getId());
            rememberCookie.setPath("/");
            if(loginCommand.isRememberId()) {
                rememberCookie.setMaxAge(60*60*24*7);
            } else {
                rememberCookie.setMaxAge(0);
            }
            response.addCookie(rememberCookie);
            
        } catch (IdPasswordNotMatchingException e) {
            bindingResult.rejectValue("pw""notMatch""아이디와 비밀번호가 맞지않습니다.");
            ModelAndView mv = new ModelAndView("user/login/loginForm");
            return mv;
        }
        
        ModelAndView mv = new ModelAndView("login/loginSuccess");
        return mv;
    }
cs

- 5행, 8~11행 : @Valid 어노테이션을 이용해 유효성검사를 한다.

- 15~16행 : 로그인에 성공하면 authInfo를 반환한다.

- 18행 : 쿠키를 생성하여 로그인 될때 생성된 id를 쿠키에 저장한다.

- 19행 : 쿠키를 찾을 경로를 변경해준다.

- 20~21행 : 아이디 기억을 체크했다면, 7일 정도의 유효시간을 정해준다. ( 60*60*24*30 : 30일 )

- 23행 : 아이디 기억을 체크하지 않았다면, 설정하지 않는다.

- 25행 : 쿠키를 적용해준다.

- 27~31행 : Service에서 exception을 한 부분이다. 아이디가 없거나, 비밀번호가 맞지않으면 에러를 보낸다.

- 33행: 로그인에 성공하면 이동할 페이지를 정할 수 있다. 메인 페이지로 보내고 싶다면 redirect:/ 로 설정하면 된다.



9) UserService.java

1
2
    AuthInfo loginAuth(LoginCommand loginCommand) throws Exception;
 
cs

Controller의 15행에 의해 만들어졌다.



10) UserServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    @Resource(name="userDAO")
    private UserDAO userDAO;
 
    @Override
    public AuthInfo loginAuth(LoginCommand loginCommand) throws Exception {
        UserVO user = userDAO.selectById(loginCommand.getId());
        if(user == null) {
            throw new IdPasswordNotMatchingException();
        }
        if(!user.matchPassword(loginCommand.getPw())) {
            throw new IdPasswordNotMatchingException();
        }
        return new AuthInfo(user.getID(), user.getNAME(), user.getGRADE());
    }
cs

- 8~9행 : 아이디가 있는 지 확인한다. 없으면 exception 처리한다.

- 10~12행 : 아이디와 비밀번호가 맞는 지 확인한다. UserVO에서 만들었다.

- 13행 : 아이디와 비밀번호가 확인되면, AuthInfo에 회원정보를 저장한다.



11) exception.java

1
2
3
4
5
package tody.lovely.exception;
 
public class IdPasswordNotMatchingException extends RuntimeException {
 
}
cs



12) UserDAO.java

1
2
3
4
5
6
7
8
@Repository("userDAO")
public class UserDAO extends AbstractDAO{
 
    public UserVO selectById(String id) {
        return (UserVO) selectOne("user.selectById", id);
    }
 
}
cs



13) user_sql.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="user">
    
    <select id="selectById" resultType="tody.lovely.vo.UserVO">
        <![CDATA[
          SELECT
              *
          FROM
              USER
          WHERE
              ID = #{id}       
      ]]>
    </select>
 
</mapper>
cs



14) loginForm.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="panel-body">
   <form:form role="form" commandName="loginCommand" action="/login" method="post">
        <fieldset>
            <div class="form-group">
                <form:input type="text" class="form-control" placeholder="ID" path="id"/>
                <form:errors path="id"/>
            </div>
            <div class="form-group">
                 <form:password class="form-control" placeholder="Password" path="pw"/>
                 <form:errors path="pw"/>
            </div>
            <div class="checkbox">
                <label>
                    <form:checkbox path="rememberId"/>아이디 기억
                </label>
            </div>
                <button type="submit" class="btn btn-lg btn-success btn-block">로그인</button>
        </fieldset>
    </form:form>
</div>
cs

- 06, 10행: 에러메세지를 보여주기 위해 추가한다.



15) loginSuccess.jsp

1
로그인 성공! ${authoInfo.name }님, 반갑습니다.
cs

controller에서 메인 페이지로 리다이렉트했다면 만들지 않아도 되는 페이지다. 각자 원하는 대로 꾸미면 된다.

${authInfo.name}은 세션에 넣어둔 정보를 가져와서 확인할 수 있다.



16) index.jsp

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
31
32
33
34
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 
<c:catch>
    <c:choose>
        <c:when test="${empty authInfo }">
            <li>
                 <a href="/login"><i class="fa fa-sign-in"></i> 로그인</a>
             </li>
             <li>
                 <a href="/register/step1"><i class="fa fa-user"></i> 회원가입</a>
             </li>
        </c:when>
        <c:otherwise>
            <c:choose>
                <c:when test="${authInfo.grade eq '1' }">
                    <li>
                       <p>관리자 ${authInfo.name }님, 환영합니다.</p>
                   </li>
                   <li>
                       <a href="/logout"><i class="fa fa-sign-out"></i> 로그아웃</a>
                   </li>
                </c:when>
                <c:otherwise>
                    <li>
                       <p>${authInfo.name }님, 반갑습니다!</p>
                   </li>
                   <li>
                       <a href="/logout"><i class="fa fa-sign-out"></i> 로그아웃</a>
                   </li>
                </c:otherwise>
            </c:choose>
        </c:otherwise>
    </c:choose>
</c:catch>
cs

- 5~12행 : 로그인을 하지 않아, 세션정보가 없다면 로그인/회원가입이 뜬다.

- 15~21행 : 로그인이 되어 세션정보가 있는데, grade가 1이면(관리자) 관리자 화면이 뜬다.

- 24~30행 : 로그인이 되어 세션정보가 있는데, grade가 1이 아니면 일반회원 화면이 뜬다.



17) 확인


17-1. 비밀번호나 아이디가 틀린 경우

에러 문구가 떴다.


17-2. 로그인 성공

authInfo 세션에 넣어둔 정보인 이름도 떴다.

그리고 메인 페이지의 수정으로 인해 로그인/회원가입도 로그아웃으로 바뀌었다.


로그인은 끝났다. 아이디 기억은 로그아웃을 완성하고 해보자.



로그아웃

로그아웃은 매우 쉽다. 그냥 세션만 끊어주면 되기 때문!


1) index.jsp

1
2
3
<li>
    <a href="/logout">로그아웃</a>
</li>
cs



2) LoginController.java

1
2
3
4
5
6
    @RequestMapping("/logout")
    public ModelAndView logout(HttpSession session) {
        session.invalidate();
        ModelAndView mv = new ModelAndView("redirect:/");
        return mv;
    }
cs

- 3행 : 세션 전체를 날려버린다.

- 4행 : 로그아웃 버튼을 누르면 바로 메인페이지로 리다이렉트한다.



3) 확인


3-1. 로그아웃을 누를 경우

세션이 모두 사라져서 다시 로그인/회원가입이 뜬다.



4) 아이디 기억 기능 확인

4-1. 아이디 기억을 하고 로그인 후 로그아웃하고 다시 로그인할 경우


아이디 기억을 누르고 로그인을 했다.

로그아웃 후 다시 로그인을 누르면, 아이디와 아이디기억부분의 체크가 자동으로 되어있다.


4-2. 아이디 기억을 하지않고 로그인 후 로그아웃하고 다시 로그인할 경우

아이디 기억을 하지 않고 로그인을 한다.

로그아웃 후 다시 로그인을 누르면 아이디 기억을 하지 않는 상태가 뜬다.


로그인, 로그아웃 끝!


2019.01.11

의외로 많은 분들이 이 글을 보시길래 깜짝 놀랐다. 통계보다가 놀랬다ㅎ 좀 빠진 부분이 있는 글이라서ㅠㅠ 그래서 나도 새로운 프로젝트에 그대로 복붙하면서 빠진 부분은 추가하고, 이상한 부분은 수정했다. 그대로 따라한다면 문제가 없을 것 같지만 그래도 생긴다면 ㅠㅠ.. 그리고 덧붙이자면 나는 이런 로그인, 로그아웃 보다는 Spring Security를 이용한 로그인, 로그아웃이 더 좋다고 생각한다. 보안적으로나.. 좀 더 있어 보이기도 하고.. 근데 스프링을 이제 배우는 단계라면 책에서도 이런 방법을 쓸 것이고, 굳이 보안까지 생각할 필욘 없다. 나중에 좀 더 스프링에 익숙해졌을 때 시큐리티를 이용해서 로그인, 로그아웃을 해보시길.. 그리고 제 블로그에 스프링 시큐리티를 이용한 로그인 로그아웃 글이 있으니 많이 놀러 와주세.. 읍읍