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
相关产品推荐
相关产品推荐

