티스토리 뷰
이번 글에서는 Devise 로그인 인증에 있어 Json Web Token(JWT)방식을 활용한 로그인 방식에 대해 다뤄보고자 합니다.
시작전에 앞서, JWT가 뭔지 간단히 소개하고 설명을 이어나가겠습니다.
1. 과거의 인증방식 : 세션
세션은 유저 고유 식별자로서, 서버가 관리합니다.
서버가 관리한다는 특징 때문에 결국은 서버가 개개인 별로 구분할 세션을 가지고 있다는 건데 서버는 그럼 결국 모든 유저들에 대한 세션을 관리를 해야 할 필요가 있었습니다. 사람이 적으면 상관이 없었으나, 사람이 많은 서비스 같은 경우에는 세션 관리에 있어 메모리에 많은 부담을 져야 할 필요가 있었습니다.
또한 만약에 서버 트래픽이 크게 증가해서 트래픽을 분산작업을 하는 로드밸런싱 방식으로 운영이 될 경우, 각 로드밸런싱 되는 서버간에 세션을 동기화(공유)해야 하는 문제점도 있어서 세션 관리에 있어 여러모로 귀찮은 문제가 많기도 했습니다.
2. 토큰 기반 인증
토큰 기반 인증이 도입됨으로서 드디어 서버가 사용자의 정보에 대해 기억을 해야하는 부담이 사라지게 됩니다.
이러한 특징을 stateless 하다고 합니다.
일단 여기까지 설명해놓고 봤을 때, 이제 정보 관리의 주체는 서버가 아닌 '내'가 된다는 것이 큰 변화입니다.
또한 토큰값을 공유 할 수 있습니다. 이 특징을 많이 사용되는 대표적인 기능으로서는 Oauth 인데요, 쉽게 말하면 잡코리아 사이트 내 로그인 때 있어 페이스북 로그인, 네이버 아이디 로그인 등을 생각하시면 됩니다.
잡코리아 사이트를 로그인을 할 때, 네이버 혹은 페이스북 계정을 이용해서 로그인이 이루어짐에 있어 계정 검증을 토큰이라는 것을 통해서 검증이 이루어집니다.
3. JWT 인증
JWT는 Json Web Token의 줄임말로서, 나(사용자)의 모든 정보를 Json 방식으로 갖고있습니다.
JWT의 가장 큰 이점은 과거에는 매번 서버에 ID/비밀번호를 보내야 했다면, JWT는 처음에만 ID/정보를 서버에 보내고, 그 이후로 부터는 서버로 부터 발급받은 Token 하나만으로 사용자를 판별해낸다는 것입니다.
기존의 토큰 방식에 암호화 방식이 추가가 되었고, 서버와 클라이언트 간의 인증에 있어 HTTP Header, HTTP Parameter, Restful API 방식 등 다양한 방식으로 데이터를 전달할 수 있습니다.
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
올바른 인증
올바르지 않은 인증 (옳지 않은 토큰 난수 및 토큰 유효기간 만료)
-
Rest API 간단 규칙
해당 규칙은 https://yuda.dev/250 및 https://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 문서 [클릭]
'프로그래밍 공부 > Ruby on Rails : Gem' 카테고리의 다른 글
Ruby on Rails : 공공 데이터(예시 : 코레일) API 연동하기 (1) | 2019.12.26 |
---|---|
Ruby on Rails : 세션이 유지된 로그인 [Gem : Mechanize] (0) | 2019.12.16 |
Ruby on Rails : 가상 브라우저를 활용한 크롤링 [Gem : selenium-webdriver] (4) | 2019.12.05 |
Ruby on Rails : Nokogiri 크롤링 [Gem : nokogiri] (0) | 2019.12.05 |
Ruby on Rails : DB 백업하기 (PostgresDB 기준 설명) [Gem : backup, whenever] (0) | 2019.12.05 |