【问题标题】:Authentication failure after page reload (Vue, Expressjs with Nodejs)页面重新加载后身份验证失败(Vue、Expressjs 和 Nodejs)
【发布时间】:2020-06-26 03:15:48
【问题描述】:

现状

我正在开发 nodejs 后端服务器和 vue 前端应用程序,它们在不同的端口(localhost:3000 和 localhost:8080)下运行。为了启用 CORS 连接,我从 vue.config.js 文件中配置了 devServer 代理。

vue.config.js

module.exports = {
    devServer: {
        proxy: {
            '/users': {
                target: 'http://127.0.0.1:3000/users',
                changeOrigin: true,
                pathRewrite: {
                    '^/users':''
                }
            },
            '/tasks': {
                target: 'http://127.0.0.1:3000/tasks',
                changeOrigin: true,
                pathRewrite: {
                    '^/tasks': ''
                }
            }
        }
    },
    outputDir: '../backend/public'
}

并在技术上使用 cors.js 来启用对后端服务器的请求,这是由 expressjs 实现的。 我正在使用 vue 组件发送请求以从后端服务器检索数据。它可以从服务器获取数据正常工作,我的目标是在重新加载页面时做出相同的行为。但是,每当我重新加载同一页面时,它总是显示由我自己编写的后端代码设置的 401 http 响应状态。

enter image description here

研究和试验至​​今

在我继续尝试之前,我应该首先介绍要操作的强制代码。不知何故,这至少解释了其中 vuex 操作使用 axios,axios 最终使用后端路由器。

tasks.module.js

import axios from "axios"
import authHeader from '../../services/auth-header'

export const tasks = {
    state: {
        tasks: []
    },

    getters: {
        allTasks: (state) => state.tasks
    },

    actions: {
        async fetchTasks({ commit }) {
            const response = await axios.get('http://127.0.0.1:3000/tasks', {headers: authHeader()})

            commit('setTasks', response.data)

            axios.defaults.headers.common['Authorization'] = authHeader()
        },

        async addTask({ commit }, description) {
            const response = await axios.post('http://127.0.0.1:3000/tasks', { description, completed: false}, {headers: authHeader()})

            commit('newTask', response.data)
        },

        async updateTask({ commit }, updTask) {
            const response = await axios.patch('http://127.0.0.1:3000/tasks/'+updTask.id, updTask, {headers: authHeader()})

            commit('updateTask', response.data)
        }
    },

    mutations: {
        setTasks: (state, tasks) => (state.tasks = tasks),
        newTask: (state, task) => state.tasks.unshift(task),
        updateTask: (state, updTask) => {
            let updates = Object.keys(updTask)

            updates.forEach((update) => {
                state.task[update] = updTask[update]
            })
        }
    }
}

TaskManager.vue

<template>
  <div class="container">
    <div class="jumbotron">
      <h3>Task Manager</h3>
      <AddTask/>
      <Tasks/>
    </div>
  </div>
</template>

<script>
import Tasks from './components/Tasks'
import AddTask from './components/AddTask'

export default {
  name:'TaskManager',
  components: {
    Tasks,
    AddTask
  }
}
</script>

Tasks.vue

<template>
<div>
    <div>
        <div class="legend">
            <span>Double click to mark as complete</span>
            <span>
                <span class="incomplete-box"></span> = Incomplete
            </span>
            <span>
                <span class="complete-box"></span> = Complete
            </span>
        </div>
    </div>
    <div class="tasks">
        <div
            @dblclick="onDblClick(task)"
            v-for="task in allTasks"
            :key="task.id"
            class="task"
            v-bind:class="{'is-completed':task.completed}">
            {{task.description}}
        </div>
    </div>
</div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex'
export default {
    name: "Tasks",
    methods:{
        ...mapActions(['fetchTasks', 'updateTask']),
        onDblClick(task) {
            const updTask = {
                id: task._id,
                description: task.description,
                completed: !task.completed
            }
            console.log(updTask)
            this.updateTask(updTask)
        }
    },
    computed: {
        ...mapGetters(['allTasks']),
    },
    created() {
        this.fetchTasks()
    }
}

现在我需要介绍一下我尝试解决的问题

  1. 配置CORS 选项

    由于这个错误页面没有显示任何应该在请求头中设置的授权头,我想出了我启用 cors 连接的方式,我相信这会启用预检请求。这是我从后端代码配置的中间件行为。 task.js(快速路由文件)

const router = new express.Router()
const auth = require('../middleware/auth')
const cors = require('cors')
const corsOptions = {
    origin: 'http://127.0.0.1:3000',
    allowedHeaders: 'content-Type, Authorization',
    maxAge:3166950
}
router.options(cors(corsOptions))

router.get('/tasks', auth, async (req, res) => {
    const match = {}
    const sort = {}

    if(req.query.completed) {
        match.completed = req.query.completed === 'true'
    }

    if(req.query.sortBy) {
        const parts = req.query.sortBy.split('_')
        sort[parts[0]] = parts[1] === 'desc' ? -1:1             // bracket notation
    }

    try {
        await req.user.populate({
            path: 'tasks',
            match,
            options: {
                limit: parseInt(req.query.limit),
                skip: parseInt(req.query.skip),
                sort
            }
        }).execPopulate()
        console.log(req.user.tasks)
        res.status(200).send(req.user.tasks)
    } catch (e) {
        res.status(500).send(e)
    }
})
module.exports = router

