컨트롤러 개발 - 상태 관리

상태 관리란 여러 개의 페이지(액션) 사이에서 정보를 유지하기 위한 구조를 말한다. 상태 관리가 필요한 이유는 HTTP의 특성에 기인한다.

HTTP는 클라이언트로부터 요청을 받으면 서버에서 응답을 보내고 끝난다. 따라서 같은 클라이언트에서 요청을 여러 번 보내도 그러한 요청을 동일한 것으로 인식하지 못한다. 즉, HTTP는 무상태 프로토콜(Stateless protocol)로 상태를 유지하지 않는 프로토콜이다.

하지만 애플리케이션 전체 또는 특정한 기능에서 사용자가 누구인지라는 "상태"를 유지해야할 상황은 많다. 그렇지 않으면 페이지에 접근할 때마다 로그인을 해야하기 때문이다.

Rails는 원래 HTTP에 없는 상태 관리 기능을 애플리케이션에 보충하고 아래 기능을 제공하고 있다.

기능

설명

쿠키

브라우저에 저장되는 작은 글자 정보(Rails 이외의 환경에서도 사용할 수 있는 범용적인 형태의 관리 방법)

세션

쿠키, 캐시, 데이터베이스 등의 상태 정보를 저장하는 구조(가장 자주 사용하는 관리 방법)

플래시

현재와 다음 요청에서만 유지되는 특별한 형태의 세션 정보

물론 쿼리 정보 또는 숨겨진 필드 등의 기능도 상태 관리의 한 방법인데 이것들은 사용이 굉장히 한정적이므로 제외해본다.

쿠키 추출과 설정하기 - cookies 메서드

쿠키는 클라이언트 쪽에 저장되는 간단한 텍스트 파일이다. 쿠키를 사용하면 여러 페이지에 걸쳐 사용자를 식별하거나, 클라이언트 단위로 정보를 관리하는 것이 가능하다.

쿠키를 설정할 때는 cookies 메서드를 사용한다.

cookies 메서드에 설정 가능한 옵션과 함께 알아둬야할 사실을 메모하자면 아래와 같다.

  • values, expires는 사실상 반드시 입력해야 하는 옵션이다. expires 옵션을 생략하면 사용자의 브라우저가 닫힐 때 쿠키도 함께 사라진다는 점에서 주의해야한다.

  • domain과 path는 쿠키가 유효한 도메인과 경로를 설정한다. 도메인 이름을 공유하는 서버를 이용하는 경우, 쿠키가 다른 사용자에게 유출될 수 있으므로 path는 반드시 지정해둬야 한다.

  • secure는 통신을 암호화할 경우에는 true로 입력한다. 이렇게 하면 암호화되지 않은 페이지가 혼재하는 경우에도 암호화된 쿠키는 송신되지 않으므로 안전하다.

  • httponly는 HTTP 통신으로만 접근할 수 있는 HTTP 쿠키를 유효화한다. 이렇게 하면 자바스크립트로부터 쿠키에 접근할 수 없게 되므로 크로스 사이트 스크립팅 취약성을 이용한 쿠키 도청을 막을 수 있다.

permanentencrypted 메서드를 사용하면 영속화 쿠키와 암호화 쿠키를 생성할 수 있다.

영속화 쿠키는 정확히 말하면 유효 기간이 20년으로 설정된 쿠키이다. expire 옵션이 지정된 경우에도 permanent 설정이 우선된다. 편리하지만 유효 기간이 긴 쿠키는 보안적인 측면에서 좋지 않으므로 남용하면 안된다.

암호화 쿠키를 사용하면 쿠키를 보다 안전하게 사용할 수 있다. 이 때 쿠키 암호화나 해독에 사용하는 토큰을 config/initializers/secret_token.rbRails.application.config.secret_key_base 매개 변수로 설정해둬야한다.

아래 처럼 작성하면 영속성 쿠키와 암호화 쿠키를 동시에 설정할 수 있다.

cookies.permanent.encrypted[:email] = { value: ...}

세션 사용 - session 메서드

페이지끼리의 정보를 공유하기 위해 쿠키 외에도 세션이라는 것을 제공한다. 세션이란, 사용자(클라이언트) 단위로 정보를 관리하기 위한 기능으로 최근의 프레임워크는 대부분 지원하고 있는 기능이다.

