5분 안에 구축하는 로그인 서비스 - 쿠키&세션
로그인 서비스는 2가지 방식으로 구현이 가능하다
1. 쿠키&세션
2. JWT
해당 글에서는 1번 쿠키&세션을 이용한 방법으로 구성해본다!
위 두가지 방법에 대한 플로우는 아래 게시글에서 확인 가능하다
일단 컨트롤러를 보자
/***
* 로그인 페이지
* @param request
* @param model
* @return
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String main(HttpServletRequest request, Model model){
// 쿠키 만료시 Cookie 값이 null이 된다. (유효 시간 동안은 개발자 모드 진입 후(F12) 쿠키 보면 AUTH 라는 이름으로 세션 ID가 들어가 있음)
Cookie auth = WebUtils.getCookie(request, "AUTH");
// 로그인 정보가 있을시
if(!ObjectUtils.isEmpty(auth)){
if(StringUtils.equalsIgnoreCase(auth.getValue(), request.getSession().getId())){
String username = (String) request.getSession().getAttribute("username");
if(StringUtils.isNotEmpty(username)){
model.addAttribute("username", username);
return "success";
}
}
}
// 로그인 만료 or 비 로그인자 일시
return "login";
}
/***
* 로그인 요청
* @param member
* @param request
* @param response
* @param model
* @return
*/
@RequestMapping(value = "/", method = RequestMethod.POST)
public String login(Member member, HttpServletRequest request, HttpServletResponse response, Model model){
// 세션 저장 (세션 ID, 사용자 정보)
// 세션은 브라우저 당 1개 생성(시크릿 모드도 동일, 같은 브라우저에서 새탭 or 새창 띄워도 로그인 유지) / 쿠키는 시크릿 모드시 없어짐
request.getSession().setAttribute("username", member.getUsername());
// 쿠키 전달 (세션 ID)
response.addCookie(new Cookie("AUTH", request.getSession().getId()){{
setMaxAge(60); // 자동 로그인 10 초 유지
setPath("/");
}});
// 화면에 표시할 ID 셋팅
model.addAttribute("username", member.getUsername());
return "success";
}
/***
* 로그아웃 요청
* @param request
* @return
*/
@RequestMapping(value = "/logout")
public String logout(HttpServletRequest request){
// 세션 저장소 세션 제거
request.getSession().invalidate();
return "redirect:/";
}
로그인 '/' GET 은 초기 로그인 진입 화면이며, 아래 세가지를 확인하여 정상 로그인 여부를 확인 한다.
1. 저장한 쿠키인 AUTH 가 있는가?
2. 현재 세션값과 쿠키에 저장된 세션값 비교시 같은가?
3. 해당 세션에 저장된 사용자 정보(username)가 있는가?
정상 로그인 이라면 로그인 완료 화면인 success.html 로 넘기고 아니라고 하면 로그인 페이지인 login.html 로 넘긴다.
로그인 '/' POST는 로그인 요청이며, 아래 3가지 작업후 로그인 완료 화면으로 넘긴다.
1. 세션에 사용자 정보(username)를 저장한다. (저장소 : 서버)
2. 세션 ID 를 쿠키에 저장한다. (저장소 : 클라이언트=브라우저)
- maxage 는 쿠키 유지 시간으로 현재 60초 동안 쿠키가 유지되도록 설정하여 60초 동안은 로그인이 유지 된다.
- path 는 '/' 아래 경로에서 쿠키값을 유지한다는 의미
3. 사용자 정보(username)을 로그인 완료 화면에 보여주기 위해 해당 값을 model로 넘겨준다.
로그아웃 API '/logout' 는 해당 세션을 무효화 시킨다.
로그인 화면은 부트스트랩을 사용하여 나름 이쁘게(?) 구성한다.
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
로그인 폼은 아래와 같이 ID/PW를 받는다
<form action="/" method="post">
<div class="mb-3">
<label for="username" class="form-label">ID</label>
<input type="text" class="form-control" id="username" name="username">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password">
</div>
<button type="submit" class="btn btn-primary" id="loginBtn">Submit</button>
</form>
로그인 완료 화면은 세션에서 얻은 username 정보를 모델에 넣어 뿌린다
<div class="col-6 col-md-4">
<p th:text="${username}+'님 로그인 완료 !!!'"></p>
<form action="/logout">
<button type="submit" class="btn btn-primary" id="logoutBtn">LogOut</button>
</form>
</div>
완성 코드는 아래 깃에서 다운 받아보면 된다!
:: https://github.com/works-code/login-cookie-session
테스트를 해본다 !
http://localhost:8080/ 으로 로그인 페이지를 호출한다
hello 라는 ID로 로그인을 한다!
새탭을 켜서 로그인 페이지를 접근하여 로그인 유지가 되는지 확인 한다. (새탭을 켜도 같은 세션 아이디임을 확인 할 수 있다.)
새창을 켜도 로그인 유지가 될까? (새창을 켜도 같은 세션 아이디임을 확인 할 수 있다.)
다른 브라우저로 접속해볼까?(브라우저 당 1개의 세션이 생성되는것을 확인 할 수 있다.)
브라우저 별로 세션 ID가 달라서 다른 ID 로 로그인 유지 가능하다
로그인 후에 새창을 띄워 시크릿창을 켜보면 로그인이 풀려있는 것을 확인 할 수 있다.
- 로그 확인 해보면 세션 아이디를 새로 발급하는것을 확인 할 수 있다.
// 일반
2021-07-17 17:19:12.055 ERROR 34600 --- [nio-8080-exec-7] com.code.controller.LoginController : ### session ID => CF239613C901300405757CDCBFE7EC78
// 시크릿 창
2021-07-17 17:19:27.782 ERROR 34600 --- [nio-8080-exec-9] com.code.controller.LoginController : ### session ID => FF20C1742220BA8314E0A55CAC8939B8
로그아웃을 해볼까? 메인으로 돌아오면서 세션 ID가 새로 생성 된다.
- request.getSession() 시점에 유효한 세션이 있으면 해당 세션을 반환하고 없으면 새로 생성한다.
- 로그를 찍어보면 로그아웃(request.getSession().invalidate();) 을 하고 나서 세션 아이디를 보면 새로 생성된것을 확인 할 수 있다.
// 로그인 후 세션 아이디
2021-07-17 16:47:31.472 ERROR 33637 --- [nio-8080-exec-4] com.code.controller.LoginController : ### session ID => D81F996D7A7C2536E860DC473CF4004A
// 로그아웃 후 세션 아이디
2021-07-17 16:47:31.476 ERROR 33637 --- [nio-8080-exec-5] com.code.controller.LoginController : ### session ID => 2909BC4ED82A0FF91C110D309F9809B9
로그인 후 60초를 기다리면 로그인이 풀리는지 확인 한다.
- 개발자 모드(F12) 접속하여 쿠키 확인해보면 AUTH가 정상적으로 만료 시간 (60초)로 들어간것을 확인 할 수 있으며
- 60초 뒤에 확인해보면 AUTH 쿠키가 사라진 것을 볼 수 있다.
- 이때 로그아웃을 한게 아니라서 세션 ID는 같다! (쿠키만 지워진것)
2021-07-17 16:58:30.913 ERROR 33637 --- [nio-8080-exec-8] com.code.controller.LoginController : ### session ID => A51446D33A7F440F4A2E9148EDBCCA15
2021-07-17 17:00:05.595 ERROR 33637 --- [nio-8080-exec-1] com.code.controller.LoginController : ### session ID => A51446D33A7F440F4A2E9148EDBCCA15
- 쿠키 만료후 재 로그인시에 사용자 정보를 해당 세션에 넣을시 같은 키값에 데이터가 있다면 새로운 값으로 교체 된다.
request.getSession().setAttribute("username", member.getUsername());
정리하면 아래와 같다!
- 세션 ID 는 브라우저 별로 1개씩 생성하며, 시크릿 모드 접속시 쿠키 제거 되며 세션 ID를 새로 발급한다. (새창, 새탭 같은 세션 ID 사용)
- 로그아웃 시 세션 무효화 하면 (request.getSession().invalidate();) 세션 ID를 사용할 수 없게 되어 세션 ID를 새로 발급 한다.
- 쿠키 만료시 자동으로 로그인이 풀리며 이때 쿠키만 만료되어 사라진것이므로 세션 ID 는 동일하다. (이때 세션 ID 에 저장된 사용자 정보는 재 로그인시 overwrite 된다.)
다음 포스팅에서는 JWT 방식으로 구현 해볼 예정이다!
- 끝-