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

Postman请求/login成功,Vue中Axios请求返回401(Rails+Vue)

Rails后端+Vue前端登录401问题排查与解决

我用Rails做后端,Vue做前端。用Postman向http://localhost:3000/login发送以下请求体能成功登录:

{
  "user": {
    "email": "userone@gmail.com",
    "password": "abc123"
  }
}

但在Vue里发送相同请求却返回401未授权错误。


登录组件代码

<script>
import {defineComponent} from 'vue'
import API from "@/utils/API";

export default defineComponent({
  name: "LoginForm",
  data: () => ({
    email: null,
    password: null,
    isValid: true
  }),
  methods: {
    async submitForm () {
      await API.post('/login', {
        user: {
          email: this.email,
          password: this.password
        }
      }).then(async () => {
        const result = await API.get('/current_user')
        console.log('current user returns: ', result.data)
        this.$router.push({ path: '/' })
      })
    }
  }
})
</script>

<template>
  <v-card class="mx-15">
    <v-card-title>Login Form</v-card-title>
    <v-card-text>
      <v-form v-model="isValid">
        <v-text-field
            label="Email"
            v-model="email"
            :rules="[v => !!v || 'Email is required']"
            error-count="2"
            required
        ></v-text-field>
        <v-text-field
            label="Password"
            v-model="password"
            type="password"
            :rules="[v => !!v || 'Password is required']"
            error-count="5"
            required
        ></v-text-field>
      </v-form>
    </v-card-text>
    <v-card-actions>
      <v-btn
          color="primary"
          :disabled="!isValid"
          @click = "submitForm"
      >
        Login
      </v-btn>
    </v-card-actions>
  </v-card>
</template>

API工具类代码(API.js)

import axios from 'axios'
// import router from '../router'

const config = {
    baseURL: process.env.VUE_APP_ROOT_API,
    headers: {
        'Content-Type': 'application/json',
        // 'Content-Type': 'application/vnd.api+json',
        Accept: '*/*',
        // Accept: 'application/vnd.api+json',
        Authorization: 'Bearer ' + localStorage.getItem('token')
    },
    xsrfCookieName: 'csrftoken',
    xsrfHeaderName: 'X-CSRFToken',
    withCredentials: false
}

const API = axios.create(config)

export default API

注:试过直接使用原生axios,结果一致。


后端请求日志

成功请求(Postman)

Processing by Users::SessionsController#create as */*
  Parameters: {"user"=>{"email"=>"userone@gmail.com", "password"=>"[FILTERED]"}, "session"=>{"user"=>{"email"=>"userone@gmail.com", "password"=>"[FILTERED]"}}}
headers are #<ActionDispatch::Http::Headers:0x00000001070ab920>
  User Load (1.3ms)  SELECT "users".* FROM "users" WHERE "users"."email" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["email", "userone@gmail.com"], ["LIMIT", 1]]
  ↳ app/controllers/users/sessions_controller.rb:16:in `create'
Completed 200 OK in 250ms (Views: 0.2ms | ActiveRecord: 9.0ms | Allocations: 42336)

失败请求(Vue前端)

Started POST "/login" for ::1 at 2023-10-30 19:48:55 -0400
Processing by Users::SessionsController#create as */*
  Parameters: {"user"=>{"email"=>"userone@gmail.com", "password"=>"[FILTERED]"}, "session"=>{"user"=>{"email"=>"userone@gmail.com", "password"=>"[FILTERED]"}}}
headers are #<ActionDispatch::Http::Headers:0x00000001073c3ea0>
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms | Allocations: 208)

问题排查与解决

从日志可见,失败请求未执行User Load查询,说明后端未进入用户验证逻辑就直接返回401,核心问题有两个:

1. 无效的Authorization请求头

登录时localStorage中还没有token,全局配置里的Authorization: 'Bearer ' + localStorage.getItem('token')会变成Bearer null,这个无效头触发了Rails后端的认证拦截。而Postman请求不会携带该无效头,因此能正常执行。

2. CSRF验证未正确处理

Rails默认开启CSRF保护,当前API配置withCredentials: false,导致axios无法自动携带cookie中的CSRF token,而Postman可能自动处理了该token或后端对其请求跳过了验证。

修复步骤

修改API.js配置

移除全局默认的Authorization头,改为登录成功后动态设置;同时开启withCredentials以携带CSRF token:

import axios from 'axios'

const config = {
    baseURL: process.env.VUE_APP_ROOT_API,
    headers: {
        'Content-Type': 'application/json',
        Accept: '*/*'
    },
    xsrfCookieName: 'csrftoken',
    xsrfHeaderName: 'X-CSRFToken',
    withCredentials: true
}

const API = axios.create(config)

// 登录成功后调用此方法设置认证token
export const setAuthToken = (token) => {
  if (token) {
    API.defaults.headers.common['Authorization'] = `Bearer ${token}`
  } else {
    delete API.defaults.headers.common['Authorization']
  }
}

export default API

更新登录组件的提交方法

登录成功后获取token并设置到axios头中:

async submitForm () {
  try {
    const loginRes = await API.post('/login', {
      user: {
        email: this.email,
        password: this.password
      }
    })
    // 根据后端返回的token结构调整,此处假设token在data.token中
    const token = loginRes.data.token
    localStorage.setItem('token', token)
    setAuthToken(token)

    const result = await API.get('/current_user')
    console.log('current user returns: ', result.data)
    this.$router.push({ path: '/' })
  } catch (err) {
    console.error('登录失败:', err)
  }
}

配置Rails跨域与CSRF

修改config/initializers/cors.rb,允许前端域名携带credentials:

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:8080' # 替换为你的Vue前端域名
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head],
      credentials: true
  end
end

内容的提问来源于stack exchange,提问作者user2799827

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 16:14:55