如何在rails中混淆我的记录的ID?

时间:2012-02-16 03:15:23

标签: ruby-on-rails database ruby-on-rails-3 data-binding obfuscation

我正在试图弄清楚如何在轨道中混淆我的记录的ID。

例如:典型的路径可能看起来像http://domain/records/1,因此人们很容易推断出网站在创建新记录时会获得多少流量。

我使用的一个解决方案是使用salt对id进行哈希处理,但由于我不确定该函数是否是双射的,我最终将其存储在数据库的另一列中并仔细检查唯一性。

我正在考虑的另一个选项是生成随机哈希并将其存储为另一列。如果它不是唯一的......只需生成另一个。

这样做的最佳方式是什么?

6 个答案:

答案 0 :(得分:24)

您可以使用内置的OpenSSL库来加密和解密您的标识符,这样您只需要覆盖模型上的to_param。您还需要使用Base64将加密数据转换为纯文本。我会把它粘在一个模块中,以便可以重复使用:

require 'openssl'
require 'base64'

module Obfuscate
  def self.included(base)
    base.extend self
  end

  def cipher
    OpenSSL::Cipher::Cipher.new('aes-256-cbc')
  end

  def cipher_key
    'blah!'
  end

  def decrypt(value)
    c = cipher.decrypt
    c.key = Digest::SHA256.digest(cipher_key)
    c.update(Base64.decode64(value.to_s)) + c.final
  end

  def encrypt(value)
    c = cipher.encrypt
    c.key = Digest::SHA256.digest(cipher_key)
    Base64.encode64(c.update(value.to_s) + c.final)
  end
end

所以现在你的模型需要看起来像这样:

class MyModel < ActiveRecord::Base
  include Obfuscate

  def to_param
    encrypt id
  end
end

然后在您的控制器中,当您需要通过加密ID查找记录时,您将使用以下内容:

MyModel.find MyModel.decrypt(params[:id])

如果您希望加密/解密ID 而不 将它们存储在数据库中,这可能是最简单的方法。

答案 1 :(得分:5)

使用某种友好的url或人类可读的slug而不是数字id。在此部门中可以选择 lots of tools 。它们不仅对您的用户更友好,而且精心挑选的slu can可以为搜索引擎带来更好的优势。

答案 2 :(得分:4)

使用随机字符串生成器或对Digest::SHA1.hexdigest的简单调用生成记录的唯一随机标识符非常容易,这会产生合理的随机和加密唯一结果。

例如,您可以创建一个名为identunique_id的辅助列,用于存储您的公共标识符。然后,您可以覆盖to_param来代替使用它:

class MyModel < ActiveRecord::Base
  before_create :assign_ident

  def self.from_param(ident)
    find_by_ident(ident)
  end

  def to_param
    self.ident
  end

protected
  def assign_ident
    self.ident = Digest::SHA1.hexdigest(SecureRandom.random_number(1<<256).to_s)
  end
end

理论上,SHA1有可能发生冲突,但由于存储器错误或硬件故障导致软件崩溃的可能性非常低。您可以通过生成几十亿个身份来测试它是否符合您的需求,以确定它们是否会发生碰撞,而不应该碰撞。 256位随机数应该为SHA1算法提供足够的数据来进行咀嚼。

答案 3 :(得分:4)

阅读@siannopollo's post后,我根据帖子的想法创建了一个Gem(但有一些改进):https://github.com/pencil/encrypted_id

答案 4 :(得分:4)

这是一个宝石,它保持数字,不需要数据库迁移,也不需要路由更改:https://github.com/namick/obfuscate_id


我发现这个宝石与其他一些宝石不一致,特别是paper_trail。这是因为它取代了find方法,而paper_trail导致find被实际记录ID调用。

所以我一直在使用gem的“scatter_swap”功能,但不是其余部分。这是模型:

require 'obfuscate_id/scatter_swap'

class Page < ActiveRecord::Base
  # This is a random number that, if changed, will invalidate all existing URLs. Don't change it!
  @@obfuscate_spin = # random number here, which is essentially the encryption key

  ##
  # Generate URL parameter to be used in the URL as the "id"
  def to_param
    # Use the obfuscate_id gem's class to "spin" the id into something obfuscated
    spun_id = ScatterSwap.hash(self.id, @@obfuscate_spin)

    # Throw any additional attributes in here that are to be included in the URL.
    "#{spun_id} #{name}".parameterize
  end

  def self.find_by_slug!(slug)
    spun_id = slug[/^[0-9]+/]
    begin
      find_by_id! ScatterSwap.reverse_hash(spun_id, @@obfuscate_spin)
    rescue ActiveRecord::RecordNotFound => e
      raise ActiveRecord::RecordNotFound, "Couldn't find matching Page."
    end
  end
end

在控制器中:

class PagesController < InheritedResources::Base
  # Find the page using its URL slug
  before_filter :find_page, except: [:index, :create, :new]

  def find_page
    @page = Page.find_by_slug! params[:id]

    # If the URL doesn't match exactly, and this is a GET.
    # We'll redirect to the new, correct URL, but if this is a non-GET, let's let them finish their request instead.
    if params[:id] != @page.to_param && request.get?
      redirect_to url_for({ id: @page.to_param }), status: 301
    end
  end
end

作为在那里发生的重定向的替代方法,您只需在页面中包含规范URL即可。重定向存在忽略URL中任何查询参数的错误。这对我的项目来说不是问题,因为我没有。但规范的网址会更好。

答案 5 :(得分:0)

只是因为这里没有提到它:您可以简单地使用UUID(wikipedia article

在Rails中有多种使用UUID作为主键的方法,具体取决于您的Rails版本和数据库引擎。很容易找到。

如果您过分依赖现有的整数主键,也可以将UUID添加到表中,并在覆盖{{1}来生成URL时使模型自动使用它} more details in the docs