由于 CORS 选项预检,ReactJS 无法发送 POST 请求

Posted

技术标签:

【中文标题】由于 CORS 选项预检,ReactJS 无法发送 POST 请求【英文标题】:ReactJS unable to send POST request due to CORS options preflight 【发布时间】:2018-12-02 03:15:07 【问题描述】:

每当我向服务器发送POST 请求时,OPTIONS 总是总是被返回。

我有一个使用ReactApollo 的非常简单的登录应用程序。当我提交表单时,应该使用 POST 请求将mutation 发送到服务器,但 OPTIONS 总是会拦截。

正如您在此处看到的(使用 chrome):

但是当我使用 Firefox 时,在OPTIONS 之后,接下来会请求 POST,如下所示:

我知道我无法禁用预检选项,因为正如我所研究的那样,使用 Content-Type: application/json / bodyParser.json() 会触发预检。

但我需要Content-Type: application/json 才能让graphql 工作。但是有什么方法可以只POST 请求而忽略OPTIONS? 我需要 POST 请求返回的 this.props.data

所以我可以将令牌存储在客户端浏览器的 localStorage 中...

我的问题是:

由于 OPTIONS (chrome),POST 请求永远不会发生 我无法在 React 组件中访问 this.props.data,因为 OPTIONS 总是在实际 POST 之前返回FIRST(在 Firefox 中)

我只想从 POST 请求中获取data ????

这是 cors 的问题吗?有人可以告诉我我做错了什么吗?非常感谢您。

PS: 我已经尝试过这个链接:https://github.com/graphql/express-graphql/issues/14 但我仍然无法解决这个问题......

这是我的服务器:

const express = require('express')
const morgan = require('morgan')
const bodyParser = require('body-parser')
// Grahql
const expressGraphQL = require('express-graphql')
const jwt = require('jsonwebtoken')
const cors = require('cors')
const chalk = require('chalk')

const model = require('./db/models')
const schema = require('./schema/schema')

const app = express()
app.use(cors())
app.options('*', cors())
app.use(bodyParser.json())
app.use(morgan('dev'))

// secret key
const SECRET = 'eggieandsausage'

// this method checks token authenticity from
// user attempting to login
const verifyTokenAuthenticity = async (req, res, next) => 
    const token = req.headers['authentication']
    try 
        // verify token from headers
        const  user  = await jwt.verify(token, SECRET)
        // store user in req
        req.user = user
     catch(err) 
        console.log(err)
    
    // proceed
    next()


// Graphql
//app.use(verifyTokenAuthenticity)

app.use('/graphql', expressGraphQL(req => (
    schema,
    graphiql: true,
    // this context is accessible within resolve()
    context: 
        model,
        SECRET,
        user: req.user
    
)))


// Initial Route
app.get('/', (req, res) => 
    res.sendFile(__dirname + '/index.html')
)

const PORT = process.env.PORT || 8080
app.listen(PORT, () => console.log(chalk.green(`MAGIC STARTS AT PORT $PORT`)))

这是我的突变:

const graphql = require('graphql')
const 
    GraphQLObjectType,
    GraphQLString,
    GraphQLNonNull
 = graphql
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')



const UserType = require('./types/user_type')

const mutation = new GraphQLObjectType(
    name: 'Mutation',
    fields: 
        signup: 
            type: UserType,
            args:  
                email:  type: new GraphQLNonNull(GraphQLString) ,
                password:  type: new GraphQLNonNull(GraphQLString) 
            ,
            resolve(parentValue,  email, password ,  model ) 
                return new Promise((resolve, reject) => 
                    model.User.create(
                        email,
                        password
                    )
                    .then(user => 
                        if (!user)
                            return reject('Sorry. something went wrong')
                        resolve(user)
                    )
                    .catch(error => reject(error))
                )
            
        ,
        signin: 
            type: UserType,
            args: 
                email:  type: new GraphQLNonNull(GraphQLString) ,
                password:  type: new GraphQLNonNull(GraphQLString) 
            ,
            // params: parentValue, args, context
            resolve(parentValue,  email, password ,  model, SECRET ) 
                return new Promise((resolve, reject) => 
                    model.User.find( where:  email  )
                        .then(user => 
                            if (!user)
                                return reject('Invalid Credentials')
                            if (!bcrypt.compareSync(password, user.password))
                                return reject('Invalid Credentials')

                            const token = jwt.sign( user:  id: user.id, email: user.email  , SECRET,  expiresIn: '1yr' )
                            user.token = token  // add token property to user object which will be resolved

                            resolve(user)

                        )
                        .catch(error => reject(error))
                )

            
        
    
)

