Добавить директиву авторизации в сервис Apollo Stitcher для удаленных схем - PullRequest
0 голосов
/ 09 июня 2019

Мне нужно добавить слой авторизации на сервере 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",
        ...
     }
  }
...