node.js - GraphQL 网关`预检请求未通过访问控制检查`
问题描述
我有一个GraphQL 网关来联合几个子图。网关跨这些子图执行传入操作。
在浏览器控制台中观察到错误
OPTIONS 方法被触发(预检),后端日志中没有任何内容。在下面的浏览器中观察到的错误
Access to fetch at 'http://dev.gateway.mydomain.net:5000/graphql'
from origin 'http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors'
to fetch the resource with CORS disabled.
网络
Access-Control-Allow-Origin: *
选项方法
GraphQL 网关服务器
下面是我的 Apollo 网关的代码
我已将我为解决我的 CORS 问题而尝试的所有内容(TRY 1..7)标记为评论
const { ApolloServer } = require('apollo-server-express');
const { ApolloGateway, RemoteGraphQLDataSource } = require('@apollo/gateway');
const express = require('express');
const path = require('path');
const expressJwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const expressSession = require('express-session');
const passport = require('passport');
const Auth0Strategy = require('passport-auth0');
const jsonwebtoken = require('jsonwebtoken');
const dotenv = require('dotenv');
const cors = require('cors');
const userInViews = require('./lib/middleware/userInViews');
const authRouter = require('./routes/auth');
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const graphqlRouter = require('./routes/graphql');
const headersRouter = require('./routes/headers');
dotenv.config({ path: `./gateway.${process.env.NODE_ENV}.env` });
const port = 5000;
const strategy = new Auth0Strategy(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL: process.env.AUTH0_CALLBACK_URL || `http://localhost:${port}/callback`,
},
(accessToken, refreshToken, extraParams, profile, done) => {
console.log('ACCESS TOKEN', accessToken);
const decoded = jsonwebtoken.decode(accessToken);
const customProfile = profile;
customProfile.permissions = decoded.permissions;
customProfile.authorization = `Bearer ${accessToken}`;
return done(null, customProfile);
},
);
passport.use(strategy);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
const session = {
secret: 'FILTERED',
cookie: {},
resave: false,
saveUninitialized: true,
};
const app = express();
app.use(express.static('assets'));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
if (app.get('env') === 'production') {
session.cookie.secure = true;
app.set('trust proxy', 1);
}
app.use(expressSession(session));
app.use(passport.initialize());
app.use(passport.session());
app.use(userInViews()); // Application level middleware: Add user to view
app.use('/', authRouter);
app.use('/', indexRouter);
app.use('/', usersRouter);
app.use('/', graphqlRouter);
app.use('/', headersRouter);
app.set('json spaces', 2);
const localUserLoginMutationCheck = expressJwt({
secret: 'f.....Y',
algorithms: ['HS256'],
credentialsRequired: false,
});
const auth0AuthenticationCheck = expressJwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,
}),
audience: 'http://dev.gateway.mydomain.net:5000',
issuer: [`https://${process.env.AUTH0_DOMAIN}/`],
algorithms: ['RS256'],
credentialsRequired: false,
});
app.use(
auth0AuthenticationCheck,
);
/**
* TRY 1
*/
const corsOptions = {
origin: '*',
credentials: true, // access-control-allow-credentials:true
optionSuccessStatus: 200,
};
app.use(cors(corsOptions));
/**
* TRY 2
*/
app.use((req, res, next) => {
console.log('INCOMING REQUEST HEADERS', req.headers);
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
console.log('INCOMING REQUEST HEADERS AFTER', req.headers);
next();
});
const gateway = new ApolloGateway({
serviceList: [
{ name: 'siebel', url: 'http://localhost:4000/graphql' },
{ name: 'sciencelogic', url: 'http://localhost:4001/graphql' },
],
buildService({ name, url }) {
return new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }) {
console.log('CONTEXT', context);
request.http.headers.set(
'user',
context.user
? JSON.stringify(context.user)
: null,
);
},
});
},
});
const gqlServer = new ApolloServer({
gateway,
/**
* TRY 3
*/
// cors: true,
/**
* TRY 4
*/
// cors: {
// origin: '*',
// methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
// preflightContinue: false,
// optionsSuccessStatus: 204,
// credentials: true,
// },
/**
* TRY 5
*/
// cors: {
// credentials: true,
// origin: (origin, callback) => {
// const whitelist = [
// 'http://localhost:3000',
// 'https://stage-tool.mydomain.net',
// 'https://stage.tool.mydomain.net',
// 'https://dsr.mydomain.net',
// ];
// if (whitelist.indexOf(origin) !== -1) {
// callback(null, true);
// } else {
// callback(new Error('Not allowed by CORS'));
// }
// },
// },
subscriptions: false,
context: ({ req }) => {
const user = req.user || null;
return { user };
},
introspection: true,
});
// const corsOptions = {
// origin: 'http://localhost:3000',
// credentials: true,
// };
gqlServer.applyMiddleware({
app,
/**
* TRY 6
*/
cors: false,
/**
* TRY 7
*/
// cors: corsOptions,
});
app.listen({ port }, () => {
console.log(`Gateway ready at http://localhost:${port}${gqlServer.graphqlPath}`);
});
子图服务器
const stackTrace = require('stack-trace');
const { ApolloServer } = require('apollo-server');
const { applyMiddleware } = require('graphql-middleware');
const { buildFederatedSchema } = require('@apollo/federation');
const jwt = require('jsonwebtoken');
const nodemailer = require('nodemailer');
const GqlHelpers = require('../gql-helpers');
const { siebelPermissions } = require('../../permissions');
const SiebelContactIntegration = require('./lib/ads-contact');
console.debug(`env ${JSON.stringify(process.env, null, 2)}`);
const resolvers = require(`./lib/gql/siebel.resolvers.${process.env.NODE_ENV}`);
const siebelSchema = GqlHelpers.gqlImport(__dirname, `./lib/gql/siebel.${process.env.NODE_ENV}.graphql`);
const typeDefs = GqlHelpers.gqlWrapper([siebelSchema]);
const federatedSchema = buildFederatedSchema([{ typeDefs, resolvers }]);
const server = new ApolloServer(
{
cors: true,
schema: applyMiddleware(
federatedSchema,
siebelPermissions,
),
context: ({ req }) => {
const jwtToken = req.headers.authorization ? req.headers.authorization : null;
console.log('HEADERS (SIEBEL)', req.headers);
const jwtTokenArray = jwtToken ? jwtToken.split(' ') : [];
const decodedJwt = jwtTokenArray.length > 0 ? jwt.decode(jwtTokenArray[1]) : null;
const jwtIssuer = decodedJwt && decodedJwt.iss ? decodedJwt.iss : null;
const jwtSubject = decodedJwt && decodedJwt.sub ? decodedJwt.sub : null;
const user = req.headers.user ? JSON.parse(req.headers.user) : null;
return { user, jwtIssuer, jwtSubject };
},
dataSources: () => ({
siebelContactIntegration: new SiebelContactIntegration(),
}),
cacheControl: {
defaultMaxAge: 3600,
},
introspection: true,
},
);
server.listen(
{
port: 4000,
},
() => {
console.log(` Siebel GraphQL Server ready at ${process.env.SAPI_URL}${server.graphqlPath}`);
},
);
ReactJs 客户端使用apollo-client
库
注意获取选项设置为no-cors
import React, { useEffect, useState } from "react";
import {
ApolloClient,
InMemoryCache,
HttpLink,
ApolloProvider,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { useAuth0 } from "@auth0/auth0-react";
function ApolloWrapper({ children }) {
const { isAuthenticated, getAccessTokenSilently } = useAuth0();
const [bearerToken, setBearerToken] = useState("");
const httpLink = new HttpLink({
uri: "http://dev.gateway.mydomain.net:5000/graphql",
});
useEffect(() => {
const getToken = async () => {
const token = isAuthenticated ? await getAccessTokenSilently() : "";
setBearerToken(token);
console.log(token);
};
getToken();
}, [getAccessTokenSilently, isAuthenticated]);
const authLink = setContext((_, { headers, ...rest }) => {
if (!bearerToken) return { headers, ...rest };
return {
...rest,
headers: {
...headers,
authorization: `Bearer ${bearerToken}` || null,
},
};
});
const client = new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink),
fetchOptions: {
mode: 'no-cors', // <== Note the fetch options
},
});
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}
export default ApolloWrapper;
使用 Apollo 客户端useLazyQuery
执行 GraphQL 查询。
import { useLazyQuery } from "@apollo/client";
const [getEmail, { loading, error, data }] = useLazyQuery(
gqlSiebelFindContact,
{
errorPolicy: "all",
}
);
任何指针?
我试图弄清楚我为什么会得到这个问题,preflight request doesn't pass access control check
以及这个问题是要在前端还是后端代码上解决。
我可以通过将 Chrome 浏览器配置为no CORS
.
非常欢迎任何进一步的故障排除和发现问题的指示
链接
解决方案
错误消息指向失败的预检请求,这是一个没有凭据的 OPTIONS 请求。此类预检请求应由app.use(cors(corsOptions))
中间件处理。app.use(passport.session())
但是这个中间件来得比较晚,所以如果任何早期的中间件(例如, )已经响应了这个 OPTIONS 请求,这将不起作用。
响应中的 OPTIONS 请求的屏幕截图Content-Length: 8
似乎暗示这发生在这里。
推荐阅读
- php - 如何在 PHP 数组中查找值
- c - 使用用户定义的函数在 C 中反转给定字符串的单词
- jquery - jQuery 数据表返回 0
- ruby-on-rails - 如何识别自定义 COP 中的全局 Rails 设置?
- android - 未应用 AlertDialog 的自定义主题
- python - 对数字元素求和以获得所需的结果
- python - 如何分隔名称和数字总和?
- amazon-web-services - AWS IAM 根据 dynamodb 属性值授予权限
- angular - 刷新 NGRX 没有成功操作
- javascript - `.then` 和 `.catch` Promise 处理程序是否以某种方式配对并像 `.then(resolutionHandler, rejectHandler)` 处理?