rails7: jwt 与 api

jwt原理
更新于: 2023-12-14 21:36:17
rails7 token

分析

  • 这个用在登录的场景,所以与 sessions 相关
  • 前端登录的时候,会带 tokenheaders 里,理论上,后端所有的 api 都需要经过这个 token 的验证,才能表明登录成功

场景

  • 我希望用 rails 完成如下功能
    • 我的路由是 /api/v1 为前缀
    • 我已经有了 user 的 model (username/password_digest)
    • username/password 登录,并返回 token
    • /api/v1 下的 controller 都会有 require_login 来校验 token 是否存在

添加 gem

gem 'bcrypt', '~> 3.1.7'
gem 'jwt', '~> 2.2', '>= 2.2.3'

Users

最基础的 users model

rails generate model User username:string password_digest:string
rails db:migrate
# app/models/user.rb
class User < ApplicationRecord
  has_secure_password

  # Other validations or associations can be added here
end

添加 controller

添加 controller

rails g controller Api::V1::Users
# app/controllers/api/v1/users_controller.rb
class Api::V1::UsersController < ApplicationController
  before_action :authenticate, only: [:profile]
  
  def create
    user = User.new(user_params)

    if user.save
      render json: { token: generate_token(user.id) }
    else
      render json: { error: user.errors.full_messages }, status: :unprocessable_entity
    end
  end

  def login
    user = User.find_by(username: params[:username])

    if user && user.authenticate(params[:password])
      render json: { token: generate_token(user.id) }
    else
      render json: { error: 'Invalid username or password' }, status: :unauthorized
    end
  end

  private
  
  def authenticate
    token = request.headers['Authorization'].split(' ').last
    payload = JWT.decode(token, ENV['SECRET_KEY']).first
    @current_user = User.find(payload['user_id'])
  end


  def user_params
    params.require(:user).permit(:username, :password)
  end

  def generate_token(user_id)
    payload = { user_id: user_id }
    JWT.encode(payload, ENV['SECRET_KEY'])
  end
end

思路清晰版

添国 app/controllers/concerns/json_web_token.rb 公用模块,以下为 .env 文件

SECRET_KEY=faGOGX_lCPgpPHJAv1mnUtv0aYC1UZ7_
require 'jwt'

module JsonWebToken
    extend ActiveSupport::Concern
    SECRET_KEY = ENV.fetch('SECRET_KEY_BASE')
    
    def jwt_encode(payload, exp = 2.years.from_now)
        JWT.encode(payload, SECRET_KEY)
    end
    
    def jwt_decode(token)
        decoded_token = JWT.decode(token, SECRET_KEY)
        HashWithIndifferentAccess.new(decoded_token[0])
    end
end

基础类 base_controller.rb,实际业务中可能要添加 skip_action

class ApplicationController < ActionController::API
    include JsonWebToken
    before_action :authenticate_request
    
    private
    def authenticate_request
        header = request.headers['Authorization']
        header = header.split(' ').last if header
        begin
            decoded = jwt_decode(header)
            @current_user = User.find(decoded[:user_id])
        rescue ActiveRecord::RecordNotFound => e
            render json: { errors: e.message }, status: :unauthorized
        rescue JWT::DecodeError => e
            render json: { errors: e.message }, status: :unauthorized
        end
    end
end

参考