web-dev-qa-db-fra.com

Le meilleur moyen de créer un jeton unique dans Rails?

Voici ce que j'utilise. Le jeton ne doit pas forcément être entendu pour deviner, il ressemble plus à un identifiant d'URL abrégé qu'autre chose, et je veux le garder court. J'ai suivi quelques exemples que j'ai trouvés en ligne et en cas de collision, je pense le code ci-dessous va recréer le jeton, mais je n'en suis pas vraiment sûr. Je suis curieux de voir de meilleures suggestions, cependant, car cela semble un peu rugueux sur les bords.

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

Ma colonne de base de données pour le jeton est un index unique et j'utilise également validates_uniqueness_of :token sur le modèle, mais comme ceux-ci sont créés automatiquement par lots en fonction des actions de l'utilisateur dans l'application (essentiellement, il passe une commande et achète les jetons), il est impossible que l'application jette une erreur.

Je pourrais aussi, je suppose, pour réduire le risque de collision, ajouter une autre chaîne à la fin, quelque chose générée en fonction de l'heure ou quelque chose du genre, mais je ne veux pas que le jeton soit trop long.

147
Slick23

-- Mettre à jour --

À compter du 9 janvier 2015} _, la solution est maintenant implémentée dans Rails 5Implémentation du jeton sécurisé d'ActiveRecord

- Rails 4 & 3 -

Juste pour référence future, créez un jeton aléatoire sûr et assurez-vous que le modèle est unique (avec Ruby 1.9 et 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

Modifier:

@kain a suggéré, et j'ai accepté, de remplacer begin...end..while par loop do...break unless...end dans cette réponse car la mise en œuvre précédente pourrait être supprimée à l'avenir.

Edit 2:

Avec Rails 4 et les préoccupations, je recommanderais de déplacer cette préoccupation.

# 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
322
Krule

Ryan Bates utilise un joli morceau de code dans son Railscast sur les invitations bêta . Cela produit une chaîne alphanumérique de 40 caractères.

Digest::SHA1.hexdigest([Time.now, Rand].join)
48
Nate Bird

Cet article présente certaines méthodes assez astucieuses:

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

Ma liste préférée est la suivante:

Rand(36**8).to_s(36)
=> "uur0cj2h"
29
coreyward

Cela peut être une réponse tardive, mais pour éviter d'utiliser une boucle, vous pouvez également appeler la méthode de façon récursive. Il semble et se sent légèrement plus propre pour moi.

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
27
Marius Pop

Si vous voulez quelque chose qui sera unique, vous pouvez utiliser quelque chose comme ceci:

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

cependant, cela générera une chaîne de 32 caractères.

Il y a cependant un autre moyen:

require 'base64'

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

par exemple, pour un identifiant tel que 10000, le jeton généré serait du type "MTAwMDA =" (et vous pouvez facilement le décoder,

Base64::decode64(string)
17
Esse

Cela peut être utile:

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

Si vous souhaitez supprimer un caractère spécial, mettez le premier argument '+/=' et tout caractère inséré le second argument '0aZ' et 15 correspond à la longueur.

Et si vous souhaitez supprimer les espaces supplémentaires et le nouveau caractère de ligne, ajoutez les éléments suivants:

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

J'espère que cela aidera à tout le monde.

14
Vik

Essayez de cette façon:

A partir de Ruby 1.9, la génération uuid est intégrée. Utilisez la fonction SecureRandom.uuid.
Génération de guides en Ruby

Cela m'a été utile

7

vous pouvez utiliser has_secure_token https://github.com/robertomiranda/has_secure_token

est vraiment simple à utiliser

class User
  has_secure_token :token1, :token2
end

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

Pour créer un GUID correct, mysql, varchar 32

SecureRandom.uuid.gsub('-','').upcase
4
Aaron Henderson
def generate_token
    self.token = Digest::SHA1.hexdigest("--#{ BCrypt::Engine.generate_salt }--")
end
1
miosser

Je pense que le jeton devrait être traité comme un mot de passe. En tant que tels, ils doivent être chiffrés dans DB.

Je fais quelque chose comme ceci pour générer un nouveau jeton unique pour un modèle:

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
0
cappie013