스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - (34) 로그인 처리 - 쿠키 세션 - 세션 방식
인프런 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술편을 학습하고 정리한 내용 입니다.
로그인 처리하기 - 세션 동작 방식
서버에 중요한 정보를 보관하고 연결을 유지하는 방법을 세션이라 함.
세션 동작 방식
세션을 어떻게 개발할지 먼저 개념을 이해해보자.

- 사용자가
loginId,password정보를 전달하면 서버에서 해당 사용자가 맞는지 확인한다.

- 세션 ID를 생성하는데, 추정 불가능해야 한다.
UUID는 추정이 불가능 하다.Cookie : mySessionId=zz0101xx-bab9-4b92-9b32-dadb280f4b61
- 생성된 세션 ID와 세션에 보관할 값(
memberA)을 서버의 세션 저장소에 보관한다.

클라이언트와 서버는 결국 쿠키로 연결이 되어야 한다.
- 서버는 클라이언트에
mySessionId라는 이름으로 세션 ID만 쿠키에 담아서 전달한다. - 클라이언트는 쿠키 저장소에
mySessionId쿠키를 보관한다.
중요
- 여기서 중요한 포인트는 회원과 관련된 정보는 전혀 클라이언트에 전달하지 않는다는 것이다.
- 오직 추정 불가능한 세션 ID만 쿠키를 통해 클라이언트에 전달한다.
클라이언트의 세션id 쿠키 전달

- 클라이언트는 요청 시 항상
mySessionId쿠키를 전달한다. - 서버에서 클라이언트가 전달한
mySessionId쿠키 정보로 세션 저장소를 조회해서 로그인 시 보관한 세션 정보를 사용한다.
정리
세션을 사용해서 서버에서 중요한 정보를 관리하게 되었다. 덕분에 다음과 같은 보안 문제를 해결할 수 있다.
- 쿠키 값을 변조 가능 → 예상 불가능한 복잡한 세션 ID를 사용한다.
- 쿠키에 보관하는 정보는 클라이언트 해킹 시 털릴 가능성이 있다. → 세션 id가 털려도 여기에는 중요한 정보가 없다.
- 쿠키 탈취 후 사용 → 해커가 토큰을 털어가도 시간이 지나면 사용할 수 없도록 서버에서 세션의 만료시간을 짧게(예: 30분) 유지한다. 또는 해킹이 의심되는 경우 서버에서 해당 세션을 강제로 제거한다.
로그인 처리하기 - 세션 직접 만들기
세션을 직접 개발해서 적용해보자.
세션 관리는 크게 다음 3가지 기능을 제공하면 된다.
- 세션 생성
- sessionId 생성 (임의의 추정 불가능한 랜덤 값)
- 세션 저장소에 sessionId와 보관할 값 저장
- sessionId로 응답 쿠키를 생성해서 클라이언트에 전달
- 세션 조회
- 클라이언트가 요청한 sessionId 쿠키의 값으로, 세션 저장소에 보관한 값 조회
- 세션 만료
- 클라이언트가 요청한 sessionId 쿠키의 값으로, 세션 저장소에 보관한 sessionId와 값 제거
코드 작성
저 기능 3가지를 생각해보면서 직접 세션을 만들어 보자.

hello.login.web.session.SessionManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 세션 관리
* */
@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 생성, 값을 세션에 저장
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());
}
public 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);
}
}
로직 자체는 어렵지 않고
1
2
3
4
5
6
7
8
9
public 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);
}
이렇게 쿠키 배열에서 스트림으로 내가 원하는 걸 찾아내는 방식으로 작성했다.
@Component: 스프링 빈으로 자동 등록한다.ConcurrentHashMap:HashMap은 동시 요청에 안전하지 않다. 동시 요청에 안전한ConcurrentHashMap을 사용.
이제 테스트 해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class SessionManagerTest {
SessionManager sessionManager = new SessionManager();
@Test
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();
}
}
간단하게 테스트를 한 메서드에서 진행했고, HttpServletRequest, HttpservletResponse는 인터페이스라 테스트가 어려워서
MockHttpServletRequest, MockHttpServletResponse을 사용했다.

잘 통과 됐다.
댓글남기기