Rails Rails 里面使用 jwt_token 来鉴权

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

简介

前后端分离之后,关于鉴权方式的选择里,除了传统的使用 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 里,这个随意

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 验证的,除非私钥泄漏

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