You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:44:52