Rails 又写了一个项目的权限系统,大家觉得怎么样?

xjz19901211 · 2013年12月18日 · 最后由 small_fish__ 回复于 2013年12月19日 · 3799 次阅读

没写成Gem,放在项目里头,下面链接里说了这个系统的大概情况,大家觉得怎么样?

http://only-x.com/2013/12/16-rails-permission.html

说下好与不好的地方,或是新的想法,大家一起学习学习

共收到 14 条回复

赞一下重复造轮子的精神。

定义了三个ability: own/read/write

只有这三个ability,怎么控制更细精度的权限?Linux系统权限还有executeable呢。

Cancan 灵活在可以根据action名来做验证。如果能把Cancan的权限配置持久化到数据库中,也不错。

@camel linux 也就 own / group + r w x 啊 我这里只搬了一点点,own + r w 没有group 与 x 当然这里只做了对资源的控制,相对来说比cancan精细多了

比如,我文章里说的cancan要让用户可以修改自己的东西,必然要写一个类似record.user_id == current_user.id 来判断用户是否可操作资源

而我写的方式是在创建时就让指定用户可以管理record,方式不一样,当然优缺点也不一同

如cancan想把一个权限控制的代码保存的数据库是非常麻烦的,而且不能修改(相对于普通使用者),而我的方式是不关心记录是谁的,只关心用户对这个资源有没有权限操作,这个确可以随意定义,缺点也很明显,很多权限记录

这只是一时想到的东东,肯定还在很多问题的,不过正好我的项目中在使用,可以慢慢完善 ,这过程中我也同样会遇到很多坑的

@camel 好像答的有点问题,那个ability只是目前用到的,我觉得,如果以后有必要,可以扩展,定义做任意的字符

#3楼 @xjz19901211 “定义” 需要一个类似"配置"的过程,Rails鼓励"约定大于配置"。Cancan直接用action名,并支持一些别名,如:read = [:index, :show]这样挺好。

我一同事一直纠结性能问题,不会每次请求都要带一次权限验证的sql查询吧。然后他一直抱怨cancan的,不知道楼主有例子分享吗?或者有打算做成gem?很感兴趣。。

#5楼 @small_fish__ 性能问题确实是大问题。cancan 每次 action 都会先做判断生成一张权限表然后对照这张表看某个 acion 是否被允许,会造成大量的无用 sql。

#6楼 @liudangyi 对于基于group+resorce的权限,不知道有什么好的方法,主要是文档类的应用

#7楼 @small_fish__ 权限本来就不是一件复杂的事情。如果想方便但不想用 cancan 可以去看看 pundit。自己写的话直接在 app_controller 里 before_filter 一下就可以了。

#6楼 @liudangyi CanCan 性能是个头痛的问题,想办法做cache吧,把can?用cache包一层,通过判断user updated_at来判断是否过期,我还没尝试过。

@camel @small_fish__ 性能问题,我想把权限cache的内存中应该还好吧,原谅我一直没做过请求量灰常多的项目。。

这个cache可以把用户的权限cache一下,或是把所有权限数据也缓存起来。。

@liudangyi 权限是不复杂,不过涉及到各种业务需求时,还是很麻烦的

看下了Pundit感觉和Cancan差不多,都是制定一个规则,权限基于这个规则 我想试着做下以资源的权限为基础,在这上面随意定制规则

知识面比较窄,说的不对还请见谅

@small_fish__ 我hack了cancan的内部结构

class DelayBlock

  attr_accessor :block, :ability, :args

  def initialize(*args, &block)
    self.ability = args.shift
    self.args = args
    self.block = block
  end

  def run
    if @result
      @result
    else
      @result = self.ability.instance_exec(*args, &block)
    end
  end
end

module CanCan

  class Rule

    # replace delay hash to normal hash
    def transfer_delay_conditions!(ability)
      recurse_replace_delay_result(self.conditions, ability)
    end

    def recurse_replace_delay_result(hash, ability)
      hash.each do |k, v|
        if v.is_a?(DelayBlock)
          hash[k] = v.run
        elsif v.is_a?(Hash)
          recurse_replace_delay_result(v, ability)
        end
      end
    end

  end

  module Ability

    #override
    # used for single match
    def can?(action, subject, *extra_args)
      match = relevant_rules_for_match(action, subject).detect do |rule|
        # ********* add by ankun ***********
        rule.transfer_delay_conditions!(self)
        # ********* // add by ankun ***********
        rule.matches_conditions?(action, subject, extra_args)
      end
      match ? match.base_behavior : false
    end


    # override
    # accessible_by
    def model_adapter(model_class, action)
      adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
      # ********* add by ankun ***********
      rules = relevant_rules_for_query(action, model_class)
      rules.each do |rule|
        rule.transfer_delay_conditions! self
      end
      # ********* // add by ankun ***********
      adapter_class.new(model_class, rules)
    end

    def attributes_for(action, subject)
      attributes = {}
      relevant_rules(action, subject).map do |rule|
        # ********* add by ankun ***********
        rule.transfer_delay_conditions!(self)
        # ********* // add by ankun ***********

        attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
      end
      attributes
    end

  end
end

以后你就可以执行

db = DelayBlock.new do
  User.where(:active => true, :admin => false).pluck(:id)
end

can :close, User, :id => db

这样灵活和性能都有所体现,你只要在DelayBlock中返回数组就行了.

这样的好处是,只有要调用 can?(:close, @user) 或者 User.accessible_by(current_ability, :close) 的时候才会执行 DelayBlock中的sql查询。希望对你有帮助

#13楼 @yakjuly 谢谢分享,太感谢,一定认真分析下。。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册