Rails에서 고유 한 토큰을 만드는 가장 좋은 방법은?


156

여기 내가 사용하는 것이 있습니다. 토큰은 반드시 추측 할 필요는 없으며 다른 것보다 짧은 URL 식별자와 비슷하며 짧게 유지하고 싶습니다. 온라인에서 찾은 몇 가지 예를 따랐으며 충돌이 발생하면 아래 코드가 토큰을 다시 만들 것이라고 생각 하지만 확실하지 않습니다. 그래도 가장자리가 조금 거칠어지기 때문에 더 나은 제안이 궁금합니다.

def self.create_token
    random_number = SecureRandom.hex(3)
    "1X#{random_number}"

    while Tracker.find_by_token("1X#{random_number}") != nil
      random_number = SecureRandom.hex(3)
      "1X#{random_number}"
    end
    "1X#{random_number}"
  end

토큰에 대한 내 데이터베이스 열은 고유 인덱스이며 validates_uniqueness_of :token모델 에서도 사용 하고 있지만 앱에서 사용자의 작업에 따라 자동으로 배치로 생성되기 때문에 (주문을하고 토큰을 구매해야 함) 앱에 오류가 발생하는 것은 불가능합니다.

충돌 가능성을 줄이고 마지막에 다른 문자열을 추가하거나 시간 또는 그와 같은 것을 기반으로 생성 된 문자열을 추가 할 수는 있지만 토큰이 너무 오래 걸리는 것을 원하지 않습니다.

답변:


333

-업데이트-

2015 년 1 월 9 일 현재이 솔루션은 Rails 5 ActiveRecord의 보안 토큰 구현으로 구현되었습니다 .

-레일 4 & 3-

나중에 참조 할 수 있도록 안전한 임의 토큰을 만들고 모델의 고유성을 보장하십시오 (Ruby 1.9 및 ActiveRecord 사용시).

class ModelName < ActiveRecord::Base

  before_create :generate_token

  protected

  def generate_token
    self.token = loop do
      random_token = SecureRandom.urlsafe_base64(nil, false)
      break random_token unless ModelName.exists?(token: random_token)
    end
  end

end

편집하다:

@kain 제안, 나는 대체, 합의 begin...end..whileloop do...break unless...end이전의 구현이 미래에서 제거 얻을 수 있기 때문에이 대답.

편집 2 :

Rails 4와 관련하여이 문제를 우려로 옮기는 것이 좋습니다.

# app/models/model_name.rb
class ModelName < ActiveRecord::Base
  include Tokenable
end

# app/models/concerns/tokenable.rb
module Tokenable
  extend ActiveSupport::Concern

  included do
    before_create :generate_token
  end

  protected

  def generate_token
    self.token = loop do
      random_token = SecureRandom.urlsafe_base64(nil, false)
      break random_token unless self.class.exists?(token: random_token)
    end
  end
end

begin / while을 사용하지 말고 loop / do를 사용하십시오
kain

@kain loop do이 경우 begin...while( "do ... while"유형의 루프) 대신 어떤 이유 ( "while ... do"유형의 루프)를 사용해야합니까 (루프가 한 번 이상 실행되어야하는 경우 )?
Krule

7
random_token이 루프 내에서 범위 지정 되므로이 정확한 코드는 작동하지 않습니다.
Jonathan Mui

1
@Krule 이제 이것을 관심사로 바 꾸었으므로 ModelName메소드에서를 제거하지 않아야 합니까? 어쩌면 그것을 self.class대신 교체 하시겠습니까? 그렇지 않으면 재사용이 쉽지 않습니까?
paracycle

1
이 솔루션은 더 이상 사용되지 않으며 Secure Token은 단순히 Rails 5에서 구현되지만 Rails 4 또는 Rails 3 (이 질문과 관련이 있음)에서는 사용할 수 없습니다.
Aleks

52

