DevLog ๐ถ
[Spring] ์ํ ๊ด๋ฆฌ ์์คํ ์ ์ธ์ ์ผ๋ก ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ๊ธฐ ๋ณธ๋ฌธ
[Spring] ์ํ ๊ด๋ฆฌ ์์คํ ์ ์ธ์ ์ผ๋ก ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ๊ธฐ
dolmeng2 2022. 8. 23. 22:14๊น์ํ ๋์ '์คํ๋ง MVC 2ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํ์ฉ ๊ธฐ์ '์ ๋ณด๊ณ ์ ๋ฆฌํ ๊ธ์ ๋๋ค ๐
- ์ง๋ ํฌ์คํ ๊ณผ ์ด์ด์ง๋๋ค :D
| ์ธ์ ์ ํตํด ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ๊ธฐ (์ง์ ๊ตฌํ)
- ์ธ์ ์ ํตํด ๋ก๊ทธ์ธ์ ์ฒ๋ฆฌํด๋ณด์.
- ํด๋ผ์ด์ธํธ๊ฐ ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ์ ์กํ๋ฉด, ์๋ฒ์์ ํด๋น ์ ๋ณด๋ฅผ ์ฒดํฌํ์ฌ ์ฌ์ฉ์๋ฅผ ์กฐํํ๋ค.
- ์๋ฒ์์๋ ์ธ์ ID๋ฅผ ์์ฑํ๋ค. (์ถ์ ๋ถ๊ฐ๋ฅํ ๊ฐ์ผ๋ก ๋๋คํ๊ฒ ์์ฑ)
- ์์ฑ ํ, ํด๋น ์ธ์ ID๋ฅผ ํค ๊ฐ์ผ๋ก ํ์ฌ ๋ฉค๋ฒ๋ฅผ ์ ์ฅํด๋๋ค.
- ์๋ฒ๋ ์ธ์ Id๋ง ์ฟ ํค์ ๋ด์ ์ ๋ฌํ๊ณ , ํด๋ผ์ด์ธํธ๋ ์ด๋ฅผ ๋ฐ์์ ๋ณด๊ดํ๋ฉฐ ์์ฒญ ์ ํจ๊ป ์ ๋ฌํ๋ค.
โญ ๊ฒฐ๊ตญ, ์ค์ํ ๊ฐ์ ์ฟ ํค์ ๋ฃ์ด์ ์ ์กํ์ง ์๋ ๊ฒ.
[SessionManager.java]
@Component
public class SessionManager {
public static final String SESSION_COOKIE = "mySessionId";
private Map<String, Object> store = new ConcurrentHashMap<>();
public void createSession(Object value, HttpServletResponse response) {
String sessionId = UUID.randomUUID().toString();
store.put(sessionId, value);
Cookie mySessionCookie = new Cookie(SESSION_COOKIE, sessionId);
mySessionCookie.setPath("/");
response.addCookie(mySessionCookie);
}
public Object getSession(HttpServletRequest request) {
Cookie sessionCookie = findCookie(request, SESSION_COOKIE);
if (sessionCookie == null) {
return null;
}
return store.get(sessionCookie.getValue());
}
public void expire(HttpServletRequest request) {
Cookie sessionCookie = findCookie(request, SESSION_COOKIE);
if (sessionCookie != null) {
store.remove(sessionCookie.getValue());
}
}
private Cookie findCookie(HttpServletRequest request, String cookieName) {
if (request.getCookies() == null) {
return null;
}
return Arrays.stream(request.getCookies())
.filter(cookie -> cookie.getName().equals(cookieName))
.findAny()
.orElse(null);
}
}
- ์ธ์ ์์ฑ ๋ฐ ์กฐํ, ๋ง๋ฃ ๊ธฐ๋ฅ์ ๊ตฌํํ์๋ค.
- ์ฟ ํค์ memberId ๋์ UUID๋ฅผ ํตํด ๋๋ค์ผ๋ก ์์ฑํ ๊ฐ์ ๋ฃ์ด์ฃผ๊ณ , ๋ณ๋์ ์ธ์ ์ ์ฅ์๋ฅผ map์ผ๋ก ๊ด๋ฆฌํ๋ค.
- ๋ง๋ฃ ์์๋ ์ธ์ ์ ์ฅ์์์ ์์ ๋ฒ๋ฆฐ๋ค.
- ์กฐํ ์์๋ HttpServlet์์ ์ฟ ํค ์ค "mySessionId"์ธ ์ฟ ํค๋ฅผ ์ฐพ์์ ํด๋น ์ฟ ํค์ value ๊ฐ์ ๋ฆฌํดํด์ค๋ค.
cf) ํ ์คํธ ์ฝ๋๋ฅผ ์งค ๋ HttpServletRequest, HttpServletResponse๋ฅผ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ณดํต์ MockHttpServletRequest, MockHttpServletResponse๋ฅผ ๊ตฌํํ๋ค!
[LMemberController.java]
@PostMapping("/login")
public String login(@Valid @ModelAttribute("loginForm") LMemberLoginRequest loginRequest,
BindingResult bindingResult, HttpServletResponse response) {
if(bindingResult.hasErrors()) {
return "login/members/loginForm";
}
LMember loginMember = service.login(loginRequest);
if(loginMember == null) {
bindingResult.reject("loginFail", "์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.");
return "login/members/loginForm";
}
sessionManager.createSession(loginMember, response);
return "redirect:/login/";
}
@PostMapping("/logout")
public String logout(HttpServletRequest request) {
sessionManager.expire(request);
return "redirect:/login/";
}
- ๋ค์์ฒ๋ผ ๋ก๊ทธ์ธ, ๋ก๊ทธ์์ ๋ก์ง์์ ์ธ์ ์ ํ์ฉํด์ค๋ค.
[LHomeController.java] - ์์
@GetMapping("/")
public String home(HttpServletRequest request, Model model) {
LMember member = (LMember) sessionManager.getSession(request);
if(member == null) {
return "login/home";
}
model.addAttribute("member", member);
return "login/loginHome";
}
- ๊ธฐ์กด์๋ ์ฟ ํค์์ ๊ฐ์ ๊บผ๋ด์ ์ผ์ง๋ง, ์ด๋ฒ์๋ ์ธ์ ๋งค๋์ ๋ฅผ ํตํด map์ ์ ์ฅ๋์ด ์๋์ง ํ๋จํ๋ค.
- ๊ตณ์ด ์ฟ ํค์ ์ ํจ๊ธฐ๊ฐ์ ์ค์ด์ง ์์๋, map์ ์์ผ๋ฉด ๋ก๊ทธ์์์ด๋ผ๊ณ ํ๋จํ๊ธฐ.
| ์ธ์ ์ ํตํด ๋ก๊ทธ์ธ ์ฒ๋ฆฌํ๊ธฐ (์๋ธ๋ฆฟ ์ธ์ )
- ์๋ธ๋ฆฟ์์ ์ ๊ณตํ๋ HttpSession์ ํตํด์ ์ฐ๋ฆฌ์ ์น ์ฌ์ดํธ์ ์ ์ฉํด๋ณด์.
- ์๋ธ๋ฆฟ์ ํตํด HttpSession์ ์์ฑํ๋ฉด ์ฟ ํค ์ด๋ฆ์ด JSESSIONID, ๊ฐ์ด ๋๋คํ ๊ฐ์ธ ์ฟ ํค๋ฅผ ์์ฑํด์ค๋ค.
- ์ฆ, ์์ฒญ์ด ๋ค์ด์ค๋ฉด ์ธ์ ์ ์์ฑํ๊ณ , ์๋ต ํค๋์ ์ธ์ ID๋ฅผ ์ถ๊ฐํด์ค๋ค. (JSESSIONID๋ ํฐ์บฃ์ด ์์ฑ, ์ธ์ ์ ์ฅ์๋ฅผ ํฐ์บฃ์ด ๊ด๋ฆฌ)
-> ์ดํ ์์ฒญ์ ์ฒ๋ฆฌํ ๋ค์ ์๋ต์ ํด์ฃผ๋ ํ์์ด๋ค.
- ํด๋ผ์ด์ธํธ์๊ฒ ์๋ต์ ์ฟ ํค๋ก JessionId๊ฐ ๋ด๊ฒจ์ ธ์ ์ ๋ฌ๋๋ ํ์ (์ด๊ฒ๋ ํฐ์บฃ์ด ํด์ค๋ค)
[LMemberController.java]
@PostMapping("/login")
public String login(@Valid @ModelAttribute("loginForm") LMemberLoginRequest loginRequest,
BindingResult bindingResult, HttpServletRequest request) {
if(bindingResult.hasErrors()) {
return "login/members/loginForm";
}
LMember loginMember = service.login(loginRequest);
if(loginMember == null) {
bindingResult.reject("loginFail", "์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.");
return "login/members/loginForm";
}
HttpSession session = request.getSession();
session.setAttribute("loginMember", loginMember);
return "redirect:/login/";
}
@PostMapping("/logout")
public String logout(HttpServletRequest request) {
HttpSession session = request.getSession();
if(session != null) {
session.invalidate();
}
return "redirect:/login/";
}
getSession์ ํตํด ๊ธฐ์กด ์ธ์ ์ด ์กด์ฌํ๋ฉด ๋ฐํ, ์์ผ๋ฉด ์๋ก ์์ฑํด์ ๋ฐํํ๋๋ก ํ๋ค. (์์ฑ ์ ์ฌ์ฉ)
์ด๋, getSession(false)๋ก ํ๋ฉด ์ธ์ ์ด ์์ ๋ ์๋ก์ด ์ธ์ ์ ์์ฑํ์ง๋ ์๊ณ null์ ๋ฐํํ๋ค. (์กฐํ ์ ์ฌ์ฉ)
์ฆ, request ์ ๋ณด์์ ์ป์ด์จ UUID ๊ฐ์ผ๋ก ๋ง๋ค์ด์ง ์ฟ ํค์ value๋ฅผ ๋ณด๊ณ ์ธ์ ์ ์ฅ์์์ ๋์ผํ JsessionId ๊ฐ์ด ์๋์ง ์ฐพ๋๋ค.
๋์ผํ ๊ฐ์ด ์์ผ๋ฉด ํด๋น session์ ๊ฐ์ ธ์ค๊ณ , ์์ผ๋ฉด ํด๋น session์ ์๋ก ๋ง๋ค์ด์ ๋ฐํํ๋ค.
session์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๊ดํ๊ธฐ ์ํด setAttribute()๋ฅผ ์ฌ์ฉํ๋ค.
name์๋ ์์๊ฐ์ ์ฌ์ฉํ๋ ๊ฒ ๋ ์ข์ง๋ง ์ฐ์ ์ ๋ ๊ฒ String์ผ๋ก ๋ฐ์๋ค...
session์ key์๋ "loginMember"๋ฅผ, value์๋ ์ค์ member ๊ฐ์ ๋ฃ์ด์ค๋ค.
์ดํ, sessionId๋ฅผ ํตํด session์ ๊ฐ์ ธ์ฌ ๋ session์ ํค๋ฅผ ํตํด ๋ด๋ถ์ member ๊ฐ์ ๊ฐ์ ธ์ฌ ์ ์๋ค.
์ดํ, ๋ก๊ทธ์์ ์ ์ธ์ ์ ์ ๊ฑฐํ๊ธฐ ์ํด์ session.invalidate()๋ฅผ ์ฌ์ฉํ๋ค.
๐ฉ ์... ์ข ๋ณต์กํ์ง๋ง ๋ค์ ์ ๋ฆฌํ์๋ฉด,
[ํฐ์บฃ์ด ๊ด๋ฆฌํ๋ ์ธ์ ์ ์ฅ์]
key - JsessionId, value - Session ํํ๋ก ์ ์ฅ.
- ์ฌ๊ธฐ์ value ๊ฐ์ request.getSession์ผ๋ก ์ป์ ๊ฐ์ด๋ผ๊ณ ๋ณผ ์ ์๋ค.
[์ธ์ ์ ์ฅ์์ value์ ์ ์ฅ๋ ๊ฐ ์ธ์ ]
key - loginMember, value - ์ค์ Member ๊ฐ์ฒด
- setAttribute๋ฅผ ํตํด ๋ฃ์ด์ค ๊ฐ!
์ด๋ฐ ์์ผ๋ก ๋์ด ์๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค!
[LHomeController.java]
@GetMapping("/")
public String home(HttpServletRequest request, Model model) {
HttpSession session = request.getSession(false);
if (session == null) {
return "login/home";
}
LMember loginMember = (LMember) session.getAttribute("loginMember");
if (loginMember == null) {
return "login/home";
}
model.addAttribute("member", loginMember);
return "login/loginHome";
}
- ์ธ์ ์กฐํ ์์๋ getSession(false)๋ฅผ ํตํด ์กฐํํ๋ค. ์ธ์ ์์ฒด๊ฐ ์์ผ๋ฉด ๋ก๊ทธ์์์ด ๋์ด์๋ค๊ณ ํ๋จํ๋ค.
- ์ธ์ ์ด ์๋ค๋ฉด ๊ฐ ์ธ์ ์ ์ ์ฅ๋ ํ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค.
- ์ด๋, ํ์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ์ญ์ ๋ก๊ทธ์์์ด ๋์ด ์๋ค๊ณ ๊ฐ์ ํ๋ค.
cf) ์ด์ํ๋ก ์งํํ๋ฉด url์ ์๋์ผ๋ก ;jsessionId=302948902384๊ฐ ๋ถ์ด์ 404 ์๋ฌ๊ฐ ๋ํ๋๋ค.
๊ทธ๋์, ์คํ๋ง ๋ด๋ถ์ config์์ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ์ ์ถ๊ฐํด์ฃผ์๋ค.
[WebMvcConfig.java]
@Bean
public ServletContextInitializer configSession() {
return servletContext -> {
servletContext.setSessionTrackingModes(
Collections.singleton(SessionTrackingMode.COOKIE));
servletContext.getSessionCookieConfig()
.setHttpOnly(true);
};
}
๐ฉ HTTP Protocol์ stateless ํ๊ธฐ ๋๋ฌธ์, ํฐ์บฃ ๊ฐ์ ์๋ธ๋ฆฟ ์ปจํ ์ด๋๊ฐ ์ธ์ ๊ด๋ฆฌ๋ฅผ ์งํํ๋ค.
- ์ด๋, ์ฟ ํค๋ URL Rewriting์ ํตํด์ ์ฃผ๋ก ๊ด๋ฆฌํ๋ค.
- ์ง๊ธ ์์ ๊ฒฝ์ฐ URL Rewriting์ด ๋ฐ์ํ ๊ฒฝ์ฐ์ธ๋ฐ, ์ด๋ ํฐ์บฃ์ด ์ฒ๋ฆฌํ๋ Coyote Http11Process๊ฐ CoyoteAdapter๋ฅผ ํธ์ถํ๋ค
[CoyoteAdapter.java]
protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
org.apache.coyote.Response res, Response response) throws IOException, ServletException {
...
String sessionID;
if (request.getServletContext().getEffectiveSessionTrackingModes()
.contains(SessionTrackingMode.URL)) {
// Get the session ID if there was one
sessionID = request.getPathParameter(
SessionConfig.getSessionUriParamName(
request.getContext()));
if (sessionID != null) {
request.setRequestedSessionId(sessionID);
request.setRequestedSessionURL(true);
}
}
// Look for session ID in cookies and SSL session
try {
parseSessionCookiesId(request);
} catch (IllegalArgumentException e) {
// Too many cookies
if (!response.isError()) {
response.setError();
response.sendError(400);
}
return true;
}
parseSessionSslId(request);
...
}
- ์ ๋ฉ์๋์์ getEffectiveSessionTrackingModes()์์ URL mode๋ฅผ ํฌํจํ๋ฉด, ํด๋น URL์์ ์ธ์ ID๋ฅผ ํ์ฑํ์ฌ ์ฐพ๋๋ค.
- ์ธ์ ID๊ฐ ์กด์ฌํ๋ค๋ฉด request์ session์ ์งํํด์ค๋ค.
- ์ดํ, try๋ฌธ์์ ์ฟ ํค์ ์ ์ฅ๋ ์ธ์ ID๋ฅผ ์ฐพ๋๋ค.
[parseSessionCookiesId.java]
protected void parseSessionCookiesId(Request request) {
Context context = request.getMappingData().context;
if (context != null && !context.getServletContext()
.getEffectiveSessionTrackingModes().contains(
SessionTrackingMode.COOKIE)) {
return;
}
// Parse session id from cookies
ServerCookies serverCookies = request.getServerCookies();
int count = serverCookies.getCookieCount();
if (count <= 0) {
return;
}
String sessionCookieName = SessionConfig.getSessionCookieName(context);
for (int i = 0; i < count; i++) {
ServerCookie scookie = serverCookies.getCookie(i);
if (scookie.getName().equals(sessionCookieName)) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// Accept only the first session id cookie
convertMB(scookie.getValue());
request.setRequestedSessionId
(scookie.getValue().toString());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
if (log.isDebugEnabled()) {
log.debug(" Requested cookie session id is " +
request.getRequestedSessionId());
}
} else {
if (!request.isRequestedSessionIdValid()) {
// Replace the session id until one is valid
convertMB(scookie.getValue());
request.setRequestedSessionId
(scookie.getValue().toString());
}
}
}
}
}
๋ด๋ถ์์ ๋์ผํ ์ด๋ฆ์ ๊ฐ์ง ์ฟ ํค๋ฅผ ์ฐพ๋ ๊ฑธ ๋ณผ ์ ์๋ค. ์ด๋, ์ฐพ์ผ๋ฉด URL์์ ์ป์ ๊ฐ์ false๋ก ์ฒ๋ฆฌํ๋ค.
โญ์๋ฌดํผ, ์ด๋ฌํ ๊ณผ์ ๋๋ฌธ์ jessionId ๋์ ์ฟ ํค์ ์๋ ๊ฐ์ ์ฌ์ฉํ๋๋ก ํ๊ธฐ ์ํด sessionTrackingMode๋ฅผ Cookie๋ก ์ค์ ํด์ฃผ๋ ๊ฒ.
๐ฉ ๋์ค์ ๊ฐ์ ๋ณด๋ฉด์ ์๊ฒ ๋ ๊ฑด๋ฐ, ๊ตณ์ด ์ด๋ ๊ฒ ๋น ๋ฑ๋ก์ผ๋ก ์ฒ๋ฆฌํ์ง ์์๋...
application.properties์ ๋ค์์ ์ถ๊ฐํด์ฃผ๋ฉด ๋๋ค...! ใ ใ
server.servlet.session.tracking-modes=cookie
โ ์คํ๋ง์์ ์ ๊ณตํ๋ ์ธ์ ์ฌ์ฉํ๊ธฐ
- ์คํ๋ง์์ ์ ๊ณตํ๋ @SessionAttribute๋ฅผ ์ฌ์ฉํ๋ฉด ์ธ์ ์ ๋ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
[LHomeController.java]
@GetMapping("/")
public String home(
@SessionAttribute(name = "loginMember", required = false) LMember loginMember,
Model model) {
if (loginMember == null) {
return "login/home";
}
model.addAttribute("member", loginMember);
return "login/loginHome";
}
- @SessionAttribute๋ฅผ ์ฌ์ฉํ๋ฉด name์ด loginMember์ธ ์ธ์ ์ ์ฐพ์ ์ ์๋ค.
- โญ ์ด๋๋ ์ธ์ ์ ์์ฑํ์ง ์๊ณ , ๋จ์ํ ์กฐํ ๊ธฐ๋ฅ๋ง ์ ๊ณตํ๋ค.
โ ์ธ์ ์ด ์ ๊ณตํ๋ ์ ๋ณด
session.getId() | JSessionID ๊ฐ |
session.getMaxInactiveInterval() | ์ธ์ ์ ์ ํจ ์๊ฐ, ์ด ๋จ์ |
new Date(session.getCreationTime()) | ์ธ์ ์ ์์ฑ ์ผ์ |
new Date(session.getLastAccessedTime()) | ์ธ์
๊ณผ ์ฐ๊ฒฐ๋ ์ฌ์ฉ์๊ฐ ์ต๊ทผ ์๋ฒ์ ์ ์ํ ์๊ฐ - ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ๋ก JsessionID๋ฅผ ์์ฒญํ ๊ฒฝ์ฐ ๊ฐฑ์ |
session.isNew() | ์๋ก ์์ฑ๋ ์ธ์ ์ธ์ง, ์ด๋ฏธ ์์ฑ๋๊ณ ์กฐํ๋ ์ธ์ ์ธ์ง ์ฌ๋ถ |
- ์ถ๊ฐ์ ์ผ๋ก, ์ธ์ ์ ์ ์ง ์๊ฐ์ ๋ณดํต LastAccessedTime + alpha์ ํ์์ผ๋ก ์งํํ๋ค.
- ์์ฒญ์ ํ ๋๋ง๋ค ๊ทธ๋งํผ ์ ์ง ์๊ฐ์ด ๋ ์ง์๋๋ ๊ฒ.
- application.properties๋ฅผ ํตํด์ ๋ฐ๋ก ์ค์ ๋ ๊ฐ๋ฅํ๋ค.
session.setMaxInactiveInterval(1800);
- WAS์์๋ LastAccessedTime + alpha ๋งํผ์ ์๊ฐ์ด ์ง๋๋ฉด ๋ด๋ถ์์ ์ธ์ ์ ์ ๊ฑฐํ๋ค. (HttpSession ๊ธฐ์ค)
- ๋ค์ ํฌ์คํ ์์๋ ํํฐ, ์ธํฐ์ ํฐ๋ฅผ ํตํ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ ๋ก์ง์ ์์๋ณด์.