채민균 / 18.11.14(wed)
정보 출처 : [https://velopert.com/2389, https://jwt.io, https://github.com/jwt/ruby-jwt]
JSON Web Token(https://jwt.io/)
{
"typ": "JWT",
"alg": "HS256"
}iss: 토큰 발급자 (issuer)
sub: 토큰 제목 (subject)
aud: 토큰 대상자 (audience)
exp: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1530839567771) 언제나 현재 시간보다 이후로 설정되어있어야한다.
nbf: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않는다.
iat: 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 할 수 있다.
jti: JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용됩니다. 일회용 토큰에 사용하면 유용하다.
{
"https://velopert.com/jwt_claims/is_admin": true
}{
"user_id": 91247
}
{
"iss": "nbt.com",
"exp": "1485270000",
"https://velopert.com/jwt_claims/is_admin": true,
"user_id": "117102",
"username": "mingyun"
}
회원인증에 필요한 것.
# gem for jwt token
gem 'jwt'class TokenManager < ApplicationRecord
# User model 참고.
#
# class User < ApplicationRecord
# has_one :token_manager
# end
belongs_to :user
def self.new_token(user_id)
hmac_secret = 'my$ecretK3y'
payload = {exp: (Time.now + 30.minutes).to_i,
iss: 'mingyun.com',
type: 'token',
user_id: user_id}
JWT.encode(payload, hmac_secret, 'HS256')
end
end
# result : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDE5MzAyMDksImlhdCI6MTU0MTkyODQwOSwiaXNzIjoibWluZ3l1bi5jb20iLCJ0eXBlIjoidG9rZW4iLCJ1c2VyX2lkIjoxM30.ppENQL4g8vAoZa-2skqeSH5TDo-5Ie4yf8oNdIyFqI8"$ rails g model TokenManager1.토큰 생성하기
class TokenManager < ApplicationRecord
belongs_to :user
HMAC_SECRET = 'my$ecretK3y'
ISS = 'mingyun.com'
REFRESH_TOKEN = 'refresh_token'
TOKEN = 'token'
ALGORITHM = 'HS256'
# TokenManager migration file 참고.
#
# class CreateTokenManagers < ActiveRecord::Migration[5.2]
# def change
# create_table :token_managers do |t|
# t.text :refresh_token, comment: 'user의 token을 재발급받기 위한 토큰'
# t.belongs_to :user, index: true, comment: '외래키'
# t.timestamps
# end
# end
# end
def self.new_refresh_token(user_id)
payload = {exp: exp(REFRESH_TOKEN),
iss: ISS,
type: REFRESH_TOKEN,
user_id: user_id}
refresh_token = JWT.encode payload, HMAC_SECRET, ALGORITHM
token_manager = find_or_initialize_by(user_id: user_id)
token_manager.refresh_token = refresh_token
token_manager.save
refresh_token
end
def self.new_token(user_id)
payload = {exp: exp(TOKEN),
iss: ISS,
type: TOKEN,
user_id: user_id}
JWT.encode(payload, HMAC_SECRET, ALGORITHM)
end
private
def self.exp(type)
if type == REFRESH_TOKEN
(Time.now + 1.year).to_i
elsif type == TOKEN
(Time.now + 30.minutes).to_i
end
end
end
2.리프레쉬 토큰 생성하기
class TokenManager < ApplicationRecord
belongs_to :user
HMAC_SECRET = 'my$ecretK3y'
ISS = 'mingyun.com'
REFRESH_TOKEN = 'refresh_token'
TOKEN = 'token'
ALGORITHM = 'HS256'
def self.usable?(token)
return false if token.blank?
begin
JWT.decode(token, HMAC_SECRET, true, {iss: ISS, verify_iss: true, algorithm: ALGORITHM})
true
rescue JWT::InvalidIssuerError, JWT::ExpiredSignature, JWT::InvalidIatError
false
end
end
def self.new_token(user_id)
payload = {exp: exp(TOKEN),
iss: ISS,
type: TOKEN,
user_id: user_id}
JWT.encode(payload, HMAC_SECRET, ALGORITHM)
end
def self.new_refresh_token
payload = {exp: exp(REFRESH_TOKEN),
iat: iat,
iss: ISS,
type: REFRESH_TOKEN,
user_id: user_id}
refresh_token = JWT.encode payload, Rails.application.config.hmac_secret, ALGORITHM
token_manager = find_or_initialize_by(user_id: user_id)
token_manager.refresh_token = refresh_token
token_manager.save
refresh_token
end
private
def self.exp(type)
if type == REFRESH_TOKEN
(Time.now + 1.year).to_i
elsif type == TOKEN
(Time.now + 30.minutes).to_i
end
end
end
3. 토큰인증
class TokenManager < ApplicationRecord
belongs_to :user
HMAC_SECRET = 'my$ecretK3y'
ISS = 'mingyun.com'
REFRESH_TOKEN = 'refresh_token'
TOKEN = 'token'
ALGORITHM = 'HS256'
# decode result
# [
# [0] {
# "exp" => 1541932110,
# "iss" => "mingyun.com",
# "type" => "token",
# "user_id" => 15
# },
# [1] {
# "typ" => "JWT",
# "alg" => "HS256"
# }
# ]
def self.user_id(token)
return nil if token.blank?
if usable? token
decoded_token = JWT.decode(token, HMAC_SECRET, true, {algorithm: ALGORITHM})
decoded_token[0]['user_id']
else
nil
end
end
def self.usable?(token)
return false if token.blank?
begin
JWT.decode(token, HMAC_SECRET, true, {iss: ISS, verify_iss: true, algorithm: ALGORITHM})
true
rescue JWT::InvalidIssuerError, JWT::ExpiredSignature
false
end
end
def self.new_token(user_id)
payload = {exp: exp(TOKEN),
iss: ISS,
type: TOKEN,
user_id: user_id}
JWT.encode(payload, HMAC_SECRET, ALGORITHM)
end
def self.new_refresh_token
payload = {exp: exp(REFRESH_TOKEN),
iat: iat,
iss: ISS,
type: REFRESH_TOKEN,
user_id: user_id}
refresh_token = JWT.encode payload, HMAC_SECRET, ALGORITHM
token_manager = find_or_initialize_by(user_id: user_id)
token_manager.refresh_token = refresh_token
token_manager.save
refresh_token
end
private
def self.exp(type)
if type == REFRESH_TOKEN
(Time.now + 1.year).to_i
elsif type == TOKEN
(Time.now + 30.minutes).to_i
end
end
end
4. token 으로 부터 payload 뽑아내기
이렇게 처리해두면, 유저가 jwt를 가지고 서버에 요청을 했을 때, 우리가 발행한 토큰이면서 만료되지 않았으면 요청받은 작업을 처리하면 된다.
토큰이 만료되었다면 리프레쉬 토큰으로 토큰을 재발급 받으면 된다.
리프레쉬 토큰이 만료되었다면 유저가 다시 로그인 flow 를 타야한다.
로그아웃은 서버에서 따로 처리할 필요가 없다.