Apollo网关的身份验证服务

时间:2019-12-26 10:03:17

标签: node.js authentication graphql microservices apollo-gateway

我正在使用Apollo Gateway开发基于微服务的应用程序。每个服务都是用Node.js编写的,并使用Graphql来构建联合模式。我还使用Mongoose与服务之间共享的MongoDB数据库进行交互。开发该应用程序的主要目标是学习并使用Graphql,微服务和Node.js等新手所熟悉的工具和技术。

我对认证有疑问。我决定将基于JWT的身份验证与每个用户的其他数据库存储会话一起使用。这样,我可以监视每个用户的活动会话,并通过禁用与​​令牌关联的会话来撤消访问。所有这些都由Auth服务管理,该服务负责身份验证,创建新用户以及登录/注销功能。 Auth服务公开了一个REST端点来验证jwt令牌,如下所示。

...
app.post('verify', async (req, res, next) => {
  const token = req.body.jwt;

  if(!token) {
    res.status(403).send({ error: 'No token provided.' });
  }

  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  const user = await User.findOne({ _id: decoded.sub});

  if (!user) {
    res.status(401).send({ error: 'No user found.' });
  }

  const session = await Session.findOne({
    '_id': {
      $in: user.sessions
    }
  });
  if(!session) {
    res.status(401).send({ error: 'Session not found or expired.' });
  }
  if(!session.valid) {
    res.status(401).send({ error: 'Session not valid.' });
  }

   res.send({
    userId: user.id,
    scopes: user.scopes
  });
}

const server = new ApolloServer({
  schema: buildFederatedSchema([
    {
      typeDefs,
      resolvers
    }
  ]),
  context: ({ req }) => {
    return {
      // headers
      userId: req.get['user-id'] || 0,
      scopes: req.get['user-scopes'] ? req.get['user-scopes'].split(',') : [],
      // Mongoose models
      models: {
        User,
        Session
      }
    }
  }
});

server.applyMiddleware({ app, cors: false });
...

我的API网关基于Apollo网关来构建联合模式。验证由Auth服务验证,并通过网关设置的请求标头与其他所有服务共享。

...
// Set authenticated user id in request for other services
class AuthenticatedDataSource extends RemoteGraphQLDataSource {
  willSendRequest({ request, context }) {
    // pass the user's id from the context to underlying services
    // as a header called `user-id`
    request.http.headers.set('user-id', context.userId);
    request.http.headers.set('user-scopes', context.scopes.join(','));
  }
}

const gateway = new ApolloGateway({
  serviceList: [
    { name: 'auth', url: 'https://auth:4000' }
  ],
  buildService: ({ name, url }) => {
    return AuthenticatedDataSource({ url });
  }
});

// Apollo server middleware - last applied
const server = new ApolloServer({
  gateway,
  // not supported
  subscriptions: false,
  context: async ({ req }) => {
    try {
      // Send auth query to Auth service REST api
      const response = await axios.post('https://auth:4000/verify', {
        jwt: req.cookies['plottwist_login']
      });
      // save auth data in context
      return {
        userId: response.data.userId,
        scopes: response.data.scopes
      }
    } catch(e) {
       // deal with error
    }
  }
});

server.applyMiddleware({ app, path, cors: false });
...

这种方式的流程如下:

  • API网关从客户端接收Graphql查询请求。
  • API网关使用auth服务提供的唯一REST端点查询Auth服务以对用户进行身份验证(从接收到的请求中复制令牌cookie)。
  • 身份验证服务对用户进行身份验证并发送回数据。
  • 网关接收响应,创建其他请求标头,并通过管理原始Graphql查询进行操作。

这伴随着网关在管理来自客户端的每个Graphql查询之前进行额外调用的费用。我不知道这是一个可行的选择,还是我的推理存在一些重大缺陷。

0 个答案:

没有答案
相关问题