Rails Rails 里面使用 jwt_token 来鉴权

ad583255925 · 2019年09月30日 · 最后由 ad583255925 回复于 2019年10月08日 · 770 次阅读

简介

前后端分离之后,关于鉴权方式的选择里,除了传统的使用session之外,还可以使用jwt_token来鉴权,尤其是手机端的项目,使用jwt_token鉴权有以下好处

  1. 前端会比较好处理,因为session一般是装在cookies里面,如果要持久化登录的话,对于前端来说拼接cookie会额外带来一些工作,而jwt_token采用自己的独有的header
  2. 服务端不用维护session服务器了,这节省了非常多的开销

大致原理

流程

在前端登录成功后,会把jwt_token返回给前端,而前端之后每次访问都会在header里面加一条(Authorization: Bearer XXXX),XXXX就是放置jwt_token的位置

jwt_token结构

详细的结构可以看这里,我这里就简单的概括一下 jwt分为三段

  1. Header,声明该token的加密方式
  2. payload,主体信息全在这里,你可以在这里控制该用户的登录期限
  3. 验签,把第一部分和第二部分通过服务器的私钥加密,变成第三段

三段数据全部用Base64加密,然后用.来隔开,就生成了jwt_token,一般会长这样

eyJhbGciOiJIUzI1NiJ9.eyJleHBfYXQiOiIyMDE5LTEwLTA2IDE3OjU3OjU0ICswODAwIiwiaWQiOjQsInVzZXJuYW1lIjoi57qq5Lqa55Cq77yI6LaF566h77yJIiwidXNlcl90eXBlIjoic3VwZXJfYWRtaW4iLCJtb2JpbGUiOiIxNzcyMTI3MzIzMyJ9.38VDrWZlDmAY4fiYmJo-_rDKhYzaxEwiewj9ruyVSCQ

安全性

该token的安全保障其实全靠那个私钥(其实基于https完全不用考虑安全问题),当前端试图更改payload信息来登录其他账号时,由于他不知道对称加密要用到的私钥,因此他无法生成正确的第三段信息,因此他无法通过服务器的验证

在Rails里面使用jwt_token

安装gem

gem 'jwt'
$ bundle install

写生成token和验证token的方法

可以放在user模型里,这个随意 生成jwt_token的方法 user.rb

class User < ApplicationRecord
  def generate_jwt_token
    hmac_secret = JWT_HMAC_SECRET ##服务器的私钥,可以是一个字符串或者钥匙,这个自己决定
    payload = {
      exp_at: (Time.now + 1.week).to_s,
      id: id,
      username: username,
      user_type: user_type,
      mobile: mobile
    }
    JWT.encode payload, hmac_secret, 'HS256'
  end
...

验证方法

def self.decoded_jwt_infos token
  JWT.decode(token, JWT_HMAC_SECRET, true, { algorithm: 'HS256' })[0]
end

在控制器层,写current_user方法

class Api::ApiController < ApplicationController
  skip_before_action :verify_authenticity_token

  def check_login
    if current_user.blank?
      render json: {msg: "登录失败", result: false}, status: :forbidden
    end
  end

  def current_user
    if request.headers['Authorization']&.start_with?("Bearer")
      jwt_token = request.headers['Authorization'].split&.last
      jwt_info = User.decoded_jwt_infos(jwt_token)
      if Time.parse(jwt_info["exp_at"]) > Time.now
        @current_user ||= User.unscoped.active.find_by_id(jwt_info["id"])
      end
    end
  rescue JWT::ExpiredSignature, JWT::VerificationError
    nil
  end

end

剩下的就是在登录成功后将生成的token返回,可以放在body里,也可以放在headers里,这个随意

共收到 5 条回复
jwt_info = User.decoded_jwt_infos(jwt_token)
      if Time.parse(jwt_info["exp_at"]) > Time.now
        @current_user ||= User.unscoped.active.find_by_id(jwt_info["id"])
      end

不用判断exp_at了吧

@ericguo 前阵子刚搞了

jasl 回复

我直接用devise-jwt了,这是一个好gem。

不建议把username、user_type、mobile这些私密信息放到payload里面,jwt 不 verify 也可先解出来的

{"exp_at"=>"2019-10-06 17:57:54 +0800", "id"=>4, "username"=>"纪", "user_type"=>"super_admin", "mobile"=>"177"}, {"alg"=>"HS256"}

ysllyfe 回复

嗯,原则上不应该放,但是本身安全层面也不靠这个,有https保护伞,放进去也没什么问题,鉴权的时候前端任何篡改都通不过jwt验证的,除非私钥泄漏

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册