Мне нужно добавить слой авторизации на сервере Apollo Stitcher, но я не могу найти никаких решений.
Архитектура : - Существует центральный сервер шлюза Apollo Graphql (сервер брошюровщика), которыйсвязывается с другими микросервисами и объединяет их схемы.Я хочу переместить слой авторизации и аутентификации с каждого микросервиса (написанного на разных языках) на сервер шлюза, чтобы любой неавторизованный запрос не смог пройти через сервер шлюза, и нам не нужно писать слой авторизации на каждом языке.
Итак, чтобы авторизация работала, мы решили использовать директивы, подобные @auth(role: ADMIN)
, но проблема в том, что она не работает для сшитой схемы.
Микросервис A (java)
schema.graqhql
directive @auth(requires: String) on FIELD | FIELD_DEFINITION | OBJECT | QUERY
type Campaign {
name: String
status: Status
}
// I have added a dummy auth directive resolver in java microservice such that it doesn't fail schema validation at server startup.
type Query {
campaigns : [Camapign] @auth(role: ADMIN)
}
Сервер Apollo Gateway
AuthDirective.js
// @flow
import {SchemaDirectiveVisitor} from "apollo-server";
class AuthDirective extends SchemaDirectiveVisitor {
visitObject(object) {
console.log("in visitObject");
this.ensureFieldsWrapped(object);
}
visitFieldDefinition(field) {
const {resolve} = field;
console.log("in visitFieldDefinition");
field.resolve = async function (...args) {
console.log("in auth resolver");
const context = args[2];
const requiredRole = "ADMIN";
const user = await getUser(context.headers.authToken);
if (!user.hasRole(requiredRole)) {
throw new Error("not authorized");
}
return resolve.apply(this, args);
};
}
ensureFieldsWrapped(objectType) {
const fields = objectType.getFields();
Object.keys(fields).forEach(fieldName => {
const field = fields[fieldName];
const {resolve} = field;
field.resolve = async function (...args) {
console.log("in auth resolver");
const context = args[2];
const requiredRole = "ADMIN";
const user = await getUser(context.headers.authToken);
if (!user.hasRole(requiredRole)) {
throw new Error("not authorized");
}
return resolve.apply(this, args);
};
});
}
}
export default AuthDirective;
// language=GraphQL
// will this directive override the directive in Microservice A ?
const RootQuery= `
directive @auth(
requires: String
) on FIELD | FIELD_DEFINITION | OBJECT | QUERY
`;
async function getMergedSchema() {
const remoteSchema = await makeMergedRemoteSchema(); // introspects microservice schemas using makeRemoteExecutableSchema
const schema = makeExecutableSchema({
typeDefs: RootQuery,
schemaDirectives: {
auth: AuthDirective
}
});
return [
...remoteSchema,
schema
];
}
//app.js
const schemas = await getMergedSchema();
const mergedSchema = mergeSchemas({
schemas,
resolvers: resolvers,
mergeDirectives: true
}});
const server = new ApolloServer({
schema: mergedSchema,
debug: true,
introspection: true,
tracing: true,
engine: {
apiKey: config.get("engine.apiKey"),
schemaTag: process.env.NODE_CONFIG_ENV,
generateClientInfo: ({request}) => {
const headers = request.http && request.http.headers;
if (headers) {
return {
clientName: headers.get("X-clientId"),
clientVersion: headers.get("X-AppVersion"),
};
} else {
return {
clientName: "Unknown",
clientVersion: "Unknown"
};
}
}
},
cacheControl: true,
playground: {
settings: {
"editor.theme": "dark",
},
},
persistedQueries: {
cache: new MemcachedCache(
['memcached-server'],
{retries: 10, retry: 10000, ttl: 10000},
),
},
subscriptions: {
path: "/sub"
},
context: ({req}) => ({
request: req
})
});
server.applyMiddleware({
app,
gui: true,
bodyParserConfig: {limit: "50mb"},
cors: {
origin: "*"
},
onHealthCheck: () => new Promise((resolve) => {
//database check or other asynchronous action
resolve();
})
});
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen({
port: ((port: any): number)
}, () => {
Logger.info(`? Server ready at http://localhost:${port}${server.graphqlPath}`);
Logger.info(`Try your health check at: http://localhost:${port}/.well-known/apollo/server-health`);
}
)
Запросна сервер Apollo
{
campaigns {
name
}
}
Я хочу, чтобы при сшивании этого запроса вызывался @auth directive
, чтобы я мог контролировать его и проверить, разрешен ли входящий запрос.если нет, то выдает UNAUTHORIZED ошибку и не перенаправляет запрос в микросервис.
Но он вызывает директиву auth в микросервисе A: (
Есть ли способ, которым я могу добиться этого, или если выЕсли у вас есть более подходящее решение или архитектура, пожалуйста, дайте мне знать. Я застрял, нужна помощь.
Я использую следующие пакеты
{
...,
"dependencies": {
"@types/graphql": "14.2.0",
"apollo-engine": "1.1.2",
"apollo-fetch": "0.7.0",
"apollo-link-context": "1.0.17",
"apollo-link-http": "1.5.14",
"apollo-server": "2.6.0-alpha.0",
"apollo-server-cache-memcached": "0.4.0",
"apollo-server-express": "2.6.0-alpha.0",
"apollo-server-memcached": "0.1.0-rc.10",
"express": "4.16.4",
"graphql": "14.3.1",
"graphql-resolve-batch": "1.0.2",
"graphql-subscriptions": "1.1.0",
"graphql-tag": "2.10.1",
"graphql-tools": "5.0.0-rc.1",
"node-fetch": "2.5.0",
"request": "2.88.0",
"subscriptions-transport-ws": "0.9.16",
"ws": "5.2.1",
...
}
}