GraphQL 网关`预检请求未通过访问控制检查`
Posted
技术标签:
【中文标题】GraphQL 网关`预检请求未通过访问控制检查`【英文标题】:GraphQL Gateway `preflight request doesn't pass access control check` 【发布时间】:2021-10-25 12:25:16 【问题描述】:我有一个GraphQL gateway 来联合几个子图。 网关跨这些子图执行传入操作。
在浏览器控制台中观察到错误
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 网关的代码
我已将我尝试 (TRY 1..7) 以解决我的 CORS 问题的所有内容都标记为 cmets
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
来解决此问题。
欢迎任何进一步的故障排除和发现问题的指针
链接
https://github.com/apollographql/apollo-client/issues/5764【问题讨论】:
如果对问题有帮助,请点赞并回答。谢谢 【参考方案1】:错误消息指向失败的预检请求,这是一个没有凭据的 OPTIONS 请求。此类预检请求应由app.use(cors(corsOptions))
中间件处理。但是这个中间件来得比较晚,所以如果任何早期的中间件(例如,app.use(passport.session())
)已经响应了这个 OPTIONS 请求,这将不起作用。
响应中带有Content-Length: 8
的OPTIONS 请求的屏幕截图似乎暗示这里发生了这种情况。
【讨论】:
这正是问题所在。我在会话中间件之前移动了cors
中间件,这很有效!!!明天将奖励赏金。它现在不允许我这样做。以上是关于GraphQL 网关`预检请求未通过访问控制检查`的主要内容,如果未能解决你的问题,请参考以下文章