我正在使用 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 查询之前进行的额外调用的成本。我想知道这是否是一个可行的选择,或者我的推理存在一些重大缺陷。