रेल में अद्वितीय टोकन बनाने का सबसे अच्छा तरीका?


156

यहाँ मैं उपयोग कर रहा हूँ। टोकन को जरूरी नहीं लगता है कि अनुमान लगाने के लिए सुना जाना चाहिए, यह कुछ और की तुलना में एक छोटे यूआरएल पहचानकर्ता की तरह है, और मैं इसे छोटा रखना चाहता हूं। मैंने कुछ उदाहरणों का अनुसरण किया है जिन्हें मैंने ऑनलाइन पाया है और एक टकराव की स्थिति में, मुझे लगता है कि नीचे दिए गए कोड टोकन को फिर से बनाएंगे, लेकिन मुझे यकीन है कि मैं निश्चित हूं। मैं बेहतर सुझावों को देखने के लिए उत्सुक हूं, हालांकि, यह किनारों के आसपास थोड़ा मोटा लगता है।

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

-- अपडेट करें --

के रूप में 9 जनवरी, 2015 को समाधान अब में कार्यान्वित किया जाता रेल 5 ActiveRecord के सुरक्षित टोकन कार्यान्वयन

- रेल 4 और 3 -

बस भविष्य के संदर्भ के लिए, सुरक्षित यादृच्छिक टोकन बनाना और यह सुनिश्चित करना कि यह मॉडल के लिए विशिष्टता है (रूबी 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..whileके साथ loop do...break unless...endइस उत्तर में है क्योंकि पिछले कार्यान्वयन भविष्य में निकाला हो सकता है।

2 संपादित करें:

रेल 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

प्रारंभ / समय का उपयोग न करें, लूप का उपयोग करें / करें
kain

@ कोई कारण loop do("जबकि ..." "प्रकार का लूप) इस मामले में उपयोग किया जाना चाहिए (जहां लूप को कम से कम एक बार चलाने की आवश्यकता होती है) begin...while(" करते हैं ... जबकि "प्रकार का लूप)?
Krule

7
यह सटीक कोड काम नहीं करेगा क्योंकि random_token को लूप के भीतर रखा गया है।
जोनाथन मुई

1
@ क्रुएल अब जब आपने इसे एक चिंता में बदल दिया है, तो क्या आपको भी ModelNameइस विधि से छुटकारा नहीं चाहिए ? हो सकता है कि इसके self.classबजाय इसे बदल दें ? अन्यथा, यह बहुत पुन: प्रयोज्य नहीं है, क्या यह है?
पैरासाइकल

1
समाधान पदावनत नहीं है, सुरक्षित टोकन यह बस रेल 5 में कार्यान्वित किया जाता है, लेकिन यह रेल 4 में उपयोग नहीं किया जा सकता है या 3 रेल (जो इस सवाल से संबंधित है)
Aleks

52

रयान बेट्स बीटा निमंत्रण पर अपनी रेलकास्ट में एक अच्छा सा कोड का उपयोग करता है । यह एक 40 वर्ण अल्फ़ान्यूमेरिक स्ट्रिंग पैदा करता है।

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

3
हाँ, यह बुरा नहीं है। मैं आमतौर पर एक URL के हिस्से के रूप में उपयोग करने के लिए बहुत छोटे तारों की तलाश कर रहा हूं।
स्लिक 23

हाँ, यह पढ़ना और समझना कम से कम आसान है। 40 पात्र कुछ स्थितियों में अच्छे हैं (जैसे बीटा आमंत्रण) और यह मेरे लिए अब तक अच्छा काम कर रहा है।
नैट बर्ड

12
@ Slick23 आप हमेशा स्ट्रिंग के एक हिस्से को भी पकड़ सकते हैं:Digest::SHA1.hexdigest([Time.now, rand].join)[0..10]
बिजन

Google Analytics के माप प्रोटोकॉल के लिए "क्लाइंट आईडी" भेजते समय मैं आईपी पते को बाधित करने के लिए इसका उपयोग करता हूं। यह UUID माना जाता है, लेकिन मैं अभी hexdigestदिए गए किसी भी IP के पहले 32 वर्ण लेता हूं ।
दीकिंगफुटथ

1
एक 32-बिट आईपी पते के लिए, यह बहुत आसान होगा कि किसी भी संभव हेक्सिडिगस्ट की खोज तालिका @thekingoftruth द्वारा बनाई गई है, इसलिए किसी को भी यह सोचकर मत जाना कि हैश का एक विकल्प अपरिवर्तनीय होगा।
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"

ऐसा लगता है कि मैं जो कर रहा हूं, वह पहली विधि के समान है, लेकिन मुझे लगा कि रैंड डेटाबेस अज्ञेय नहीं था?
स्लिक 23

और मुझे यकीन नहीं है कि मैं इसका पालन करता हूं: if self.new_record? and self.access_token.nil?... क्या यह सुनिश्चित करने के लिए कि टोकन पहले से संग्रहीत नहीं है, क्या जाँच कर रहा है?
स्लिक 23

4
आपको मौजूदा टोकन के खिलाफ हमेशा अतिरिक्त चेक की आवश्यकता होगी। मुझे नहीं पता था कि यह स्पष्ट नहीं था। बस validates_uniqueness_of :tokenमाइग्रेशन के साथ तालिका में एक अद्वितीय सूचकांक जोड़ें और जोड़ें।
कोरीवर्ड

6
ब्लॉग पोस्ट के लेखक यहाँ! हां: मैं हमेशा इस मामले में एकता की पुष्टि के लिए एक db बाधा या समान जोड़ता हूं।
थिबुत बर्रेरे

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, उत्पन्न टोकन "MTAwMDA =" जैसा होगा (और आप आसानी से आईडी के लिए इसे डीकोड कर सकते हैं, बस बना सकते हैं

Base64::decode64(string)

मुझे यह सुनिश्चित करने में अधिक दिलचस्पी है कि उत्पन्न मूल्य अद्वितीय तार बनाने के तरीकों के बजाय पहले से उत्पन्न और संग्रहीत मूल्यों से टकराएगा नहीं।
स्लिक 23

उत्पन्न मान पहले से उत्पन्न मानों से नहीं टकराएगा - बेस 64 नियतात्मक है, इसलिए यदि आपके पास अद्वितीय आईडी हैं, तो आपके पास अद्वितीय टोकन होंगे।
Esse

मैं उस random_string = Digest::MD5.hexdigest("#{ActiveSupport::SecureRandom.hex(10)}-#{DateTime.now.to_s}-#{id}")[1..6]जगह गया था जहाँ आईडी टोकन की आईडी है।
स्लिक 23

11
यह मुझे लगता है कि Base64::encode64(id.to_s)एक टोकन का उपयोग करने के उद्देश्य को हरा देता है। सबसे अधिक संभावना है कि आप आईडी का उपयोग करने के लिए एक टोकन का उपयोग कर रहे हैं और जिस व्यक्ति के पास टोकन नहीं है, उसके लिए संसाधन को दुर्गम बना सकते हैं। हालाँकि, इस स्थिति में, कोई व्यक्ति बस चला सकता है Base64::encode64(<insert_id_here>)और वे आपकी साइट पर हर संसाधन के लिए तुरंत सभी टोकन होंगे।
जॉन लेमन सामन

काम करने के लिए इसे बदलने की जरूरत हैstring = (Digest::MD5.hexdigest "#{SecureRandom.hex(10)}-#{DateTime.now.to_s}")
कासिम

14

यह मददगार हो सकता है:

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

अगर आप किसी विशेष चरित्र को पहले तर्क '+ / =' में रखना चाहते हैं और दूसरा तर्क '0aZ' में डाला गया है और 15 यहाँ लंबाई है।

और यदि आप चीजों को जोड़ने की तुलना में अतिरिक्त रिक्त स्थान और नई पंक्ति वर्ण को निकालना चाहते हैं:

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

आशा है कि यह किसी को भी मदद करेगा।


3
यदि आप "+ / =" जैसे अजीब अक्षर नहीं चाहते हैं, तो आप बेस 64 के बजाय सिर्फ सिक्योरग्रैंडहैम (10) का उपयोग कर सकते हैं।
मिन मिंग लो

16
SecureRandom.urlsafe_base64वही चीज हासिल करता है।
1212

7

आप उपयोगकर्ता के पास _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"

अच्छी तरह से लिपटे हुए!
साभार

1
मुझे अपरिभाषित स्थानीय चर 'has_secure_token' मिलता है। कोई विचार क्यों?
एड्रियन मैट्टो

3
@ AdrianMatteo मेरे पास यही मुद्दा था। क्या मैं has_secure_tokenरेल 5 के साथ आता है समझ में आया है, लेकिन मैं 4.x का उपयोग कर रहा था। मैंने इस लेख के चरणों का पालन किया है और अब यह मेरे लिए काम करता है।
तमारा बरनाड


5

एक उचित, mysql, varchar 32 GUID बनाने के लिए

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

चूंकि हम किसी एकल वर्ण '-' को प्रतिस्थापित करने का प्रयास कर रहे हैं, इसलिए आप gsub के बजाय tr का उपयोग कर सकते हैं। SecureRandom.uuid.tr('-','').upcase। Tr और gsub के बीच तुलना के लिए इस लिंक की जाँच करें ।
श्री राज

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

0

मुझे लगता है कि टोकन को पासवर्ड की तरह ही हैंडल किया जाना चाहिए। जैसे, उन्हें डीबी में एन्क्रिप्ट किया जाना चाहिए।

मैं एक मॉडल के लिए एक अद्वितीय नया टोकन उत्पन्न करने के लिए कुछ ऐसा कर रहा हूं:

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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.