module.exports = mutation

这是登录页面

import React from 'react'
import gql from 'graphql-tag'
import  graphql  from 'react-apollo'
class App extends React.Component 
    constructor(props) 
        super(props)
        this.state =  email: 'edrren@gmail.com', password: 'password' 
    
    onSubmit(e) 
        e.preventDefault()
        const  email, password  = this.state
        console.log( email, password )
        this.props.mutate(
            variables:  email, password 
        ).then(() => console.log(this.props.data))
        .catch(() => console.log("error"))
    
    render() 
        return (
            <form onSubmit=this.onSubmit.bind(this)>
                <label>Email</label>
                <input type="text" onChange=(e) => this.setState( email: e.target.value) value=this.state.email />
                <br/>
                <label>Password</label>
                <input type="password" onChange=(e) => this.setState( password: e.target.value) value=this.state.password />
                <button type="submit" >Login</button>
            </form>
        )
    

const mutation = gql`
    mutation SignIn($email: String!, $password: String!)
      signin(email: $email, password: $password) 
        token
      
    
`;
export default graphql(mutation)(App)

我的提供者:

import React from 'react'
import ReactDOM from 'react-dom'

import ApolloClient,  createNetworkInterface  from 'apollo-client'
import  ApolloProvider  from 'react-apollo'
import  BrowserRouter as Router, Route, Switch  from 'react-router-dom'
import App from './App'
import Header from './components/Header'
import Profile from './components/Profile'
const networkInterface = createNetworkInterface(
  uri: 'http://localhost:8080/graphql'
);
const client = new ApolloClient(
    networkInterface,
    dataIdFromObject: o => o.id
)
const Root = () => 
  return (
    <ApolloProvider client=client>
      <Router>
          <Switch>
          <Route exact path="/" component=App />
          <Route path="/profile" component=Profile />
        </Switch>
      </Router>
    </ApolloProvider>
  )

ReactDOM.render(<Root />, document.getElementById('root'));

【问题讨论】:

你的 UI 的 URL 是什么? @Hriday 是localhost:3000 如果你使用 create-react-app 那么你可以代理你的请求。例如"proxy": "/graphql": "target": "http://localhost:8080" 我可以知道把它放在哪里吗?是的,我正在使用 cra 在 package.json 中,最外层,即在主对象中 【参考方案1】:

您应该在 create-react-app 项目中使用代理。

package.json主对象中添加以下代理:

"/graphql": 
  "target": "http://localhost:8080/",
  "changeOrigin": true

它基本上将您的请求从“http://localhost:3000/graphql”代理到“http://localhost:8080/graphql”。

还要更改您的代码以使用相对 api url。所以现在应该是:

 uri: '/graphql'

这将对您的本地 url 进行 api 调用,节点服务器会将其代理到上述目标。因此这里不涉及跨域。

【讨论】:

以上是关于由于 CORS 选项预检,ReactJS 无法发送 POST 请求的主要内容,如果未能解决你的问题,请参考以下文章

React:如何强制浏览器为 OPTION 发送特定的标头(预检)

Cors - 如何处理需要自定义标头的预检选项请求? (AWS:使用 vpc 端点的私有 API 网关)

CORS 预检选项请求的 403 错误。怎么修?

Yii2 和 reactjs CORS 过滤器给出错误:预检响应具有无效的 HTTP 状态代码 401

AWS 中的 403 OPTIONS Cors 错误,预检请求

Slim 框架上 CORS 期间的预检授权标头