티스토리 뷰

프로그래밍 공부/Ruby on Rails : Gem

Ruby on Rails : JWT with devise [gem : jwt]

마음 따뜻한 개발자, 나른한 하루 2019. 12. 6. 23:42

이번 글에서는 Devise 로그인 인증에 있어 Json Web Token(JWT)방식을 활용한 로그인 방식에 대해 다뤄보고자 합니다.

시작전에 앞서, JWT가 뭔지 간단히 소개하고 설명을 이어나가겠습니다.

 

 

1. 과거의 인증방식 : 세션

이미지 출처 : 자유로운 우랑우탄 (이호연 블로그) / 쉽게 알아보는 서버 인증 (세션/쿠키 인증 Process)

세션유저 고유 식별자로서, 서버가 관리합니다.

서버가 관리한다는 특징 때문에 결국은 서버가 개개인 별로 구분할 세션을 가지고 있다는 건데 서버는 그럼 결국 모든 유저들에 대한 세션을 관리를 해야 할 필요가 있었습니다. 사람이 적으면 상관이 없었으나, 사람이 많은 서비스 같은 경우에는 세션 관리에 있어 메모리에 많은 부담을 져야 할 필요가 있었습니다.

 

또한 만약에 서버 트래픽이 크게 증가해서 트래픽을 분산작업을 하는 로드밸런싱 방식으로 운영이 될 경우, 각 로드밸런싱 되는 서버간에 세션을 동기화(공유)해야 하는 문제점도 있어서 세션 관리에 있어 여러모로 귀찮은 문제가 많기도 했습니다.

 

2. 토큰 기반 인증

이미지 출처 : 자유로운 우랑우탄 (이호연 블로그) / 쉽게 알아보는 서버 인증 (토큰인증 Process)

토큰 기반 인증이 도입됨으로서 드디어 서버가 사용자의 정보에 대해 기억을 해야하는 부담이 사라지게 됩니다.

이러한 특징을 stateless 하다고 합니다.

 

일단 여기까지 설명해놓고 봤을 때, 이제 정보 관리의 주체는 서버가 아닌 '내'가 된다는 것이 큰 변화입니다.

 

또한 토큰값을 공유 할 수 있습니다. 이 특징을 많이 사용되는 대표적인 기능으로서는 Oauth 인데요, 쉽게 말하면 잡코리아 사이트 내 로그인 때 있어 페이스북 로그인, 네이버 아이디 로그인 등을 생각하시면 됩니다.

 

 

잡코리아 사이트를 로그인을 할 때, 네이버 혹은 페이스북 계정을 이용해서 로그인이 이루어짐에 있어 계정 검증을 토큰이라는 것을 통해서 검증이 이루어집니다.

 

3. JWT 인증

이미지 출처 : 오픈나루(Open naru) / JWT 인증 처리과정

JWT는 Json Web Token의 줄임말로서, 나(사용자)의 모든 정보를 Json 방식으로 갖고있습니다.

 

JWT의 가장 큰 이점은 과거에는 매번 서버에 ID/비밀번호를 보내야 했다면, JWT는 처음에만 ID/정보를 서버에 보내고, 그 이후로 부터는 서버로 부터 발급받은 Token 하나만으로 사용자를 판별해낸다는 것입니다.

 

기존의 토큰 방식에 암호화 방식이 추가가 되었고, 서버와 클라이언트 간의 인증에 있어 HTTP Header, HTTP Parameter, Restful API 방식 등 다양한 방식으로 데이터를 전달할 수 있습니다.

RestfulAPI 방식을 통해 Devise 로그인 처리 인증

 

JWT는 3가지의 구성이 합쳐진 개념을 가지고 있습니다 :

JWT는 위 사진과 같이 구성이 되어있는데요,

 

 

  • Header : 토큰의 암호화 방식에 대한 정보가 담겨져 있습니다.
  • Payload : 토큰에 포함될 정보들을 가지고 있습니다. Payload 내에는 또 여러 정보들이 담겨져 있는데, 이 정보들 각각을 '클레임' 이라고 부릅니다. JWT는 토큰을 Decode를 통해 얻을 수 있는 Payload의 클레임에 기록된 최소 정보만을 가지고 유저가 누군지 판별해 내는겁니다.
  • Signature : 토큰의 무결성 검증을 보증함으로서, 토큰이 변조가 되지 않았음을 증명합니다.

JWT 토큰에 대해 인코딩/디코드 테스트를 할 수 있는 사이트도 있으니 참고바랍니다 :)

https://jwt.io/

 

