Spring Login#1 ์ฟ ํค์ ์ธ์
by JiwonDev๋ก๊ทธ์ธ (LoginForm.html)์์ ID,PW๋ฅผ ๋ฐ์์ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ฉด ์ฟ ํค๋ฅผ ์ ์กํ๋ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํํด๋ณด์.
์ฟ ํค๋ฅผ ์ ์กํ๋ ๊ณผ์ ์ ๋งค์ฐ ๊ฐ๋จํ๋ค. ํต์ฌ์ ์ฟ ํค๋ฅผ ์ด์ฉํด์ '์ด๋ป๊ฒ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์งํ ๊ฒ์ธ๊ฐ?' ์ด๋ค.
๊ณผ๊ฑฐ์ ์น ๋ธ๋ผ์ฐ์ ๋ ์ฟ ํค์ ์ธ์ ์ ์ฌ์ฉํ๋๋ฐ, ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์ฒ์๋ถํฐ ๋ฐฐ์๋ณด์.
# ๋ก๊ทธ์ธ ์๊ตฌ์ฌํญ
- ํ ํ๋ฉด - ๋ก๊ทธ์ธ ์
โ ํ์ ๊ฐ์
โ ๋ก๊ทธ์ธ - ํ ํ๋ฉด - ๋ก๊ทธ์ธ ํ
โ ๋ณธ์ธ ์ด๋ฆ(๋๊ตฌ๋ ํ์ํฉ๋๋ค.)
โ ์ํ ๊ด๋ฆฌ
โ ๋ก๊ทธ ์์ - ๋ณด์ ์๊ตฌ์ฌํญ
โ ๋ก๊ทธ์ธ ์ฌ์ฉ์๋ง ์ํ์ ์ ๊ทผํ๊ณ , ๊ด๋ฆฌํ ์ ์์
โ ๋ก๊ทธ์ธ ํ์ง ์์ ์ฌ์ฉ์๊ฐ ์ํ ๊ด๋ฆฌ์ ์ ๊ทผํ๋ฉด ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ์ด๋
โ ํ์ ๊ฐ์ , ์ํ ๊ด๋ฆฌ

@ ํจํค์ง ๊ตฌ์กฐ
๋๋ฉ์ธ์ ๋น์ฆ๋์ค ๋ก์ง๋ง ์กด์ฌํด์ผ ํ๋ค. ์น๊ณผ ๊ด๋ จ๋ ์ฝ๋๋ฅผ ์ฌ์ฉ, ์์กดํ์ง ์๋๋ก ๋ง๋ค์.
- ๐ Domain = (ํ๋ฉด, UI, ๊ธฐ์ ์ธํ๋ผ๊ฐ ์๋) ํต์ฌ ๋น์ฆ๋์ค ์์ญ
- ๐ Web = ์น๊ณผ ๊ด๋ จ๋ ์์ญ

