Rails 我的权限管理简单实现 (非 gem)

flowerwrong · 发布于 2014年5月28日 · 最后由 lilijreey 回复于 2016年10月27日 · 3338 次阅读
9442

写在前面:

ruby-version: 2.1.1
rails-version: 4.1.1

一.model

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.text :password
      t.integer :role_id

      t.timestamps
    end
  end
end
class CreateRoles < ActiveRecord::Migration
  def change
    create_table :roles do |t|
      t.string :name
      t.text :desc

      t.timestamps
    end
  end
end

二.login logout

class SessionsController < ApplicationController
  layout "user", :only => :new

  skip_before_action :require_login, only: [:new, :create]

  def new
    @session = User.new
  end

  def create
    @user = User.authentication(login_params["netid"], login_params["password"])
    if @user
      session[:user_id] = @user.id
      flash[:notice] = "welcome #{@user.name}"
      redirect_to users_path
    else
      flash[:notice] = "pass or netid incorrect"
      redirect_to login_path
    end
  end

  def destroy
    session[:user_id] = nil
    redirect_to login_path
  end

  private
  def login_params
    params.require(:user).permit(:netid, :password)
  end
end

userModel里面的两个方法

class User < ActiveRecord::Base
  belongs_to :role
  def self.hash_password(pass, name)
    salt = name
    Digest::SHA256.hexdigest(pass + salt)
  end

  def self.authentication(netid, pass)
    user = User.find_by(netid: netid)
    if user && Digest::SHA256.hexdigest(pass + user.name) == user.password
      return user
    end
    false
  end
end

三.实现current_user,我用的是help_method

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  private
  def current_user
    @current_user = User.find_by id: session[:user_id] if session[:user_id]
  end
  helper_method :current_user
end

四.实现某些控制器需要登陆

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  before_action :require_login

  private
  def current_user
    @current_user = User.find_by id: session[:user_id] if session[:user_id]
  end

  def require_login
    unless current_user
      flash[:notice] = "please login"
      redirect_to login_path
    end
  end
  helper_method :current_user
end

不需要的请使用skip_before_action

class SessionsController < ApplicationController
  layout "user", :only => :new

  skip_before_action :require_login, only: [:new, :create]

  def new
  end

  def create
  end

  def destroy
  end

  private
  def login_params
    params.require(:user).permit(:netid, :password)
  end
end

五.实现权限控制,同样是作为help_method

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  before_action :require_login

  private
  def current_user
    @current_user = User.find_by id: session[:user_id] if session[:user_id]
  end

  def require_login
    unless current_user
      flash[:notice] = "please login"
      redirect_to login_path
    end
  end

  def required_role(need_role)
    role = current_user.role
    role_name = [role.name]

    unless (need_role & role_name) == role_name
      flash[:error] = "u r not permission"
      redirect_to login_path
    end
  end

  helper_method :current_user

  helper_method :required_role
end

在控制器中这样写

class RolesController < ApplicationController
  before_action :set_role, only: [:show, :edit, :update, :destroy]

  before_action  do
    required_role(["admin"])
  end
end

收工。

PS:原本不需要role表,但是方便以后扩展处permission表,还是留下了。

共收到 20 条回复
1553

那如何在 view 中实现cancan那种功能:

<% if can? :update, Article %>
  <%= link_to "Edit", edit_article_path(@article) %>
<% end %>

<% if can? :update, Comment %>
  <%= link_to "Edit", edit_comment_path(@comment) %>
<% end %>

你可以让用户点击过去后提示说没有权限,但如果能在 view 中隐藏没有权限的链接那才是最完美的

9442

#1楼 @Peter required_role是help_method, 暂时可以这样干 <% if required_role(["admin"])%> ...... <% end %> 如果需要细化,还需要实现permission表

11222

如果你的app只需要是否登录这个权限,这样写足够好了。如果需要更多一点,helpers慢慢地就会满天飞得招架不过来了。

9442

#3楼 @billy 哈哈,所以叫做简单实现 目前的app只有5个固定的group组,开放的只有一个登陆,其他都是需要登陆的, 所以不同的controller里面写一个 before_action required_role(["group_name1", "group_name2]) 这样即可

11222

可以的。实际一点没什么不好的。需求来了再改。

20

最近在使用 Pundit, 独立出来的 policy classes 很容易管理,感觉不错!

1553

#6楼 @gonglexin Cancancan 还算折腾明白了,会用能用,不想再折腾 Pundit 了

想问一下,相对 Cancancan,Pundit 能很精细的控制各个权限吗?比如各种role,每个role有不同权限

4215

#7楼 @Peter cantango, 看过点介绍,好像没维护了。

1553

#8楼 @chenge 现在不是用cancancan吗

4215

#9楼 @Peter cancancan这个名气大,不过还有别的吧。正好前不久涉及到权限,了解了一下。

20

#7楼 @Peter 没使用过Cancan, 刚刚看了下Cancan 的介绍,感觉 Cancan 是从 user 的角度出发去检查他的各种 ablity, 貌似管理权限都是在一个叫 Ablity 的文件里面。而 Pundit 给我的感觉是从 model (resources) 出发,精细到每一个 action 操作定义他的权限。每一个 model 都可以定义一个权限管理的 policy class, 针对 Controller 里面的 new, create, update ... 在 policy class 里面定义对应的 new?, create?, update? 规则。 对于 "各种role, 每个role都有不同权限" 完全可以把这些逻辑写到对应的细粒度的 action rule (new?, create?, update? ...) 里面。

Cancan 应该也很容易做到这些,我选择 Pundit 的原因是 pundit 更容易维护, 文档更清晰,不喜欢 Cancancan 这个名字。。。

2474

Pundit+1 #7楼 @Peter 你得自己提供role的区分方式……Pundit 只是提供权限的集中管理……

9442

我昨天看了rolify+cancan+devise,但是看起来头疼,所以还是决定自己弄个简单的,其实也没有多复杂。主要是我的app需求比较简单。5个固定的用户组,只有一个开放uri,也就是login。目前这样我觉得是最简单的了。

12991

CanCan 的理念在于把 user 的权限判断全部放在 ability 中,集中化管理。不需要把逻辑散布在 controller, helper, model 各层。但一个不好的地方也很明显,每次处理 request 都会加载整个 ability 文件。比如有时候只需要 update resource1,结果一加载 ability 把关于 resource2, resource3 ... 的权限都计算了一遍。想避免这种情况只能大量的用 block 方式定义每条权限。

不知道 CanCanCan 现在是不是还是这一套模式。

@flowerwrong LZ 也可以看看 Pundit,本质上它就是用 Ruby Object 来封装权限逻辑的一种实现,没做任何 magic 的事情。这个 是他们公司的一篇 blog ,讲怎么样实现简单的权限管理的。然后他们把代码稍微封装了一下做成了 Pundit。

1770

Pundit 已經很簡潔了

12260

我用admin? 靠谱吗

9442

#15楼 @PlayMonkey 用的人挺多的,看来真的不错。

13125

query cache ?

3005

cancan吧

24996

你的权限控制策略都写死到代码上了, 无法动态更改, 可用性不大.

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