JSESSIONID가 뭐야?
요약
1. 톰캣에 최초 접근 시 톰캣은 응답 헤더에 sessionid 쿠키를 발급한다.
2. session cookie name을 별도로 지정하지 않았다면 기본값이 JSESSIONID
3. 톰캣은 JSESSIONID로 사용자의 세션을 구분한다.
= 똑같은 request.getSession().getAttribute("키값") 했을때 내 정보만 보여진다.
회사에서 사용자의 요청을 여러 서버에 분배해서 처리할때
A서버(톰캣)에서 로그인 후 작업하다가 B서버(톰캣)로 요청이 전달됐을때 로그인이 풀리는 문제가 있었다.
A서버 세션에 저장된 인증 정보가 B서버 세션에 없기때문에 그런것이었는데
결국엔 기존에 사용하던 사용 솔루션을 활용해서 처리했지만
세션에 대해서 조금 궁금한게 있어서 한번 찾아봤다.
HttpSession session = request.getSession();
session.setAttribute("role", myRole);
이런식으로 세션을 사용하는데
어떻게 사용자들은 같은 "role" key값으로 각자의 myRole을 set하고 get 할까?
어떻게 세션을 구분하는지 궁금했다.
그래서 한번 간단하게 확인해봤다.
//컨트롤러
@RestController
public class SessionController {
@GetMapping("/setSession")
public String setSession(HttpServletRequest request) {
HttpSession session = request.getSession();
session.setAttribute("mySession", "tomcat8080");
return "finish";
}
@GetMapping("/helloSession")
public String helloSession(HttpServletRequest request) {
HttpSession session = request.getSession();
if (session.getAttribute("mySession") == null) {
session.setAttribute("mySession", "emptyValue");
}
return session.getAttribute("mySession").toString();
}
}
//application.yml
logging:
level:
root: debug
server:
port: 8080
이렇게 똑같은 소스로 간단한 프로젝트 2개를 만들었다.
하나는 8080포트, 하나는 8081 포트로 서버를 실행한다.
그리고 테스트해보면
이렇게 8080서버에 mySession 값을 설정해두고 8081서버에 가서 확인해보면
당연한 결과지만 어떻게 구분하는지 궁금해서 helloSession에 breakPoint 를 걸어두고
어떤 과정을 거쳐오는지 확인해봤다.
그랬더니 저 컨트롤러 하나 오는데도 엄청 많은 과정을 거쳐오는데 그중에서 관련있는걸 찾았다.
//org.apache.tomcat.embed
//CoyoteAdapter.java
/**
* Parse session id in Cookie.
*
* @param request The Servlet request object
*/
protected void parseSessionCookiesId(Request request) {
// If session tracking via cookies has been disabled for the current
// context, don't go looking for a session ID in a cookie as a cookie
// from a parent context with a session ID may be present which would
// overwrite the valid session ID encoded in the URL
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());
}
}
}
}
}
이 코드에서 세션 쿠키id를 파싱해서 사용한다.
세션 쿠키 이름를 가져오는 SessionConfig 클래스를 확인해보면
public class SessionConfig {
private static final String DEFAULT_SESSION_COOKIE_NAME = "JSESSIONID";
private static final String DEFAULT_SESSION_PARAMETER_NAME = "jsessionid";
/**
* Determine the name to use for the session cookie for the provided
* context.
* @param context The context
* @return the cookie name for the context
*/
public static String getSessionCookieName(Context context) {
return getConfiguredSessionCookieName(context, DEFAULT_SESSION_COOKIE_NAME);
}
//생략
private static String getConfiguredSessionCookieName(Context context, String defaultName) {
// Priority is:
// 1. Cookie name defined in context
// 2. Cookie name configured for app
// 3. Default defined by spec
if (context != null) {
String cookieName = context.getSessionCookieName();
if (cookieName != null && cookieName.length() > 0) {
return cookieName;
}
SessionCookieConfig scc = context.getServletContext().getSessionCookieConfig();
cookieName = scc.getName();
if (cookieName != null && cookieName.length() > 0) {
return cookieName;
}
}
return defaultName;
}
//생략
}
여기서 기본값이 "JSESSIONID"로 돼있는걸 볼수있다!
개발자 도구에서 보이던
JSESSIONID의 출처를 찾았다.
그런데 getConfiguredSessionCookieName을 보면 주석으로 우선순위는
1. context에 정의된 쿠키이름
2. 어플리케이션에서 정의한 쿠키이름
3. 기본값
이라고 하는데 한번 변경해보자!
//application.yml
logging:
level:
root: debug
server:
port: 8080
servlet:
session:
cookie:
path: /
name: PILMINGSESSIONID
domain: localhost
http-only: true
secure: true
timeout: 3600
application.yml파일에서 servlet 설정을 이렇게 추가한 뒤 서버를 실행해보면
그럼 이제 위의 주석에서 가장 우선순위가 높은것은 context에 정의된 쿠키이름이었으니
한번 컨텐스트 설정을 변경해보자!
@Configuration
public class TomcatConfig {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> sessionManagerCustomizer() {
return factory -> factory.addContextCustomizers(context -> {
context.setSessionCookieName("CONTEXTSESSIONID");
});
}
}
검색해보니 이런식으로 톰캣 컨텍스트를 설정할수있다고한다!
그럼 과연 세션 쿠키이름으로 PILMINGSESSIONID가 나올지 CONTEXTSESSIONID가 나올지 확인해보자
역시 우선순위를 잘 따르는걸 알수있다!
외장톰캣을 사용할때는 context에만 추가하면 되기때문에 설정이 간단한데
스프링 부트에서는 보통 내장톰캣을 사용하기때문에 굳이 이렇게까지 사용할필요는 없을것같다
필요하다면 application.yml이나 application.properties에서 설정해서 사용하자!
요즘에는 서버를 여러대 둬서 분산처리를 많이 한다던데
그럴땐 어떻게 세션을 유지하는지 한번 알아보자! (언젠가..)