rails7: jwt 与 api
jwt原理
分析
- 这个用在登录的场景,所以与
sessions
相关 - 前端登录的时候,会带
token
在headers
里,理论上,后端所有的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
参考
- https://chat.zhile.io/c/b99f04d9-dfdf-49c4-8ac0-43a19da21f68
- https://chat.zhile.io/c/5c835d14-5870-43dd-a228-b73cee9b2246
- https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
- https://www.bluebash.co/blog/rails-6-7-api-authentication-with-jwt/
- https://github.com/jwt/ruby-jwt
- https://sdrmike.medium.com/rails-7-api-only-app-with-devise-and-jwt-for-authentication-1397211fb97c