Skip to content

Authentication Strategy

caibiwsq edited this page Jul 4, 2019 · 1 revision

Authentication Strategy

关于登录时的身份认证,你可以通过继承Warden::Strategies::Base来实现你自己的身份认证策略,下面给出一个example,你可以根据你的需要更改

class EnterpriseStrategy < Warden::Strategies::Base
  def valid?
    true #检测是否使用该strategy
  end
	#这个方法里面实现自己的authenticate
  def authenticate!
    device_type = params[:DEVICE_TYPE]
    device_type = device_type.upcase if device_type.present?
    unless User::DEVICE_TYPES.include?(device_type)
      fail "Error in device type" and return
    end

    user = User.find_by_any(params[:email_or_mobile])
    if user.present? and params[:code].present? and user.mobile_login_code.value == params[:code]
      user.device_type = device_type.downcase
      success!(user)
    elsif user.present? and user.valid_password? params[:password]
      user.device_type = device_type.downcase
      success!(user)
    else
      fail "ERROR Incorrect username or password"
    end
  end
end
#把你的策略添加到Warden中
Warden::Strategies.add(:enterprise, EnterpriseStrategy)

#用你自定义的身份认证策略覆盖默认的身份认证策略,scope为你需要使用该策略对应的role
Devise.setup do |config|
  config.warden do |manager|
    strategies = manager.default_strategies(:scope => :user)
    strategies[strategies.index(:database_authenticatable)] = :enterprise
  end
end

Blacklist

如果你想用blacklist的实现某种role的登录,只需要在对面role的model里面添加下面这段话

devise :database_authenticatable,
         :jwt_authenticatable, jwt_revocation_strategy: Blacklist

默认的blacklist生成的token只会在token有效期到了或者登出时失效,devise.rb中默认添加了jwt.dispatch_requests = [['POST', %r{^users/sign_in$}]],当请求中括号对应的route path 时,rails-pangu会自动调用对应model的on_jwt_dispatch方法,如果你想在其他请求时调用on_jwt_dispatch方法,你可以在其中添加对应的route path。

如果你想要实现blacklist的其它额外的功能,你可以在role对应的model中通过重写jwt_payloadon_jwt_dispatch这两个函数来实现。下面给出一个单设备登录的example

attr_accessor :device_type

def device_type
  @device_type
end	
#登录时,rails-pangu会把jwt_payload的内容自动merge到jwt 的payload中,如果你想实现单设备登录,你必须在登录时提供device_type
def jwt_payload
  { 'device' => device_type }
end
 
def on_jwt_dispatch(token, payload)
  last_jwt = $redis.get("user_device_jwt:#{self.id}:#{payload['device']}")
  if last_jwt.present?
    jti, exp = last_jwt.split(":")
    expiration = exp.to_i - Time.now.to_i
    if expiration > 0
      $redis.setex("user_blacklist:#{self.id}:#{payload['device']}:#{jti}", expiration, jti)
    end
  end
  $redis.setex("user_device_jwt:#{self.id}:#{payload['device']}", payload['exp'] - Time.now.to_i, "#{payload['jti']}:#{payload['exp']}")
end

JTIMatcher

如果你想用JTIMatcher的实现某种role的登录(推荐给非user以外的role使用,在这种策略下,可以通过手动修改jti的值让所有旧的token失效),你首先需要给role添加一个jti column。运行rails generate migration add_jti_to_models 生成migrate,然后再migration文件中添加下面的code

def change
  add_column :models, :jti, :string, null: false
  add_index :models, :jti, unique: true
  # If you already have user records, you will need to initialize its `jti` column before setting it to not nullable. Your migration will look this way:
  # add_column :users, :jti, :string
  # User.all.each { |user| user.update_column(:jti, SecureRandom.uuid) }
  # change_column_null :users, :jti, false
  # add_index :users, :jti, unique: true
end

添加完成后运行rails db:migrate,然后在对面role的model里面添加下面这段话

include Devise::JWT::RevocationStrategies::JTIMatcher

devise :database_authenticatable,
       :jwt_authenticatable, jwt_revocation_strategy: self

Refresh_token

通过调用Warden::JWTAuth::UserEncoder.new.call方法生成新的token。对于blacklist,如果你没有记录以前的token是没有办法让旧token失效,如果你在sign_in时记录了token,那么你可以通过把改token加入黑名单来让旧token失效。对于JTIMatcher,你可以通过修改model的jti来实现让旧token全部失效。

token, payload = Warden::JWTAuth::UserEncoder.new.call(current_model, :model, nil)

上面单设备登录对应refresh_token的example

def refresh_token
  user_device_jwt = $redis.get("user_device_jwt:#{current_user.id}:#{device_type_d}")
  auth_should_update = false
  if user_device_jwt.present?
    exp = user_device_jwt.split(':').last.to_i
    if exp < 3.days.after.to_i
      current_user.device_type = device_type_d
      token, payload = Warden::JWTAuth::UserEncoder.new.call(current_user, :user, nil)
      current_user.on_jwt_dispatch(token, payload)
      response.set_header('Authorization', "Bearer #{token}")
        render json: {
          ok: true
        } and return
    end
  end
  render json: {
    ok: false
  }
end

关于jwt token,你可以在devise.rb中在jwt.expiration_time = 1.day.to_i修改token的有效期。

Clone this wiki locally