하지만 JWT에도 치명적인 단점이 몇 가지 존재합니다.

 

  • JWT에는 토큰 만료시간이 주어지는데, 토큰이 만료되기 전에 토큰을 탈취당하면 그냥 털린다고 봐야 합니다. (이에 대한 해결책으로 Refresh Token이 존재하긴 하나, 본문에서는 설명 생략하겠습니다.)

  • JWT는 세션에 비해 길이가 깁니다. 그렇다보니 요청이 많아질 수록 자원낭비가 심해질 수 있다는 문제가 생깁니다.

  • JWT는 인코딩/디코더가 가능하다 보니 Payload 내에는 중요한 정보를 담지 않는것이 권유됩니다.

 

 

  • Devise 인증 with JWT

 실습 전 참고 

1. 해당 과정은 Devise Gem이 미리 깔려 있다는 전재하에 진행됩니다. [참고 : Devise Gem 설치]

2. 해당 과정의 확인을 위해 Postman을 미리 설치해주세요 [클릭]

 

이제 본격적으로 JWT를 활용한 Devise 인증법에 대해 다뤄보겠습니다.

앞서 말했다 싶이 저희는 JWT의 특징 중 하나인 Restful API를 이용한 방식으로 인증을 해볼겁니다.

 

1.  Gemfile  에 다음 내용을 넣어주세요.

## Devise JWT 인증
gem 'jwt'

## 환경변수 사용
gem 'figaro'

그리고 터미널에 아래 명령어를 입력해서 Gem을 설치합니다.

bundle install

 

2.  lib  폴더 안에  json_web_token.rb  파일을 생성 후, 다음 내용을 작성합니다.

 코드리뷰  후에 JWT 키를 해석(Decode) 할 때 쓰일 메소드 입니다.

class JsonWebToken
  def self.decode(token)
    return HashWithIndifferentAccess.new(JWT.decode(token, ENV["SECRET_KEY_BASE"])[0])
  rescue
    nil
  end
end

 

3.  config/initializers/jwt.rb  파일을 생성 후, 다음 내용을 추가합니다.

 코드리뷰  처음에 서버가 켜질 때,  lib/json_web_token.rb  파일을 init(초기화) 합니다.

require 'json_web_token'

 

4. 터미널에 다음 명령어를 입력해서 랜덤 secret 값을 얻어냅니다.

rake secret

 

5.  config  폴더 속에  application.yml  파일을 생성해낸 후, 환경변수를 등록합니다.

# SECRET_KEY_BASE: "×××××××××××××××××××××××××××××××××××××××××××××××"
SECRET_KEY_BASE: "10e38994b4067f8d65ab425c911e162d0144e0e33769f510c5265f0c0224285678a52f54f4c41abd3fdf5266ae918d2ccd8358bf94319bfa320de64efdbea095"

후에 위 SECRET Key는 JWT 토큰 복호화 키로 사용됩니다.

 

6. API 서버는 CSRF 검증이 필요없습니다.

 app/controllers/application_controller.rb  파일로 이동 후, 다음 코드를 추가해주세요.

skip_before_action :verify_authenticity_token
class ApplicationController < ActionController::Base
    skip_before_action :verify_authenticity_token
end

 

7.  app/controllers/application_controller.rb  파일에서 다음 내용을 추가합니다.

attr_reader :current_user

protected

## JWT 토큰 검증
def jwt_authenticate_request!
  ## 토큰 안에 user id 정보가 있는지 확인 / 없을 시 error response 반환
  unless user_id_in_token?
    render json: { errors: ['Not Authenticated'] }, status: :unauthorized
    return
  end

  ## Token 안에 있는 user_id 값을 받아와서 User 모델의 유저 정보 탐색
  @current_user = User.find(auth_token[:user_id])
  rescue JWT::VerificationError, JWT::DecodeError
  render json: { errors: ['Not Authenticated'] }, status: :unauthorized
end

private

## 헤더에 있는 정보 중, Authorization 내용(토큰) 추출
def http_token
  http_token ||= if request.headers['Authorization'].present?
  	request.headers['Authorization'].split(' ').last
  end
end

## 토큰 해석 : 토큰 해석은 lib/json_web_token.rb 내의 decode 메소드에서 진행됩니다.
def auth_token
  auth_token ||= JsonWebToken.decode(http_token)
end

## 토큰 해석 후, Decode 내용 중 User id 정보 확인
def user_id_in_token?
  http_token && auth_token && auth_token[:user_id].to_i
end

 

8.  app/controllers  폴더에서  authentication_controller.rb  파일을 생성 후, 다음 내용을 추가합니다.

class AuthenticationController < ApplicationController

  ## JWT 토큰 생성을 위한 Devise 유저 정보 검증
  def authenticate_user
    ## body로 부터 받은 json 형식의 params를 parsing
    json_params = JSON.parse(request.body.read)
	  
    user = User.find_for_database_authentication(email: json_params["auth"]["email"])
    if user.valid_password?(json_params["auth"]["password"])
      render json: payload(user)
    else
      render json: {errors: ['Invalid Username/Password']}, status: :unauthorized
    end
  end

  private

  ## Response으로서 보여줄 json 내용 생성 및 JWT Token 생성
  def payload(user)
	## 해당 코드 예제에서 토큰 만료기간은 '30일' 로 설정
	@token = JWT.encode({ user_id: user.id, exp: 30.days.from_now.to_i }, ENV["SECRET_KEY_BASE"])	
	@tree = { :"JWT token" => @token, :userInfo => {id: user.id, email: user.email} } 
	 
	return @tree
  end
  