Rails의 세션은 기본적으로 쿠키에 모든 정보를 저장하므로 표준 형태에서는 쿠키와 거의 차이가 없다. 하지만 세션에서는 설정을 변경해서 저장 대상(데이터 저장소)을 변경할 수 있다는 장점이 있다. 브라우저가 열려있는 동안 데이터를 유지하고 싶은 경우에는 일단 세션을 우선적으로 사용하자.

세션 정보를 저장하는 데이터 저장소는 아래와 같다.

저장 대상

설명

쿠키(CookieStore)

클라이언트 사이드 클라이언트의 쿠키로 세션을 저장(기본). 고속으로 동작하지만 클라이언트 측에 저장하므로 비밀 정보 저장에는 적합하지 않음. 저장 사이즈도 4KB로 제한됨.

캐시(CacheStore)

서버 사이드 애플리케이션 캐시에 세션을 저장. 그다지 중요하지 않은 수명이 짧은 데이터를 저장할 때 사용.

데이터베이스(ActiveRecordStore)

서버 사이드 액티브 레코드로 접근할 수 있는 데이터베이스에 세션을 저장. 서버에 저장하므로 비밀 정보를 저장할 때 적합함.

세션 처리는 아래 과정을 거친다.

  1. 첫 번째 요청 (클라이언트 -> 서버)

  2. 세션 ID를 발급 (서버 -> 클라이언트)

  3. 두 번째 이후부터의 요청 (클라이언트 -> 서버)

  4. 세션 ID를 키로 세션 정보를 추출 (서버)

  5. 세션 정보를 기반으로 생성된 결과를 출력 (서버 -> 클라이언트)

포인트는 서버 사이트에서 세션을 관리할 때도 키는 쿠키를 사용한다는 점이다. 이렇게 클라이언트에게 발행되는 쿠키를 세션 ID라고 부른다. 서버 측에서는 클라이언트로부터 발송된 세션 ID를 키로 사용자를 식별하고, 해당 사용자의 세션 정보를 추출한다.

flash 메서드

리다이렉트 처리를 전후로 일시적으로만 데이터를 저장하고 싶을 때 쓴다.

예를 들어 데이터를 추가 또는 수정하고 그 결과를 리다이렉트 대상의 화면에서 "~의 저장에 성공했습니다."와 같이 표시하고 싶은 경우!

플래시는 redirect_to 메서드와 함께 사용해야한다. 아래처럼 redirect_to 메서드에 notice 옵션으로 지정한 문자열은 리다이렉트 대상에 플래시로 전달된다.

format.html { redirect_to book, notice: 'Book was successfully created.'}

필터

필터는 액션 메서드가 실행되기 이전과 이후에 부가 처리를 위해서 사용한다. 여러 액션에 공통적인 처리를 액션마다 개별적으로 작성할 필요 없이, 한꺼번에 작성할 수 있다.

before, after, around 필터를 사용하자.

필터는 정의된 컨트롤러 또는 해당 컨트롤러에서 파생된 컨트롤러에 적용된다. 부모 클래스 -> 자식 클래스 순서로 필터가 실행된다. 물론 skip_xxxx_action 메서드로 부모 컨트롤러로부터 전달된 필터를 제거할 수도 있다.

애플리케이션 공통 기능 정의 - Application 컨트롤러

Application 컨트롤러는 모든 컨트롤러의 부모 클래스가 되며, 모든 컨트롤러의 근원이 된다는 의미에서 루트 컨트롤러라고도 불린다.

원칙적으로 Application 컨트롤러에는 애플리케이션의 공통 기능만 작성하는 것이 좋다.

예시

  • 각각의 컨트롤러에서 사용할 헬퍼메서드

  • 모든(또는 대부분) 컨트롤러에서 이용하는 필터

  • 애플리케이션의 공통 설정

공통 필터로 로그인 기능을 구현하는 것도 좋은 예시다. Application 컨트롤러의 before 필터에 check_logined 를 설정하면 모든 컨트롤러에서 인증 기능을 사용할 수 있다. 이 때, login_controller에서만큼은 skip_before_action :check_logined하도록 해야한다. 그렇지 않으면 로그인 페이지에서 로그인 페이지로 무한 리다이렉트 될 수 있으므로 주의해야한다.

애플리케이션 전체 또는 여러 개의 컨트롤러나 모델에서 공통으로 사용하는 로직이 있다면 이런 공통 로직을 두는 표준 장소가 있다. 공통 로직을 모듈로 만들어 아래 경로에 넣어두자.

  • app/controllers/concerns

  • app/models/concerns

Last updated