-
Notifications
You must be signed in to change notification settings - Fork 25
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的实现某种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_payload
和on_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的实现某种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
通过调用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的有效期。