在rails中使用jwt实现授权和权限控制
关于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做授权和权限控制就完成了