Spring REST API에서 세션에 정보 저장

0

프로젝트

목록 보기
5/14
post-thumbnail

들어가기에 앞서..

최근, 에브리 타임에서 아래와 같은 글이 있어서, 한번 같이 머리를 대고 으쌰으쌰 해보자는 생각으로 글에 달려 있는 오픈 카카오톡 방에 입장했다.

게시글의 하단에는 대부분의 레퍼런스들은 MVC 패턴으로 작성된 상태인데, 자신의 프로젝트는 View가 빠져있다고 적혀져있었다.

그래서 나는 아 Spring으로 REST API와 같은 형태로 서버를 구성하고 있구나~ 여기서 원래 jwt 토큰 방식으로 유저 정보의 일부를 넘겨 주고, 해당 토큰을 사용해서 로그인 여부와 같은 것들을 확인할 수 있는데 이걸 세션 방식으로 사용하고 싶구나~ 라는 것을 알아차렸다.
(실제로는 아무래도 소셜 로그인을 구현하는 것도 문제였던 것 같다)

이 게시글에는 상품도 걸려있었는데!! 물론 그것 땜은 아니고, 최근에 세션에 대한 개념을 조금 확립했다고 생각해서 도움을 주고싶다는 생각으로 들어가게 되었다.

세션은?

사용자가 웹 브라우저를 통해 웹서버에 접속한 시점으로부터 웹 브라우저를 종료하여 연결을 끝내는 시점까지, 같은 사용자로부터 오는 일련의 요청을 하나의 상태로 보고, 그 상태를 일정하게 유지하는 기술.
즉, 방문자가 웹 서버에 접속해있는 상태를 하나의 단위로 보고 그것을 세션이라고 한다.

웹 앱에서 전반적인 기능을 수행할 때, 공유하는 공간이라고 생각하면 편할 것 같다. 내 정보를 저장해놓으면 웹 웹에서는 이 정보를 활용해서 다른 여러 페이지에서 내 정보를 보여줄 수 있다!

질문에 대한 해결법

나는 가장 먼저 톰캣의 세션을 생각했다. 기본적으로 자바 웹 앱을 동작시킬 때 웹 서버 톰캣을 사용하고 톰캣은 세션을 쉽게 지원해주기 때문에, 톰캣 세션에 저장하자고 생각했다.

기본적으로 톰캣에서는 세션을 열면 응답의 쿠키에 JSESSIONID 라는 클라이언트의 세션 id를 보내준다.

이것을 어떻게 활용할 수 있냐~ 아주 간단하다. 세션을 너무 어렵게 생각하는 사람들이 있을 수도 있는데 이 게시글을 통해 조금 쉽게 생각했으면 좋겠다.

Request 객체 생성

public class Person implements Serializable {
    private String uid;

    public Person() {}

    public Person(String uid) {
        this.uid = uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getUid() {
        return uid;
    }

    @Override
    public String toString() {
        return "Person{" +
                "uid='" + uid + '\'' +
                '}';
    }
}

먼저, Request로 uid라는 기본적인 내용을 받을건데, 해당 내용을 객체화 하였다. 시리얼라이즈가 되어있는 이유는 추후에 외부 저장소에다 이 객체를 담을 일이 있을 때, 직렬화를 해주기위해서이다. 만약 톰캣의 세션에다가 담을 것이라면 굳이 할 필요가 없다.

이것저것 메소드가 많은 이유는, Lombok이라는 녀석이 셋터와 겟터 뿐만 아니라 toString역시 자동으로 작성하여준다!!

세션 생성 요청을 받는 컨트롤러

	@PostMapping("/session_create")
    public Map<String, Object> createSession(@RequestBody Person req, HttpSession session) {
        Map<String, Object> data = new HashMap<>();

        session.setAttribute("user", req);

        data.put("sessionId", session.getId());

        return data;
    }

/session_create를 하게 되면 세션을 생성하고, 세션에다가 request로 받은 Person 객체를 담는다. 그리고 확인을 위해 sessionId를 응답으로 반환하는 모습이다.

현재 createSession() 메소드의 인자에 HttpSession이라는 것이 들어가있는데, 이것은 request 헤더에 만약 세션 id가 존재한다면 해당 세션을 불러오고 없으면 세션을 새롭게 생성하는 로직이 자동적으로 이루어진다.

Postman을 사용해서 요청을 보내보면 다음과 같다.

생성된 세션 id가 잘 반환된 모습이다. 아까 말한대로 그럼 쿠키에 JSESSIONID가 들어가 있나? 확인을 해보면

Postman에서 반환받은 id값과 똑같은 세션 id가 JSESSIONID라는 쿠키 값에 들어가있는 것을 확인할 수 있다. 그렇다면 이제 이 쿠키값을 이용해 다시 서버에 요청을 보내고, 세션에 저장된 유저의 id 값을 불러와 보도록 해보자

세션 확인용 컨트롤러

	@GetMapping("/session_check")
    public Map<String, Object> checkSession(HttpSession session) {
        Map<String, Object> data = new HashMap<>();

        Person user = (Person) session.getAttribute("user");;

        data.put("uid", user.getUid());

        return data;
    }

아주 간단하다. HttpSession을 받아서 해당 세션의 user라는 속성으로 저장된 객체를 불러오고 있다. 그리고 이 Person 객체의 uid 값을 확인하기 위해서 다시 반환해준다. 실제로 Postman에서 확인해보면 다음과 같다.

이전에 받은 쿠키를 가지고 요청을 보내니 세션에 저장된 uid: 3을 가진 Person 객체를 잘 불러오는 것을 확인할 수가 있었다.

만약 내가 쿠키로 가지고 있는 JSESSIONID의 세션 id 값이 더 이상 유효하지 않거나 잘못되었다면
위의 사진에서 uid: null과 같이 표시될 것이다.

마지막으로는 세션을 열었으니까, 닫는 과정도 필요하다.

세션 만료용 컨트롤러

세션을 계속적으로 유지하면 안되므로, 사용자가 로그아웃하면 세션을 닫아주어야한다.

	@GetMapping("/session_destroy")
    public Map<String, Object> destroySession(HttpSession session) {
        Map<String, Object> data = new HashMap<>();

        data.put("result", "success");

        session.invalidate();

        return data;
    }

/session_destroy로 요청을 날리면 내가 가지고 있는 세션 id의 세션을 만료시키고 성공이라는 메시지를 날린다.

느낀점

이전에도 API를 구현할 때, REST API의 기본이 stateless 즉, 상태를 유지하지 않아야된다는 생각에 jwt를 사용한적이 있었다. 이 과정은 단순하지만은 않다. 그리고 서버가 말이 stateless지, 실제로는 계속 가지고 있어야 하는 정보들이 무조건 존재하기 때문에 이처럼 세션으로 구현하면 오히려 간단하고 좋다는 생각이 들었다.

그러나 JSESSIONID라는 쿠키의 값은 진짜 말그대로 내 세션 id 그 자체이기 때문에 탈취되면 내 세션의 정보를 마음대로 접근할 수가 있다!! 아무래도 Spring security와 같은 것을 같이 사용하여 세션의 보안을 강화시켜야 되지 않을까 싶다!

profile
최악의 환경에서 최선을 다하기

0개의 댓글