Ryan Bates는 베타 초대 에 대한 Railscast 에서 멋진 코드를 사용합니다 . 이것은 40 자의 영숫자 문자열을 생성합니다.

Digest::SHA1.hexdigest([Time.now, rand].join)

3
그래, 나쁘지 않아 나는 일반적으로 URL의 일부로 사용할 훨씬 짧은 문자열을 찾고 있습니다.
Slick23

예, 최소한 읽고 이해하기가 쉽습니다. 베타 초대와 같은 일부 상황에서는 40자가 좋으며 지금까지는 잘 작동합니다.
네이트 버드

12
@ Slick23 당신은 항상 문자열의 일부를 잡을 수 있습니다 :Digest::SHA1.hexdigest([Time.now, rand].join)[0..10]
Bijan

'클라이언트 ID'를 Google 애널리틱스의 측정 프로토콜로 전송할 때 IP 주소를 난독 처리하는 데 사용합니다. UUID 여야하지만 hexdigest주어진 IP에 대해 첫 32 자를 사용합니다.
thekingoftruth

1
32 비트 IP 주소의 경우 @thekingoftruth에 의해 생성 된 모든 가능한 16 진수 요약표를 쉽게 찾을 수 있으므로 해시의 하위 문자열조차도 되돌릴 수 없다고 생각하지 마십시오.
mwfearnley

32

이것은 늦은 응답 일 수 있지만 루프 사용을 피하기 위해 메소드를 재귀 적으로 호출 할 수도 있습니다. 그것은 나에게 약간 깨끗해 보입니다.

class ModelName < ActiveRecord::Base

  before_create :generate_token

  protected

  def generate_token
    self.token = SecureRandom.urlsafe_base64
    generate_token if ModelName.exists?(token: self.token)
  end

end

30

이 기사 에서이 작업을 수행하는 꽤 매끄러운 방법이 있습니다.

https://web.archive.org/web/20121026000606/http://blog.logeek.fr/2009/7/2/creating-small-unique-tokens-in-ruby

내가 가장 좋아하는 것은 다음과 같습니다.

rand(36**8).to_s(36)
=> "uur0cj2h"

첫 번째 방법은 내가하고있는 것과 비슷하지만 rand는 데이터베이스에 독립적이지 않다고 생각 했습니까?
Slick23

그리고 나는 이것을 따르지 않을 것입니다 : if self.new_record? and self.access_token.nil?... 토큰이 아직 저장되지 않았는지 확인하는 것은 무엇입니까?
Slick23

4
기존 토큰에 대한 추가 검사가 항상 필요합니다. 나는 이것이 명백하지 않다는 것을 몰랐다. validates_uniqueness_of :token마이그레이션을 통해 테이블에 고유 인덱스를 추가 하고 추가하십시오.
coreyward

6
블로그 게시물의 저자는 여기! 예 :이 경우에는 항상 단언을 주장하기 위해 db 제약 조건 또는 이와 유사한 것을 추가합니다.
Thibaut Barrère

1
게시물을 찾는 분들을 위해 (존재하지 않는 이상) ... web.archive.org/web/20121026000606/http://blog.logeek.fr/2009/7/...
King'ori Maina

17

독창적 인 것을 원하면 다음과 같이 사용할 수 있습니다.

string = (Digest::MD5.hexdigest "#{ActiveSupport::SecureRandom.hex(10)}-#{DateTime.now.to_s}")

그러나 이것은 32 자의 문자열을 생성합니다.

그러나 다른 방법이 있습니다.

require 'base64'

def after_create
update_attributes!(:token => Base64::encode64(id.to_s))
end

예를 들어 10000과 같은 id의 경우 생성 된 토큰은 "MTAwMDA ="과 같습니다 (그리고 쉽게 ID를 해독 할 수 있습니다.

Base64::decode64(string)

고유 한 문자열을 만드는 방법이 아니라 생성 된 값이 이미 생성되고 저장된 값과 충돌하지 않도록하는 데 더 관심이 있습니다.
Slick23

생성 된 값은 이미 생성 된 값과 충돌하지 않습니다. base64는 결정론 적이므로 고유 한 ID가 있으면 고유 한 토큰이 있습니다.
Esse

나는 갔다 random_string = Digest::MD5.hexdigest("#{ActiveSupport::SecureRandom.hex(10)}-#{DateTime.now.to_s}-#{id}")[1..6]ID 토큰의 ID입니다.
Slick23

11
Base64::encode64(id.to_s)토큰 사용의 목적 을 잃어버린 것 같습니다 . 대부분 토큰을 사용하여 ID를 숨기고 토큰이없는 사람이 리소스에 액세스 할 수 없도록합니다. 그러나이 경우 누군가가 실행하기 만하면 Base64::encode64(<insert_id_here>)즉시 사이트의 모든 리소스에 대한 모든 토큰을 갖게됩니다.
Jon Lemmon

작동하려면 이것으로 변경해야합니다string = (Digest::MD5.hexdigest "#{SecureRandom.hex(10)}-#{DateTime.now.to_s}")
Qasim

14

도움이 될 수 있습니다.

SecureRandom.base64(15).tr('+/=', '0aZ')

첫 번째 인수 '+ / ='에 넣는 것보다 특수 문자를 제거하고 두 번째 인수 '0aZ'에 넣은 문자는 15이며 길이는 here입니다.

그리고 여분의 공백과 줄 바꾸기 문자를 제거하려면 다음과 같은 것을 추가하십시오.

SecureRandom.base64(15).tr('+/=', '0aZ').strip.delete("\n")

이것이 누군가에게 도움이되기를 바랍니다.


3
"+ / ="와 같은 이상한 문자를 원하지 않으면 base64 대신 SecureRandom.hex (10)을 사용하면됩니다.
Min Ming Lo

16
SecureRandom.urlsafe_base64같은 것을 달성합니다.
iterion

7

has_secure_token https://github.com/robertomiranda/has_secure_token 을 사용할 수 있습니다

정말 사용하기 쉽습니다

class User
  has_secure_token :token1, :token2
end

user = User.create
user.token1 => "44539a6a59835a4ee9d7b112b48cd76e"
user.token2 => "226dd46af6be78953bde1641622497a8"

멋지게 포장! 감사합니다 : D
mswiszcz

1
정의되지 않은 로컬 변수 'has_secure_token'이 나타납니다. 어떤 아이디어가 있습니까?
Adrian Matteo

3
@AdrianMatteo 나는이 같은 문제가 있었다. 내가 이해 한 바에 따르면 has_secure_tokenRails 5가 제공되지만 4.x를 사용하고있었습니다. 나는 이 기사 의 단계를 밟았 으며 이제는 나를 위해 일한다.
타마라 베르나 드


5

적절한 mysql varchar 32 GUID를 만들려면

SecureRandom.uuid.gsub('-','').upcase

단일 문자 '-'를 바꾸려고하므로 gsub 대신 tr을 사용할 수 있습니다. SecureRandom.uuid.tr('-','').upcase. tr과 gsub를 비교하려면 이 링크 를 확인하십시오 .
Sree Raj

2
def generate_token
    self.token = Digest::SHA1.hexdigest("--#{ BCrypt::Engine.generate_salt }--")
end

0

토큰과 마찬가지로 비밀번호를 처리해야한다고 생각합니다. 따라서 DB로 암호화해야합니다.

모델에 고유 한 새 토큰을 생성하기 위해 이와 같은 작업을 수행하고 있습니다.

key = ActiveSupport::KeyGenerator
                .new(Devise.secret_key)
                .generate_key("put some random or the name of the key")

loop do
  raw = SecureRandom.urlsafe_base64(nil, false)
  enc = OpenSSL::HMAC.hexdigest('SHA256', key, raw)

  break [raw, enc] unless Model.exist?(token: enc)
end
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.