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์ด๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ง์
'๐ฑ Spring Framework > Spring MVC' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring ์์ธ์ฒ๋ฆฌ - ์ค๋ฅํ์ด์ง(404,500) (0) | 2021.08.30 |
---|---|
Spring Login#2 ํํฐ, ์ธํฐ์ ํฐ, ArugmentResolver (0) | 2021.08.30 |
Spring Bean Validation#2 ์คํ๋ง ๋น ๊ฒ์ฆ (0) | 2021.08.29 |
Spring Vaildation#1 ๊ฒ์ฆ (0) | 2021.08.29 |
Spring ๋ฉ์์ง, ๊ตญ์ ํ ๊ธฐ๋ฅ (0) | 2021.08.28 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev