在Rails API中使用devise-jwt+devise实现认证及React前端集成
解决Devise-JWT + React的Token获取与前端认证问题
第一步:完善Rails后端的Devise-JWT配置,确保Token正确生成与返回
你现在的后端已经搭好了基础的Devise路由和自定义控制器,但还缺少Devise-JWT的核心配置,这会导致无法生成并返回有效的access token。我一步步帮你补全:
1. 添加Devise-JWT核心初始化配置
首先在config/initializers/devise.rb里补充JWT相关设置,这是生成token的关键:
Devise.setup do |config| # 保留你已有的其他Devise配置... config.jwt do |jwt| # 从Rails凭证里读取密钥(先运行rails credentials:edit添加devise_jwt_secret_key) jwt.secret = Rails.application.credentials.devise_jwt_secret_key! # 指定哪些请求需要返回token:登录、注册接口 jwt.dispatch_requests = [ ['POST', %r{^/api/v1/users/sign_in$}], ['POST', %r{^/api/v1/users$}] ] # 指定登出请求需要吊销token jwt.revocation_requests = [ ['DELETE', %r{^/api/v1/users/sign_out$}] ] # 设置token过期时间,这里设为1天,你可以按需调整 jwt.expiration_time = 1.day.to_i end end
2. 修复Registrations控制器的问题
你的注册控制器有几个小问题需要修正:
- 文件路径和命名空间不匹配:控制器类是
Api::V1::RegistrationsController,文件应该放在app/controllers/api/v1/registrations_controller.rb respond_to写法错误,不需要额外指定controllers参数- 注册成功后,我们可以主动把token返回在响应体里,方便前端直接获取
修改后的代码:
# 路径:app/controllers/api/v1/registrations_controller.rb class Api::V1::RegistrationsController < Devise::RegistrationsController respond_to :json before_action :sign_up_params, only: [:create] def create build_resource(sign_up_params) if resource.save # 调用Devise-JWT的工具生成token token = Warden::JWTAuth::UserEncoder.new.call(resource, :user, nil).first render json: { user: Api::V1::UserSerializer.new(resource), meta: { message: 'Sign up success' }, access_token: token }, status: :created else render json: { errors: resource.errors.full_messages, meta: { message: 'Sign up failed' } }, status: :unprocessable_entity end end protected def sign_up_params params.require(:sign_up).permit(:first_name, :last_name, :mobile, :email, :password, :password_confirmation) end end
3. 完善Sessions控制器
同样,登录控制器的命名空间和文件路径要匹配,同时我们要在登录成功时返回token:
# 路径:app/controllers/api/v1/sessions_controller.rb class Api::V1::SessionsController < Devise::SessionsController respond_to :json private def respond_with(resource, _opts = {}) # 登录成功返回token和用户信息 token = Warden::JWTAuth::UserEncoder.new.call(resource, :user, nil).first render json: { user: Api::V1::UserSerializer.new(resource), meta: { message: 'Login success' }, access_token: token }, status: :ok end def respond_to_on_destroy # 登出成功的响应 render json: { meta: { message: 'Logout success' } }, status: :no_content end end
4. 确认Routes配置正确
确保你的路由里Devise控制器的路径和我们修改的一致:
Rails.application.routes.draw do # 生产环境记得删掉这部分Sidekiq配置 require 'sidekiq/web' mount Sidekiq::Web => '/sidekiq' namespace :api, defaults: { format: 'json' } do namespace :v1 do devise_for :users, controllers: { sessions: 'api/v1/sessions', registrations: 'api/v1/registrations' } # 可以加一个获取当前用户的接口,方便前端验证 get '/users/me', to: 'users#show' end end end
第二步:React前端的Token使用与身份认证
现在后端已经能在注册/登录时返回access_token了,前端需要完成以下操作:
1. 注册/登录请求处理
用fetch或axios发送请求,获取token后存在localStorage里(也可以用sessionStorage,根据你的需求选择):
// 注册示例 const handleSignUp = async (formData) => { try { const response = await fetch('/api/v1/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sign_up: formData }) }); const data = await response.json(); if (response.ok) { // 存储token和用户信息 localStorage.setItem('access_token', data.access_token); localStorage.setItem('current_user', JSON.stringify(data.user)); // 跳转到首页或仪表盘 window.location.href = '/dashboard'; } else { alert(data.errors.join(', ')); } } catch (error) { console.error('Sign up error:', error); } }; // 登录示例 const handleLogin = async (formData) => { try { const response = await fetch('/api/v1/users/sign_in', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user: formData }) // 注意这里参数是user,不是sign_up }); const data = await response.json(); if (response.ok) { localStorage.setItem('access_token', data.access_token); localStorage.setItem('current_user', JSON.stringify(data.user)); window.location.href = '/dashboard'; } else { alert('Invalid email or password'); } } catch (error) { console.error('Login error:', error); } };
2. 带Token发送认证请求
封装一个通用的请求函数,每次发送需要权限的请求时,自动在请求头里带上token:
// 封装带token的fetch const authenticatedFetch = async (url, options = {}) => { const token = localStorage.getItem('access_token'); const headers = { 'Content-Type': 'application/json', ...options.headers, // 如果有token就添加Authorization头 ...(token && { Authorization: `Bearer ${token}` }) }; return fetch(url, { ...options, headers }); }; // 示例:获取当前用户信息 const fetchCurrentUser = async () => { try { const response = await authenticatedFetch('/api/v1/users/me'); if (!response.ok) throw new Error('Token invalid'); return await response.json(); } catch (error) { console.error('Fetch user error:', error); // token过期或无效时,清除本地存储并跳转到登录页 localStorage.removeItem('access_token'); localStorage.removeItem('current_user'); window.location.href = '/login'; } };
3. 登出处理
发送登出请求并清除本地存储的token:
const handleLogout = async () => { try { await authenticatedFetch('/api/v1/users/sign_out', { method: 'DELETE' }); localStorage.removeItem('access_token'); localStorage.removeItem('current_user'); window.location.href = '/login'; } catch (error) { console.error('Logout error:', error); } };
额外建议
- 生产环境一定要移除Sidekiq的web界面挂载代码,避免安全风险
- 可以用React Context或Redux来全局管理用户状态和token,减少重复代码
- 如果需要处理token过期,可以配置Devise-JWT的刷新令牌功能,前端在token过期前自动请求刷新
内容的提问来源于stack exchange,提问作者Sourabh Banka
相关产品推荐
相关产品推荐

