gameFu's Blog

在rails中使用jwt实现授权和权限控制

June 22, 2015

关于jwt

JWT 全称json_web_token,是一种高效可靠的数字签名解决标准,它可以携带自定义用户信息,经过 base64 编码, hamc SHA256 加密生成 token, 然后通过 http authorization 请求头传递作为登陆凭证。在RESTful API越来越流行的情况下,传统的cookie, session携带授权令牌很难满足需求了,两者都会带来业务上拓展的困难。

登录流程

首先,用户端会首先检查本地有没有缓存jwt,如果有,直接在请求头上带上jwt去访问你的api,然后api检查这个jwt是否合法,如果合法,则通过,返回这个api请求到的信息,如果不合法返回错误信息。 如果本地没有缓存jwt,那么访问api时,api会引导用户去获取jwt,并记住之前访问的url,当用户通过输入短信验证码(或者其他方式),获取到jwt后在请求头上带上这个jwt去访问原来访问的url

在ruby on rails下使用jwt

在Gemfile中添加


gem 'jwt'

运行 bundle 进行安装


bundle

首先是获取jwt, 我把他定义在一个名为json_web_token的controller中


def create

    case params[:auth_type]
      when 'user'
        if verify_code(request_phone_number, request_verify_code)
          user = User.find_by(:phone_number => request_phone_number)

          unless user
            #创建新的用户
            user = User.create(:phone_number => request_phone_number)
          end
        else
          raise LogicError, '验证码不正确'
        end
      when 'operation_user'
        user = OperationUser.auth(params[:username], params[:password])
      when 'base_station_user'
        user = BaseStationUser.auth(params[:username], params[:password])
    end

    unless user
      raise LogicError, '用户名或密码错误'
    end

    # 返回生成的jwt
    res = user.as_json(methods: :token)
    response_json res
  end

首先,请求这个方法的时候必须要带上一个参数是 auth_type这个参数表示登录用户的角色,不同的角色需要携带不同的参数,如user需要携带的是手机号,短信验证码,而operation_user需要携带的是用户名,密码,
根据不同的用户类型来判断,例如,这段代码验证了手机验证码是否正确

  if verify_code(request_phone_number, request_verify_code)
          user = User.find_by(:phone_number => request_phone_number)

如果,通过了验证,那么返回jwt。在models的 concerns目录下创建 json_web_token_able.rb,这个文件里的代码是用来生成jwt的


  module JsonWebTokenAble
  def to_jwt
    payload = {
        # key为用户角色,value为用户id
        self.class.name => {
            :user_id => self.id
        },
        # 设置过期时间
        'exp' => 1.month.from_now.to_i
    }
    # 第二个参数为自己配置的密钥
    JWT.encode(payload, ENV["SECRET_KEY_BASE"])
  end

  alias_method :token, :to_jwt
end

在各种角色的用户模型里引进(业务需求并不需要动态建立角色,直接建立不同角色的模型,你可以根据你的业务需要进行调整)


include JsonWebTokenAble

那么如同我的业务需求一样,我需要在返回的时候将用户的信息返回回来该如何做呢,在models地 concerns目录下创建 auth_able.rb,


module AuthAble

  # 扩展类方法
  module ClassMethods
    # 返回user对象
    def auth(identity, password)
      user = self.find_by username: identity
      # 匹配用户名密码
      user ? user.auth(password) : false
    end
  end

  # 扩展的实例方法
  module InstanceMethods

    def auth(password)
      self.authenticate password
    end

    # 将密码令牌排除
    def as_json(options=nil)
      opts = {except: ["password_digest"]}
      super opts.merge(options)
    end
  end

  # 将扩展的类方法和实例方法包含到模型中
  def self.included(receiver)
    receiver.extend         ClassMethods
    receiver.send :include, InstanceMethods

    # 可验证模型的配置
    receiver.has_secure_password validations: false
    receiver.validates :username, presence: true
  end
end

跟json_web_token_able.rb一样,只需要在模型中引入就可以


include AuthAble

用户获取到jwt后,就应该需要判断jwt是否合法,我们可以建立一个helper来进行判断

json_web_token_helper.rb


  #从请求头获取 jwt
  def request_jwt
    request.headers["Token"]
  end

  # 解析jwt 如果不合法 抛出异常
  def jwt_claim(jwt)
    begin
      JWT.decode(jwt, ENV["SECRET_KEY_BASE"])  
    rescue
      raise AuthError, '未授权的请求'
    end
  end

  # 获取用户id
  def jwt_user_id(jwt_claim, auth_type=nil)
    unless auth_type
      auth_type = user_type
    end
    begin
      # 判断用户类型是否合法
      jwt_claim[0][auth_type]["user_id"]
    rescue
      raise AuthError, '未授权用户'
    end
  end

  # 返回当前登录用户id
  def request_jwt_user_id
    meta_class = user_type.constantize
    if meta_class.find_by :id => jwt_user_id(jwt_claim(request_jwt))
      jwt_user_id(jwt_claim(request_jwt))
    else
      raise AuthError, '未授权用户'
    end
  end

  # 获取当前登录用户
  def current_user
    meta_class = user_type.constantize
    @current_user = meta_class.find_by :id => request_jwt_user_id
  end

  #用户类名 用来支持多表用户
  def user_type
    @user_type ||= 'User'
  end

  def user_type=(user_type_params)
    @user_type = user_type_params
  end

  alias_method :current_user_id, :request_jwt_user_id
end

只需要将这个helper引入controller中就行了,由于这里面的函数会用的比较频繁,你可以选择跟我一样,将其引入到application_controller.rb中


include Backend::JsonWebTokenHelper

要想验证jwt是否合法,只需要在controller中调用current_user就能判断。

那么如何根据jwt来判断不同类型的用户呢? 在json_web_token_helper.rb里我们预留了一个函数用来设置用户类型,这样检查jwt时候合法的时候就会去检查jwt中的用户类型是否正确


def user_type=(user_type_params)
    @user_type = user_type_params
  end

接下来要做的是就很简单了,比如base_station下的所有控制器只要去继承一个设置了用户类型的新的公共控制器就可以达到权限控制的效果了

base_controller.rb


  before_action :set_user_type

  private
  def set_user_type
    @user_type = 'BastationUser'
  end

然后需要在你以BastationUser用户操作的控制器只要全部继承这个base_controller.rb就行了,其他用户的权限控制也是类似操作,至此,jwt做授权和权限控制就完成了