Circle Image

Wen Lin Gang

Ruby & Go Full-stack programmer @ Lifelong learner.

Rails 中的身份验证 (Authentication - Authn) 和授权 (Authorization - Authz)

什么是 Authn 和 Authz

Authn 与 Authz 它们有什么不同?

在信息安全中,身份验证(缩写为 authn)和授权(缩写为 authz)是相关但独立的概念。两者都是身份和访问管理 (IAM) 的重要组成部分。

authn 和 authz 有什么不同?简单来说,authn 与身份有关,即某人是谁,而 authz 与权限有关,即某人被允许做什么。

身份验证意味着确保一个人或设备是他们(它们)声称的人(或东西)。领取活动门票的人可能会被要求出示身份证以验证其身份;同样,应用程序或数据库可能希望通过检查用户的身份来确保用户是合法的。身份验证确保数据不会暴露给错误的人。

授权决定了经过身份验证的用户可以查看的内容和执行的操作。想想当银行客户在线登录他们的账户时会发生什么。因为他们的身份已经过验证,他们可以看到自己的账户余额和交易历史——但他们无权查看其他人的。相反,银行的经理可以被授权查看任何客户的财务数据。

实现用户注册和登录必要的流程 - Authentication

class User < ApplicationRecord
  has_secure_password

  validates :name, :password, presence: true
  validate_uniqueness_of :name
end

has_secure_password(attribute = :password, validations: true) 是 Rails 内置的安全密码模块,他增加了设置和验证 BCrypt 密码的方法,这种机制要求你有一个 XXX_digest 属性,其中 XXX 是所需密码的属性名。比如我们当前数据库存放密码的字段是 password 那就需要数据库有一列是 password_digest,然后他会自动做如下的校验:

使用该属性需要添加 bundle add gem 'bcrypt' 到 Gemfile,然后运行 bundle install

添加之后可以这样验证用户密码:user.authenticate('password')

Controller

class UsersController < ApplicationController
  def create
    user = User.new(user_params)
    if user.save
      render json: user, status: :ok
    else
      render json: { error: user.errors.full_messages }, status: :unprocessable_entity
    end
  end

  private
    def user_params
      params.require(:user).permit(:name, :password)
    end
end
class SessionsController < ApplicationController
  def create
    user = User.find_by(name: params[:name])
    if user && user.authenticate(params[:password])
      if params[:remember_me]
        # 此为示例,如果要使用 cookies 机制应正确存储加密后的结果到客户端,否则会有安全问题
        cookies[:remember_token] = { value: user.id, expires: 24.hour }
      else
        session[:user_id] = user.id
      end

      render json: user, status: :ok
    else
      render json: { error: 'Invalid name or password' }, status: :unauthorized
    end
  end

  def destroy
    session.delete(:user_id)
    render json: { ok: true }, status: :ok
  end
end
class ApplicationController < ActionController::Base
  before_action :authenticate_user!
  include ActionController::Cookies

  private
    # 定义该函数后,只要继承 ApplicationController 那就都可以用 current_user 来获取当前用户的信息
    def current_user
      @current_user ||= User.find_by_id(session[:user_id])
    end


    def authenticate_user!
      render json: { error: 'Not Authorized' }, status: :unauthorized unless current_user
    end
end

我们分别提到了 sessioncookies 两个关键的信息,他们都是 Rails 用于在 Web 应用程序中存储和管理数据的机制:

区别

使用方式

# 设置会话
session[:user_id] = 1

# 获取会话
user_id = session[:user_id]

# 删除会话
session.delete(:user_id)
# 设置 cookie
cookies[:username] = {
  value: 'john',
  expires: 1.week.from_now
}

# 获取 cookie
username = cookies[:username]

# 删除 cookie
cookies.delete(:username)

Router

Rails.application.routes.draw do
  resources :users, only: [:create]
  post '/login', to: 'sessions#create'
  delete '/logout', to: 'sessions#destroy'
end

实现用户身份验证 - Authorization

用户身份验证如有策略、角色、权限等等根据实际业务需要,能够使用的比如一些 Gem 比如:

假设我们现在在做一个 Blog 系统,我们期望只有 Blog 的作者才能管理他自己的文章

class User < ApplicationRecord
  has_many :blogs
end

class Blog < ApplicationRecord
  belongs_to :user
end

Controller

class BlogsController < ApplicationController
  before_action :set_blog, only: [:show, :update, :destroy]
  before_action :authorize_author!, only: [:update, :destroy]

  def update
    if @blog.update(blog_params)
      render json: @blog, status: :ok
    else
      render json: { errors: @blog.errors.full_messages }, status: :unprocessable_entity
    end
  end

  def destroy
    @blog.destroy
    render json: { ok: true }, status: :ok
  end

  def show
    render json: @blog, status: :ok
  end

  private
    def set_blog
      @blog = Blog.find(params[:id])
    end

    def blog_params
      params.require(:blog).permit(:title, :body)
    end

    def authorize_user!
      if @blog.user != current_user
        render json: { error: 'Unauthorized' }, status: :unauthorized
      end
    end
end

总结

上面示例只是简单讲解和实现了 authn 和 authz 的分工,每个系统的访问控制如何设计还需要结合自身情况来决定。

在设计和实现之前,推荐阅读一下这些最佳实践:

….持续收集中