end

 

9. 새로운 Controller을 생성합니다.

rails g controller apis test

 

10. 새로 만들어낸 Apis Controller에 다음과 같이 내용을 입력해주세요.

class ApisController < ApplicationController
  ## API Controller 액션에 방문 전, application Controller의 jwt_authenticate_request! 메소드 발동
  before_action :jwt_authenticate_request!

  ## test 액션에 방문에 있어 토큰 검증 성공 시, Token 인증 성공 메세지가 응답될 것입니다.
  def test
	@dataJson = { :message => "[Test] Token 인증 되었습니다! :D", :user => current_user }
	render :json => @dataJson, :except => [:id, :created_at, :updated_at]
  end
  
end

 

9.  config/routes.rb  파일을 열람 후, 다음과 같이 라우터 정의를 내려주세요.

devise_for :users  
post 'auth_user' => 'authentication#authenticate_user'
get 'apis/test'

 

10. 이제 모든 준비가 끝났습니다!

Postman 프로그램을 통해 테스트를 한번 해보겠습니다.

 

1) 계정을 생성합니다.

User.create(email: "kbs4674@naver.com", password: "654321")

위 계정을 통해 JWT 토큰 통신을 시도해 보겠습니다.

 

2) JWT 토큰 생성

1. Method : POST
2. Data : Parameter
  1) email
  2) password

 

올바른 정보 입력 시 (Response 200)

## Body Request

{ "auth": { "email": "kbs4674@naver.com", "password": "654321" } }

 

유효하지 않은 정보 입력 시 (Response 401)

## Body Request

{ "auth": { "email": "kbs4674@naver.com", "password": "123456" } }

 

3) JWT 토큰 인증을 거친 홈페이지 접근

1. Method : Get
2. Data : Header
  1) Authorization
    - Token

 

올바른 인증

 

올바르지 않은 인증 (옳지 않은 토큰 난수 및 토큰 유효기간 만료)

좌 : 토큰 만료(expioration) / 우 : 토큰 Value 조작

 

 

  • Rest API 간단 규칙

해당 규칙은 https://yuda.dev/250https://meetup.toast.com/posts/92 글을 참고했습니다.

 

1. Params, Header, Body

1) Params

https://chm4674.run.goorm.io/apis/test?email=kbs4674@naver.com

Params는 흔하면서도 간단하게 URI 뒤에 Key / value를 붙이는 개념입니다.

하지만 이 방법은 별로일 수 있는게 Key가 URL에 노출됩니다.

 

인증 용도로서 사용은 부적합하고,

정렬 혹은 검색이 필요할 때 사용하도록 권장되고 있습니다.

 

2) Header

Header은 Key와 Value로만 표현이 가능하고, 보통 인증 혹은 인증 후 권한 확인을 위해 사용됩니다.

 

3) Body

{ "auth": { "email": "kbs4674@naver.com", "password": "123456" } }

API 통신을 통한 데이터 요청에 있어, 어떤 특정 데이터에 대한 응답을 받고자 할 때, 특정 정보를 요청할 때 사용됩니다.

보통은 Key, Value 표현에 있어선 JSON 형식으로 표현을 많이합니다.

 

2. Restful API Method 규칙

Method 의미
GET 데이터를 조회할 때 사용합니다.
POST 데이터를 등록할 때 사용합니다.
PUT 데이터를 수정할 때 사용합니다.
DELETE 데이터를 지울 때 사용합니다.

 

3. Restful Response Code

응답코드 의미
200 데이터 작업 성공
201 데이터 작업 성공 (Post 작업인 경우)
400 클라이언트가 부적절한 요청을 한 경우
401 클라이언트가 인증되지 않은 상태에서 요청을 한 경우 (비로그인 상태에서 요청 등)
403 인증여부에 관계없이 응답하고 싶지 않은 요청을 해낸 경우
404 존재하지 않는 URI
405 정의되지 않은 Method를 통해 요청한 경우
500 서비스(서버) 장애, 절대 사용자에게 눈에띄게 하지 말라.

 

4. 기타 Restful API 규칙

다음 부록 내용을 참고해주세요.

 부록  [학학이] RESTful API 설계 가이드

 

 

  • 자료 참고

1. JWT 개념

2. JWT with Devise 설치법 [클릭]

3. Devise-JWT Github 문서 [클릭]

댓글
댓글쓰기 폼
공지사항
Total
54,001
Today
29
Yesterday
104
링크
«   2020/11   »
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          
글 보관함