auth.js(中间件)

const jwt = require('jsonwebtoken')
const User = require('../models/user')

const auth = async (req, res, next) => {
    try {
        const token = req.header('Authorization').replace('Bearer ','')
        const decoded = jwt.verify(token, 'thisisnewcourse')
        console.log('decoded token passed')
        const user = await User.findOne({ _id: decoded._id, 'tokens.token': token})
        console.log('user found')

        if(!user) {
            throw new Error()
        }
        req.token = token
        req.user = user
        next()

    } catch (error) {
        console.log('error caught')
        res.status(401).send({error: 'please authenticate'})
    }
}

module.exports = auth
  1. 登录后设置Authorization header为axios默认header

auth.module.js(由于登录正常,我只复制登录操作部分)

actions: {
        async login ({ commit }, user){
            try {
                const response = await axios.post('http://127.0.0.1:3000/users/login', user)

                if(response.data.token){
                    localStorage.setItem('user', JSON.stringify(response.data))

                    axios.defaults.headers.common['Authorization'] = `Bearer ${response.data.token}`
                }

                commit('loginSuccess', response.data)
            } catch (e) {
                console.log(e)
            }


        }
  1. 快速路由上的中间件链接(cors, auth)

我在同一个后端代码(task.js)上尝试了两个不同的中间件

router.get('/tasks', [cors(corsOptions), auth], async (req, res) => {
    // same as previously attached code
}

现在我相信引用 another post with similar issue 会帮助我,但它是关于启用 CORS,而不是标题不是通过预检请求或其他类型的请求发送的问题。

【问题讨论】:

    标签: node.js express authentication cors vuex


    【解决方案1】:

    您没有包含 authHeader 的代码,但我认为它只是返回 Authorization 标头的值。

    我觉得这有点可疑:

    async fetchTasks({ commit }) {
      const response = await axios.get('http://127.0.0.1:3000/tasks', {headers: authHeader()})
    
      commit('setTasks', response.data)
    
      axios.defaults.headers.common['Authorization'] = authHeader()
    },
    

    最后一行似乎是在尝试全局设置 Authorization 标头,以便将其包含在所有后续 axios 请求中。这很好,但不早点这样做似乎很奇怪。您在 login 操作中有类似的行,这是有道理的,但我认为刷新页面时不会调用它。

    然后就是这个:

    {headers: authHeader()}
    

    如果authHeader 返回Authorization 标头的,那么这将不起作用。相反,您需要:

    {headers: { Authorization: authHeader() }}
    

    理想情况下,您不需要在此处设置任何标头,而是在尝试此请求之前设置全局标头。

    虽然这不是您的问题的直接原因,但您似乎对 CORS 感到困惑。您已经配置了代理,这意味着您没有使用 CORS。您提出的请求来自同一来源,因此 CORS 不适用。如果您不发出跨域请求,则不需要包含 CORS 响应标头。如果您确实想发出跨域请求,请不要使用代理。您应该在开发期间尝试模仿您的生产环境,因此如果您打算在生产中使用 CORS,您应该在开发期间使用它。否则,请坚持使用代理。

    【讨论】:

    • 感谢您的回答@skirtle。仅供参考,我确实输入了 {Authorization : Bearer ${user.token}} authHeader() 返回。但是它一直在路由器代码上显示 401 错误消息。自从我应用您的建议以来,我遇到的最大不同是 Web 浏览器现在向我显示导航栏和 TaskManager.vue 视图。希望这意味着我得到了您的帮助,以缩小 Task.vue 发生“fetchTask”动作的问题。我已经从控制台追踪了错误消息,以查看 axios 的角色在代码后面是如何工作的,但这还不够
    • @JungHyunLee 您发布的最初显示标头的图片显示没有Authorization 请求标头被发送到服务器。这就是您需要集中调试工作的地方。如果没有该标头,您只会从服务器获得 401。有几个不同的问题可能会导致 401,因此即使您修复了其中一个问题,您仍可能会看到相同的错误消息。继续检查开发人员工具中的请求标头,这是调试此问题的一种更可靠的方法。一点控制台日志记录,您很快就会修复它。
    • 我终于找到了预期的结果!这些是我尝试解决问题的方法。从开始到现在已经快一周了,我感到非常满意。 1. 我将中间件放回路由器代码中,返回到 console.logging(现在可以再次使用它)/ 2. 将 async 函数更改为正常功能,当然要使用 promise(现在可以再次使用它)/ 3 . 因为浏览器不断向我显示 CORS 问题猜测“授权”标头的原因,所以调用预检请求是不寻常的标头(我毫不怀疑 CORS 强制预检请求)/.. 继续
    • 所以我从 vue.config.js 中省略了 devServer 代理,并在 auth 中间件写入中设置了响应头 (res.header('Access-Control-Allow-Origin', '*') res.header ('Access-Control-Allow-Header', 'Content-Type') res.header('Access-Control-Allow-Header', 'Authorization')) 并且完全可以设置预检标头以允许授权标头。最后,当我 console.logged axios 默认标头时,我从登录操作中错误地设置了标头。所以我从 ${user.token} 更正为 ${response.data.token} @skirtle
    猜你喜欢
    • 2019-10-08
    • 2018-01-08
    • 2018-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-08
    • 2016-08-13
    • 1970-01-01
    相关资源
    最近更新 更多