Best way to create unique token in Rails?

拈花ヽ惹草 提交于 2019-11-26 04:29:57

问题


Here\'s what I\'m using. The token doesn\'t necessarily have to be heard to guess, it\'s more like a short url identifier than anything else, and I want to keep it short. I\'ve followed some examples I\'ve found online and in the event of a collision, I think the code below will recreate the token, but I\'m not real sure. I\'m curious to see better suggestions, though, as this feels a little rough around the edges.

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

My database column for the token is a unique index and I\'m also using validates_uniqueness_of :token on the model, but because these are created in batches automatically based on a user\'s actions in the app (they place an order and buy the tokens, essentially), it\'s not feasible to have the app throw an error.

I could also, I guess, to reduce the chance of collisions, append another string at the end, something generated based on the time or something like that, but I don\'t want the token to get too long.


回答1:


-- Update --

As of January 9th, 2015. the solution is now implemented in Rails 5 ActiveRecord's secure token implementation.

-- Rails 4 & 3 --

Just for future reference, creating safe random token and ensuring it's uniqueness for the model (when using Ruby 1.9 and 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

Edit:

@kain suggested, and I agreed, to replace begin...end..while with loop do...break unless...end in this answer because previous implementation might get removed in the future.

Edit 2:

With Rails 4 and concerns, I would recommend moving this to concern.

# 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



回答2:


Ryan Bates uses a nice little bit of code in his Railscast on beta invitations. This produces a 40 character alphanumeric string.

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



回答3:


This might be a late response but in order to avoid using a loop you can also call the method recursively. It looks and feels slightly cleaner to me.

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



回答4:


There are some pretty slick ways of doing this demonstrated in this article:

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

My favorite listed is this:

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



回答5:


If you want something that will be unique you can use something like this:

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

however this will generate string of 32 characters.

There is however other way:

require 'base64'

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

for example for id like 10000, generated token would be like "MTAwMDA=" (and you can easily decode it for id, just make

Base64::decode64(string)



回答6:


This may be helpful :

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

If you want to remove any special character than put in first argument '+/=' and any character put in second argument '0aZ' and 15 is the length here .

And if you want to remove the extra spaces and new line character than add the things like :

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

Hope this will help to anybody.




回答7:


Try this way:

As of Ruby 1.9, uuid generation is built-in. Use the SecureRandom.uuid function.
Generating Guids in Ruby

This was helpful for me




回答8:


you can user has_secure_token https://github.com/robertomiranda/has_secure_token

is really simple to use

class User
  has_secure_token :token1, :token2
end

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



回答9:


To create a proper, mysql, varchar 32 GUID

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



回答10:


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



回答11:


I think token should be handled just like password. As such, they should be encrypted in DB.

I'n doing something like this to generate a unique new token for a model:

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


来源:https://stackoverflow.com/questions/6021372/best-way-to-create-unique-token-in-rails

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!