@ ๋๋ฉ์ธ ๋ง๋ค๊ธฐ
- ํ๋ฒ ๋ ๋งํ์ง๋ง, ๋๋ฉ์ธ ๊ฐ์ฒด์๋ ์น๊ณผ ๊ด๋ จ๋ ์ฝ๋(ItemUpdateForm๋ฑ)๊ฐ ๋ค์ด๊ฐ๋ฉด ์๋๋ค.
๐Member ๐MemberRepository โก DB ์ฝ๋๋ ๊ฐ๋จํ static HashMap<>์ผ๋ก ๋์ฒดํ์๋ค.
@Data public class Member { private Long id; @NotEmpty private String loginId; //๋ก๊ทธ์ธ ID @NotEmpty private String name; //์ฌ์ฉ์ ์ด๋ฆ @NotEmpty private String password; }
@Slf4j @Repository public class MemberRepository { private static Map<Long, Member> store = new HashMap<>(); //static ์ฌ์ฉ private static long sequence = 0L;//static ์ฌ์ฉ public Member save(Member member) { member.setId(++sequence); log.info("save: member={}", member); store.put(member.getId(), member); return member; } public Member findById(Long id) { return store.get(id); } public Optional<Member> findByLoginId(String loginId) { return findAll().stream() .filter(m -> m.getLoginId().equals(loginId)) .findFirst(); } public List<Member> findAll() { return new ArrayList<>(store.values()); } public void clearStore() { store.clear(); } }
๐Item ๐ItemRepository โก DB ์ฝ๋๋ ๊ฐ๋จํ static HashMap<>์ผ๋ก ๋์ฒดํ์๋ค.
@Data public class Item { private Long id; private String itemName; private Integer price; private Integer quantity; public Item() { } }
@Repository public class ItemRepository { private static final Map<Long, Item> store = new HashMap<>(); //static private static long sequence = 0L; //static public Item save(Item item) { item.setId(++sequence); store.put(item.getId(), item); return item; } public Item findById(Long id) { return store.get(id); } public List<Item> findAll() { return new ArrayList<>(store.values()); } public void update(Long itemId, Item updateParam) { Item findItem = findById(itemId); findItem.setItemName(updateParam.getItemName()); findItem.setPrice(updateParam.getPrice()); findItem.setQuantity(updateParam.getQuantity()); } public void clearStore() { store.clear(); } }
๐LoginService โก ๋ก๊ทธ์ธ ์์ด๋๋ฅผ ์ฐพ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋น๊ตํ๋ค. ์์ด๋๊ฐ ์๋ค๋ฉด null์ ๋ฐํํ๋ค.
@Service @RequiredArgsConstructor public class LoginService { private final MemberRepository memberRepository; /** * @return null ๋ก๊ทธ์ธ ์คํจ */ public Member login(String loginId, String password) { return memberRepository.findByLoginId(loginId) .filter(m -> m.getPassword().equals(password)) .orElse(null); } }
@ ๋ก๊ทธ์ธ ํ์ด์ง, Form ๊ฐ์ฒด, ์ปจํธ๋กค๋ฌ
๐จloginForm.html โก ํ์๋ฆฌํ ์ฌ์ฉ. ์คํ์ผ ๊ด๋ จ ์ฝ๋๋ ์๋ต

<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <!-- ๋ถํธ์คํธ๋ฉ์ ๋ค์ด๋ฐ์ ์ฌ์ฉ --> <link href="../css/bootstrap.min.css" rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> <style> .container { max-width: 560px; } .field-error { border-color: #dc3545; color: #dc3545; } </style> </head>
<body> <div class="container"> <div class="py-5 text-center"> <h2>๋ก๊ทธ์ธ</h2> </div> <form action="item.html" method="post" th:action th:object="${loginForm}"> <!-- ๊ธ๋ก๋ฒ ์๋ฌ (๊ฐ์ฒด์์ ๋ฐ์ํ ์๋ฌ)๋ ์กฐ๊ฑด์ผ๋ก ํ์ธํ๋ค. --> <div th:if="${#fields.hasGlobalErrors()}"> <p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}"> ์ ์ฒด ์ค๋ฅ ๋ฉ์์ง </p> </div> <div> <label for="loginId">๋ก๊ทธ์ธ ID</label> <input class="form-control" id="loginId" th:errorclass="field-error" th:field="*{loginId}" type="text"> <!-- th:errors๋ FieldError๊ฐ ๋ฐ์ํ์ ๋๋ง ๋ ๋๋ง๋๋ค. --> <div class="field-error" th:errors="*{loginId}"/> </div> <div> <label for="password">๋น๋ฐ๋ฒํธ</label> <input class="form-control" id="password" th:errorclass="field-error" th:field="*{password}" type="password"> <!-- th:errors๋ FieldError๊ฐ ๋ฐ์ํ์ ๋๋ง ๋ ๋๋ง๋๋ค. --> <div class="field-error" th:errors="*{password}"/> </div> <hr class="my-4"> <div class="row"> <div class="col"> <button class="w-100 btn btn-primary btn-lg" type="submit">๋ก๊ทธ์ธ</button> </div> <div class="col"> <button class="w-100 btn btn-secondary btn-lg" onclick="location.href='items.html'" th:onclick="|location.href='@{/}'|" type="button">์ทจ์ </button> </div> </div> </form> </div> <!-- /container --> </body> </html>
๐LoginForm, ๐LoginController โก LoginForm์ ์ด์ฉํ๋ค. ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ฉด ์ฟ ํค๋ฅผ ๋ณด๋ด์ค๋ค.
@Data public class LoginForm { @NotEmpty private String loginId; @NotEmpty private String password; }
@Slf4j @Controller @RequiredArgsConstructor public class LoginController { private final LoginService loginService; @GetMapping("/login") public String loginForm(@ModelAttribute("loginForm") LoginForm form) { return "login/loginForm"; } @PostMapping("/logout") public String logout(HttpServletResponse response) { expireCookie(response, "memberId"); return "redirect:/"; } @PostMapping("/login") public String login(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletResponse response) { if (bindingResult.hasErrors()) { return "login/loginForm"; } Member loginMember = loginService.login(form.getLoginId(), form.getPassword()); if (loginMember == null) { bindingResult.reject("loginFail", "์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ๋ง์ง ์์ต๋๋ค."); return "login/loginForm"; } //๋ก๊ทธ์ธ ์ฑ๊ณต ์ฒ๋ฆฌ //์ฟ ํค์ ์๊ฐ ์ ๋ณด๋ฅผ ์ฃผ์ง ์์ผ๋ฉด ์ธ์
์ฟ ํค๊ฐ ๋จ(๋ธ๋ผ์ฐ์ ์ข
๋ฃ์ ๋ชจ๋ ์ข
๋ฃ) Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId())); response.addCookie(idCookie); return "redirect:/"; } }
# ์ฟ ํค๋ฅผ ์ด์ฉํด ๋ก๊ทธ์ธ ์ธ์ ์ ์งํ๊ธฐ

- ์ด์ ๋ธ๋ผ์ฐ์ ๋ ์ข ๋ฃํ๊ธฐ ์ ๊น์ง Response์ ์ธ์ ์ฟ ํค๋ฅผ ๊ณ์ํด์ ์ ์กํด์ค๋ค.
๐จloginhome.html โก ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ์ ๋ ๋ณด์ฌ์ค ํ ํ๋ฉด ๋ทฐ์ด๋ค.

<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <link th:href="@{/css/bootstrap.min.css}" href="../css/bootstrap.min.css" rel="stylesheet"> </head>
<body> <div class="container" style="max-width: 600px"> <div class="py-5 text-center"> <h2>ํ ํ๋ฉด</h2> </div> <h4 class="mb-3" th:text="|๋ก๊ทธ์ธ: ${member.name}|">๋ก๊ทธ์ธ ์ฌ์ฉ์ ์ด๋ฆ</h4> <hr class="my-4"> <div class="row"> <div class="col"> <button class="w-100 btn btn-secondary btn-lg" type="button" th:onclick="|location.href='@{/items}'|"> ์ํ ๊ด๋ฆฌ </button> </div> <div class="col"> <form th:action="@{/logout}" method="post"> <button class="w-100 btn btn-dark btn-lg" onclick="location.href='items.html'" type="submit"> ๋ก๊ทธ์์ </button> </form> </div> </div> <hr class="my-4"> </div> <!-- /container --> </body> </html>
์ด์ ๋ชจ๋ ๊ฑด ์ค๋น๋์๋ค. ๋ธ๋ผ์ฐ์ ์์ ์ ์กํ๋ ์ฟ ํค๊ฐ์ ์ด์ฉํด์ ๋ก๊ทธ์ธ ์ํ๋ฅผ ๊ตฌํํด๋ณด์.
@ ๋ก๊ทธ์ธ ์ํ์ ์ง1 - ๋จ์ํ๊ณ ๋ฉ์ฒญํ ๋ฐฉ๋ฒ
- @CookieValue๋ฅผ ์ด์ฉํด์ ์ ์ก๋ ์ฟ ํค๊ฐ(memberId)์ด ์์ผ๋ฉด ๋ก๊ทธ์ธ ์ํ๋ก ํ๋จํ๋ค.
@Slf4j @Controller @RequiredArgsConstructor public class HomeController { private final MemberRepository memberRepository; @GetMapping("/") public String homeLogin( @CookieValue(name = "memberId", required = false) Long memberId, Model model) { if (memberId == null) { return "home";} //๋ก๊ทธ์ธ Member loginMember = memberRepository.findById(memberId); if (loginMember == null) { return "home";} model.addAttribute("member", loginMember); return "loginHome"; // ์ฟ ํค๊ฐ ์๋ค๋ฉด Login๋ ํํ๋ฉด ์ ์ก } }
ํ์ง๋ง ์ด๋ ๊ฒ ๊ฐ๋ฐํ๋ค๊ฐ๋ ํฐ์ผ๋๋ค. ๋ธ๋ผ์ฐ์ ์์ ์๋ฌด๋ฆฌ ๋ง์๋ด์ผ, ์ฟ ํค๋ ์ง์ ์์ฑํด์ ์กฐ์ํ ์ ์๋ค.
- ๋ค๋ฅธ ์ฌ๋์ ๊ณ์ ์ ํดํนํ ์ ์๋ค.
- ์ฟ ํค์ ๋ณด๊ด๋ ๊ฐ์ธ ๋ฐ์ดํฐ๋ ๋๊ตฌ๋ ์ ๊ทผํด์ ๋ณผ ์ ์๋ค๋ ๋ฌธ์ ๊ฐ ์๋ค.
โก ์ฆ ์์ด๋, ์ด๋ฆ, ์นด๋์ ๋ณด์ ๊ฐ์ ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ์ ์ฅํด์๋ ์๋๋ค. - ์ฟ ํค๋ฅผ ์ํธํ ํ๋ค๊ณ ํ๋ค, ์ฟ ํค ์์ฒด๋ฅผ ๋ชฐ๋ ํ์ณ์ ํ์ ์ฌ์ฉํ ์ ์๋ค๋ ๋ฌธ์ ์ ์ด ์๋ค.
โก ๋ก๊ทธ์ธ ์ฟ ํค์ ๋ง๋ฃ ๊ธฐํ์ด ํ์ํ๋ค.

@ ๋ก๊ทธ์ธ ์ํ์ ์ง2 - ์๋ฒ ์ธ์ ์ ์ฅ์
์ฌ์ค ์ฒซ๋ฒ์งธ ๋ฐฉ๋ฒ์ ๋๋ฌด ๋ฉ์ฒญํ ๋ฐฉ๋ฒ์ด์๋ค. ์์์ ๋ฐ์ํ ์ ์๋ ๋ฌธ์ ์ ๋ค์ ํด๊ฒฐํ๋ ์ฝ๋๋ฅผ ๋ง๋ค์ด๋ณด์.
- ์๋ฒ๋จ์์ ์ธ์
์ ์ฅ์๋ฅผ ๋ง๋ค๊ณ ์ฌ์ฉ์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ค. ์ฟ ํค์๋ ์ธ์
์ธ์ฆ๊ฐ(UUID)์ ๋์ ์ ์กํ๋ค.
โก Cookie: mySessionId=zz0101xx-bab9-4b92-9b32-dadb280f4b61 - ์ธ์
์ ์ฅ์์ ํค๋ ๋ง๋ฃ์๊ฐ์ ์งง๊ฒ(์: 30๋ถ) ์ ์งํ๋ค. ํ์ณ์ ๋์ค์ ์ฌ์ฌ์ฉํ๊ธฐ ํ๋ค๊ฒ ๋ง๋ ๋ค.
โก ํดํน์ด ์์ฌ๋๋ ๊ฒฝ์ฐ ์๋ฒ์์ ๊ฐ์ ๋ก ์ธ์ ์ ์ญ์ ํ๋ ๋ก์ง์ ๋ง๋ค ์ ์๋ค.

์ธ์ ์์ฑ, ์กฐํ, ๋ง๋ฃ๋ฅผ ๋ด๋นํ๋ ๐SessionManager ๋ฅผ ๋ง๋ค์ด๋ณด์.
@Component public class SessionManager { public static final String SESSION_COOKIE_NAME = "mySessionId"; private Map<String, Object> sessionStore = new ConcurrentHashMap<>(); //## ์ธ์
์์ฑ ##// public void createSession(Object value, HttpServletResponse response) { //์ธ์
id(UUID)๋ฅผ ์์ฑํ๊ณ , ๊ฐ์ ์ธ์
์ ์ ์ฅ String sessionId = UUID.randomUUID().toString(); sessionStore.put(sessionId, value); //์ฟ ํค ์์ฑ Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId); response.addCookie(mySessionCookie); } //## ์ธ์
์กฐํ ##// public Object getSession(HttpServletRequest request) { Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME); if (sessionCookie == null) { return null; } return sessionStore.get(sessionCookie.getValue()); } //## ์ธ์
๋ง๋ฃ ##// public void expire(HttpServletRequest request) { Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME); if (sessionCookie != null) { sessionStore.remove(sessionCookie.getValue()); } } // ์๋ธ๋ฆฟ์์ ์ธ์
๊ฐ์ฒด(Session)๋ฅผ ์ ๊ณตํด์ฃผ๊ธด ํ์ง๋ง, ์ดํด๋ฅผ ๋๊ธฐ์ํด ์ง์ ๊ตฌํํด๋ณด์๋ค. public Cookie findCookie(HttpServletRequest request, String cookieName) { if (request.getCookies() == null) { // ์ฟ ํค๊ฐ์ array ๋๋ null ๋ก ๋ฐํ๋๋ค. return null; } return Arrays.stream(request.getCookies()) // ํด๋น ์ฟ ํค์ด๋ฆ์ ์ฐพ์ ๋ฐํํ๋ ๋ก์ง .filter(cookie -> cookie.getName().equals(cookieName)) .findAny() // ์ฒ์์ผ๋ก ๋ฐ๊ฒฌ๋ ๊ฐ ์๋ฌด๊ฑฐ๋ .orElse(null); // ์๋ค๋ฉด null ๋ฐํ } }
๐SessionManager ๊ฐ ์ ์๋ํ๋์ง ํ์ธํ๊ธฐ์ํด MockResponse ๊ฐ์ฒด๋ฅผ ์ด์ฉํด ํ ์คํธ ํด๋ณด์.
class SessionManagerTest { SessionManager sessionManager = new SessionManager(); @Test @DisplayName("SessionManager ํ
์คํธ") void sessionTest() { //์ธ์
์์ฑ MockHttpServletResponse response = new MockHttpServletResponse(); Member member = new Member(); sessionManager.createSession(member, response); //์์ฒญ์ ์๋ต ์ฟ ํค ์ ์ฅ MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(response.getCookies()); //์ธ์
์กฐํ Object result = sessionManager.getSession(request); assertThat(result).isEqualTo(member); //์ธ์
๋ง๋ฃ sessionManager.expire(request); Object expired = sessionManager.getSession(request); assertThat(expired).isNull(); } }

์ธ์ ๋งค๋์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค๋ฉด, ์๋์ ๊ฐ์ด ๐LoginController ๋ฅผ ์ธ์ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๊ฒ ๋ฐ๊ฟ์ฃผ์.
@Slf4j @Controller @RequiredArgsConstructor public class LoginController { private final LoginService loginService; private final SessionManager sessionManager; @GetMapping("/login") public String loginForm(@ModelAttribute("loginForm") LoginForm form) { return "login/loginForm"; } @PostMapping("/logout") public String logoutV2(HttpServletRequest request) { sessionManager.expire(request); return "redirect:/"; } @PostMapping("/login") public String loginV2(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult, HttpServletResponse response) { if (bindingResult.hasErrors()) { return "login/loginForm";} Member loginMember = loginService.login(form.getLoginId(), form.getPassword()); if (loginMember == null) { bindingResult.reject("loginFail", "์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ๋ง์ง ์์ต๋๋ค."); return "login/loginForm"; } //๋ก๊ทธ์ธ ์ฑ๊ณต ์ฒ๋ฆฌ //์ธ์
๊ด๋ฆฌ์๋ฅผ ํตํด ์ธ์
์ ์์ฑํ๊ณ , ํ์ ๋ฐ์ดํฐ ๋ณด๊ด sessionManager.createSession(loginMember, response); return "redirect:/"; } }
์ด์ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ค๋ฉด, ๋ธ๋ผ์ฐ์ ์์ ์ธ์ ID (UUID)๋ฅผ ์ฟ ํค๋ก ๋ณด๋ด์ฃผ๊ฒ ๋๋ค.
@Slf4j @Controller @RequiredArgsConstructor public class HomeController { private final MemberRepository memberRepository; private final SessionManager sessionManager; @GetMapping("/") public String homeLoginV2(HttpServletRequest request, Model model) { //์ธ์
๊ด๋ฆฌ์์ ์ ์ฅ๋ ํ์ ์ ๋ณด ์กฐํ Member member = (Member) sessionManager.getSession(request); // [์ธ์
ID ์ฟ ํค]์ ํด๋น๋๋ ์ธ์
๋ฐ์ดํฐ์์ if (member == null) { return "home";} // ์ธ์
์์. ๋ก๊ทธ์ธ์ํ ์ ์ง model.addAttribute("member", member); return "loginHome"; } }
@ ๋ก๊ทธ์ธ ์ํ ์ ์ง3 - ์๋ธ๋ฆฟ HTTP ์ธ์
๋งค๋ฒ SessionManager๋ฅผ ๊ตฌํํ๊ธฐ์๋ ๋ฒ๊ฑฐ๋กญ๋ค. ๊ทธ๋์ ์๋ธ๋ฆฟ ์คํ์์๋ HttpSession ๊ฐ์ฒด๋ฅผ ์ ๊ณตํด์ค๋ค.
- ์คํ๋ง์ ๋ง๋ฒ์ ๋๊ตฌ๊ฐ ์๋๋ค. ๊ฒฐ๊ตญ ์คํ๋ง ๋ด๋ถ์์๋ ์๋ฐ ์๋ธ๋ฆฟ์ ์ด์ฉํ์ฌ ์น ์๋น์ค๋ฅผ ๊ตฌํํ๋ค.
- HttpSession์ HttpServletRequest ์์ ๋ฐ์์ฌ ์ ์๋ค.
- HttpSession์ JSESSIONID ๋ผ๋ ์ธ์
ID ์ฟ ํค๋ฅผ ์์ฑํ๋ค.
โก Cookie: JSESSIONID=5B78E23B513F50164D6FDD8C97B0AD05
public class SessionConst { public static final String LOGIN_MEMBER = "loginMember"; }
@PostMapping("/login") public String login(... HttpServletRequest request){ // ... ์๋ต ... //์ธ์
์ด ์์ผ๋ฉด ์๋ ์ธ์
๋ฐํ, ์์ผ๋ฉด ์ ๊ท ์ธ์
์ ์์ฑ // request.getSession(false)๋ก ํ๋ฉด ์ธ์
์ด ์๋ค๋ฉด null์ ๋ฐํํ๋ค. HttpSession session = request.getSession(); //์ธ์
์ ๋ก๊ทธ์ธ ํ์ ์ ๋ณด ๋ณด๊ด session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember); } @PostMapping("/logout") public String logoutV3(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session != null) { session.invalidate(); // ์ธ์
์ ๊ฑฐ } return "redirect:/"; }
HttpSession ๊ฐ์ฒด๋ฅผ ์ด์ฉํ๋ฉด ๋ง์น ์ฟ ํค๋ฅผ ์ฌ์ฉํ์ง ์๋ ๊ฒ์ฒ๋ผ ์ธ์ ์ ์ถ์ํํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
@๋ก๊ทธ์ธ ์ํ์ ์ง4 - ์คํ๋ง ์๋ธ๋ฆฟ HTTP ์ธ์
์คํ๋ง์์๋ ์์ ๊ฐ์ HttpSession ๊ฐ์ฒด๋ฅผ @SessionAttribute ์ด๋ ธํ ์ด์ ์ ์ด์ฉํด ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
- ๋ธ๋ผ์ฐ์ ์์ ์ ์กํ๋ JSESSIONID๋ฅผ @SessionAttribute(~) ํ๋๋ก ๋ฐ์์ฌ ์ ์๋ค.
@Slf4j @Controller @RequiredArgsConstructor public class HomeController { private final MemberRepository memberRepository; private final SessionManager sessionManager; @GetMapping("/") public String homeLoginV3Spring( @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) { //์ธ์
์ ํ์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด home if (loginMember == null) { return "home";} //์ธ์
์ด ์ ์ง๋๋ฉด ๋ก๊ทธ์ธ์ผ๋ก ์ด๋ model.addAttribute("member", loginMember); return "loginHome"; } }
@ ์ฐธ๊ณ - HTTP TrackingModes
- ๊ณผ๊ฑฐ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์ฟ ํค๋ฅผ ์ง์ํ์ง ์๋ ๊ฒฝ์ฐ, URL์ ์ฟ ํค๋ฅผ ์ ๋ ๊ฒฝ์ฐ๊ฐ ์์๋ค.

๊ทธ๋์ ์๋ฒ์์๋ TrackingModes๋ผ๊ณ ํด์ ์ฟ ํค์ URL ๋ ๋ค ๋ณด๋ด์ฃผ๋ ๊ธฐ๋ฅ์ด ์์๋ค. ์์ฆ์ ๊ฑฐ์ ์ฌ์ฉํ์ง ์๋ ๊ธฐ๋ฅ์ด๋ฏ๋ก ํ์ํ์ง ์๋ค๋ฉด ์คํ๋ง application.properties์์ ์ฟ ํค๋ง ์ฌ์ฉํ๋๋ก ๋ณ๊ฒฝํ ์ ์๋ค.
# application.properties server.servlet.session.tracking-modes=cookie
# HttpSession ์ ๋ณด์ ํ์์์ (๋ง๋ฃ) ์ค์
์์ ์์๋ ์ธ์ ์ ๋จ์ ๋ก๊ทธ์ธ๋ง ๊ฐ๋ฅํ๋๋ก ๋ง๋ค์๋๋ฐ, HttpSession ๊ฐ์ฒด์๋ ๋ค์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํด์ค๋ค.
HttpSession session = request.getSession(false); if (session == null) { return "์ธ์
์ด ์์ต๋๋ค."; } //์ธ์
๋ฐ์ดํฐ ์ถ๋ ฅ session.getAttributeNames().asIterator() .forEachRemaining( name -> log.info("session name={}, value={}", name, session.getAttribute(name))); log.info("sessionId={}", session.getId()); // JSESSION ID ๊ฐ log.info("getMaxInactiveInterval={}", session.getMaxInactiveInterval());// ์ ํจ์๊ฐ(sec) log.info("creationTime={}", new Date(session.getCreationTime()));// ์ธ์
์์ฑ์ผ์ log.info("lastAccessedTime={}", new Date(session.getLastAccessedTime()));//๋ง์ง๋ง ์ฌ์ฉ์๊ฐ log.info("isNew={}", session.isNew());//์๋ก ๋ง๋ ์ธ์
์ธ์ง, ํด๋ผ์๊ฒ ๋ฐ์ ์ธ์
์ธ์ง ์ฌ๋ถ

์ด๋ ๊ฒ HttpSession์ด ์ ๊ณตํ๋ ๋ฐ์ดํฐ๋ฅผ ์ด์ฉํด์ ๋ง๋ฃ๊ธฐ๊ฐ์ ์ค์ ํ ์ ์๋ค.
โก ๋ง๋ฃ์๊ฐ์ ์ค์ ํ๋ ๊ฑด ๋ณด์์ ์ธ ์ด์ ๋ ์์ง๋ง, ์๋ฒ์ ๋ฉ๋ชจ๋ฆฌ ์ฉ๋์ ๊ณ์ ์ฐจ์งํ๊ธฐ ๋๋ฌธ์ ํ์์ ์ด๋ค.
if(HTTP ์ธ์
์ฟ ํค๊ฐ ์ ์ก๋์๋ค๋ฉด){ session.setMaxInactiveInterval(1800); // 1800์ด๋ก ๋ง๋ฃ๊ธฐํ ๊ฐฑ์ }
์ด๋ ๊ฒ HttpSession์ ๋ง๋ฃ๊ธฐ๊ฐ์ ์ง์ ํ๋ฉด WAS๊ฐ ๋ด๋ถ์์ ํด๋น ์ธ์ ์ ์ ๊ฑฐํ๋ค.
์ฐธ๊ณ ๋ก ๊ธฐ๋ณธ ์ธ์ ๋ง๋ฃ๊ธฐ๊ฐ์ ์คํ๋ง application.properties ์ต์ ์ผ๋ก ์ค์ ํ ์ ์๋ค.
# application.properties server.servlet.session.timeout=60 # 60์ด๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ง์
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev