Rails'te benzersiz jeton oluşturmanın en iyi yolu?


156

İşte kullandığım. Jetonun tahmin edilmesi zorunlu olarak duyulmak zorunda değil, daha çok her şeyden daha kısa bir url tanımlayıcısı gibi ve kısa tutmak istiyorum. Çevrimiçi bulduğum bazı örnekleri takip ettim ve bir çarpışma durumunda, aşağıdaki kodun jetonu yeniden oluşturacağını düşünüyorum , ancak gerçek emin değilim. Yine de, daha iyi öneriler görmeyi merak ediyorum, çünkü bu kenarlarda biraz pürüzlü hissediyor.

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

Jeton için veritabanı sütunum benzersiz bir dizin ve ben de validates_uniqueness_of :tokenmodel üzerinde kullanıyorum , ancak bunlar uygulamadaki kullanıcının eylemlerine göre otomatik olarak gruplar halinde oluşturulduğundan (bir sipariş verir ve jetonları satın alırlar), uygulama bir hata atmak mümkün değil.

Ayrıca, çarpışma olasılığını azaltmak, sonunda başka bir dize, zamana veya bunun gibi bir şeye dayalı olarak üretilen bir şey ekleyebilirim, ancak jetonun çok uzun sürmesini istemiyorum.

Yanıtlar:


334

-- Güncelleme --

İtibariyle 9 Ocak 2015 solüsyon şimdi uygulanan Raylar 5 activerecord güvenli belirteç uygulanması .

- Raylar 4 ve 3 -

Sadece ileride başvurmak, güvenli rastgele simge oluşturmak ve model için benzersiz olmasını sağlamak (Ruby 1.9 ve ActiveRecord kullanırken):

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

Düzenle:

@kain önerdi, ben yerine, mutabık begin...end..whileile loop do...break unless...endönceki uygulama gelecekte kaldırılacak: çünkü bu cevap.

Düzenleme 2:

Rails 4 ve kaygılarla, bunu kaygılandırmak için taşımanızı tavsiye ederim.

# 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


@kain loop do("döngü ... do" döngü türü) bu durumda ( begin...while"döngü ... döngü" türü ) yerine ("do ... while" döngü) yerine (döngü en az bir kez çalıştırılması gereken ) kullanılmalı mı?
Krule

7
döngü içinde random_token bulunduğundan bu tam kod çalışmaz.
Jonathan Mui

1
@Krule Şimdi bunu bir Endişeye dönüştürdüğünüze göre ModelName, yöntemde de kurtulmamalısınız ? Belki onun self.classyerine? Aksi takdirde, çok tekrar kullanılamaz, değil mi?
bisiklet

1
Çözüm onaylanmadı, Güvenli Jeton sadece Rails 5'de uygulandı, ancak Rails 4 veya Rails 3'te (bu soru ile ilgilidir) kullanılamaz
Aleks

52

Ryan Bates, Railscast'te beta davetiyelerde biraz kod kullanıyor . Bu 40 karakterli alfasayısal bir dize oluşturur.

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

3
Evet, bu kötü değil. Genellikle bir URL'nin parçası olarak kullanmak için çok daha kısa dizeler arıyorum.
Slick23

Evet, bu en azından okumak ve anlamak kolaydır. 40 karakter bazı durumlarda iyidir (beta davetleri gibi) ve bu benim için şimdiye kadar iyi çalışıyor.
Nate Bird

12
@ Slick23 Her zaman ipin bir kısmını da alabilirsiniz:Digest::SHA1.hexdigest([Time.now, rand].join)[0..10]
Bijan

Google Analytics'in ölçüm protokolüne "istemci kimliği" gönderirken IP adreslerini gizlemek için bunu kullanıyorum. Bir UUID olması gerekiyordu, ama sadece hexdigestherhangi bir IP için ilk 32 karakterini alıyorum .
thekingoftruth

1
32 bit IP adresi için @ thekingoftruth tarafından oluşturulan tüm olası onaltılıkların bir arama tablosuna sahip olmak oldukça kolay olurdu, bu yüzden hiç kimse karma alt dizesinin bile geri döndürülemez olacağını düşünmeyin.
mwfearnley

32

Bu geç bir yanıt olabilir, ancak bir döngü kullanmaktan kaçınmak için yöntemi özyinelemeli olarak da çağırabilirsiniz. Bana biraz daha temiz görünüyor ve hissediyor.

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

Bu makalede gösterilen bunu yapmanın bazı kaygan yolları vardır:

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

Listelenen favorim:

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

İlk yöntem yaptığım gibi görünüyor, ama rand veritabanı agnostik olmadığını düşündüm?
Slick23

Ve bunu takip ettiğimden emin değilim: if self.new_record? and self.access_token.nil?... jetonun henüz saklanmadığından emin olmak için kontrol eden şey bu mu?
Slick23

4
Mevcut jetonlara karşı her zaman ek kontrollere ihtiyacınız olacaktır. Bunun açık olmadığını fark etmedim. Sadece validates_uniqueness_of :tokentaşıma işlemiyle tabloya benzersiz bir dizin ekleyin ve ekleyin.
coreyward

6
blog yazısının yazarı burada! Evet: Bu durumda birliği tanımlamak için her zaman bir db kısıtlaması veya benzeri eklerim.
Thibaut Barrère


17

Benzersiz olacak bir şey istiyorsanız, bunun gibi bir şey kullanabilirsiniz:

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

ancak bu 32 karakterlik bir dize oluşturur.

Ancak başka bir yol var:

require 'base64'

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

örneğin 10000 gibi bir kimlik için, oluşturulan simge "MTAwMDA =" gibi olur (ve id için kolayca kodunu çözebilirsiniz, sadece

Base64::decode64(string)

Oluşturulan değerin, benzersiz dizeler oluşturma yöntemleri yerine, zaten oluşturulmuş ve depolanmış değerlerle çarpışmamasını sağlamakla daha fazla ilgileniyorum.
Slick23

oluşturulan değer önceden oluşturulmuş değerlerle çarpışmaz - base64 belirleyicidir, bu nedenle benzersiz kimlikleriniz varsa benzersiz belirteçlere sahip olursunuz.
Esse

random_string = Digest::MD5.hexdigest("#{ActiveSupport::SecureRandom.hex(10)}-#{DateTime.now.to_s}-#{id}")[1..6]Kimliğin belirtecin kimliği olduğu yere gittim .
Slick23

11
Bana Base64::encode64(id.to_s)bir belirteç kullanma amacını yener gibi geliyor . Büyük olasılıkla, kimliği gizlemek ve kaynağa jetonu olmayan herkes için erişilemez hale getirmek için bir jeton kullanıyorsunuz. Ancak, bu durumda, birisi sadece çalıştırmak için olabilir Base64::encode64(<insert_id_here>)ve anında sitenizdeki her kaynak için tüm belirteçleri olurdu.
Jon Lemmon

Çalışmak için bu şekilde değiştirilmesi gerekiyorstring = (Digest::MD5.hexdigest "#{SecureRandom.hex(10)}-#{DateTime.now.to_s}")
Qasim

14

Bu yardımcı olabilir:

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

'+ / =' İlk argümanından başka özel bir karakteri ve '0aZ' ve 15 ikinci argümanına yerleştirilen herhangi bir karakteri kaldırmak istiyorsanız, buradaki uzunluktur.

Ve ek boşlukları ve yeni satır karakterini kaldırmak yerine aşağıdaki gibi şeyleri eklemek istiyorsanız:

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

Umarım bu herkese yardımcı olur.


3
"+ / =" Gibi garip karakterler istemiyorsanız, base64 yerine SecureRandom.hex (10) kullanabilirsiniz.
Min Ming Lo

16
SecureRandom.urlsafe_base64aynı şeyi başarır.
iterion

7

has_secure_token kullanıcısı olabilirsiniz https://github.com/robertomiranda/has_secure_token

kullanımı gerçekten basit

class User
  has_secure_token :token1, :token2
end

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

güzel sarılmış! Teşekkürler: D
mswiszcz

1
Tanımsız yerel değişken 'has_secure_token' alıyorum. Neden herhangi bir fikir?
Adrian Matteo

3
@AdrianMatteo Aynı sorunu yaşadım. Anladığım kadarıyla has_secure_tokenRails 5 ile geliyor, ama 4.x kullanıyordum. Bu makaledeki adımları izledim ve şimdi benim için çalışıyor.
Tamara Bernad


5

Düzgün, mysql, varchar 32 GUID oluşturmak için

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

Tek bir karakteri '-' değiştirmeye çalıştığımız için, gsub yerine tr kullanabilirsiniz. SecureRandom.uuid.tr('-','').upcase. Tr ve gsub arasındaki karşılaştırma için bu bağlantıyı kontrol edin .
Sree Raj

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

0

Jetonun tıpkı şifre gibi ele alınması gerektiğini düşünüyorum. Bu nedenle, DB'de şifrelenmeleri gerekir.

Bir model için benzersiz yeni bir jeton oluşturmak için böyle bir şey yapıyorum:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.