3 분 소요

 인프런 스프링 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을 사용했다.

잘 통과 됐다.

댓글남기기