Keycloak 上下文中缺少访问令牌
Access token missing from Keycloak context
我正在尝试从 postman 向我的节点 apollo express 后端发出经过身份验证的请求。我收到一条错误消息,指出用户未经身份验证。当我查看上下文对象时,没有访问令牌并调用 context.kauth.isAuthenticated() returns false.
查看access token,accessToken
确实是空白,但是请求头中确实存在Bearer Token。
所以我不确定为什么没有包含访问令牌。
我正在向 post 人发出请求,我在请求中包含令牌,如下所示:
为了获得这个访问令牌,我首先向 Keycloak 发出一个 postman 请求来生成这个令牌(请注意,我故意不显示我的用户名和密码 post
我在上面的 postman 请求中使用了上面的访问令牌。
这是我的 index.js
文件的样子:
require("dotenv").config();
import { ApolloServer } from "apollo-server-express";
import { ApolloServerPluginDrainHttpServer } from "apollo-server-core";
const { makeExecutableSchema } = require('@graphql-tools/schema');
import { configureKeycloak } from "./auth/config"
import {
KeycloakContext,
KeycloakTypeDefs,
KeycloakSchemaDirectives,
} from "keycloak-connect-graphql";
import { applyDirectiveTransformers } from "./auth/transformers";
import express from "express";
import http from "http";
import typeDefs from "./graphql/typeDefs";
import resolvers from "./graphql/resolvers";
import { MongoClient } from "mongodb";
import MongoHelpers from "./dataSources/MongoHelpers";
async function startApolloServer(typeDefs, resolvers) {
const client = new MongoClient(process.env.MONGO_URI);
client.connect();
let schema = makeExecutableSchema({
typeDefs: [KeycloakTypeDefs, typeDefs],
resolvers
});
schema = applyDirectiveTransformers(schema);
const app = express();
const httpServer = http.createServer(app);
const { keycloak } = configureKeycloak(app, '/graphql')
const server = new ApolloServer({
schema,
schemaDirectives: KeycloakSchemaDirectives,
resolvers,
context: ({ req }) => {
return {
kauth: new KeycloakContext({ req }, keycloak)
}
},
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
server.applyMiddleware({ app });
await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(` Server ready at http://localhost:4000${server.graphqlPath}`);
}
startApolloServer(typeDefs, resolvers);
这是我的 keycloak.json 文件:
我真的很困惑,我最初的想法是我没有正确地向 postman 提出请求。感谢您的指导
要求:
- 使用node、apollo、express基于
keycloak-connect
中间件 获取keycloak认证授权
- 使用 Postman 通过 Bearer 令牌进行经过身份验证的调用。
index.js
不是minimal, reproducible example,因为typeDefs
、./auth/transformers
等部分缺失
https://github.com/aerogear/keycloak-connect-graphql 上有一个很酷的描述和很好的示例代码。
因此,如果仅稍微更改您的方法(例如,不需要 mongodb),然后相应地从 Github 页面的描述中添加同样略有更改的代码,则可以获得独立的运行 index.js
.
例如,它可能看起来像这样:
"use strict";
const {ApolloServer, gql} = require("apollo-server-express")
const {ApolloServerPluginDrainHttpServer} = require("apollo-server-core")
const {makeExecutableSchema} = require('@graphql-tools/schema');
const {getDirective, MapperKind, mapSchema} = require('@graphql-tools/utils')
const {KeycloakContext, KeycloakTypeDefs, auth, hasRole, hasPermission} = require("keycloak-connect-graphql")
const {defaultFieldResolver} = require("graphql");
const express = require("express")
const http = require("http")
const fs = require('fs');
const path = require('path');
const session = require('express-session');
const Keycloak = require('keycloak-connect');
function configureKeycloak(app, graphqlPath) {
const keycloakConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'config/keycloak.json')));
const memoryStore = new session.MemoryStore();
app.use(session({
secret: process.env.SESSION_SECRET_STRING || 'this should be a long secret',
resave: false,
saveUninitialized: true,
store: memoryStore
}));
const keycloak = new Keycloak({
store: memoryStore
}, keycloakConfig);
// Install general keycloak middleware
app.use(keycloak.middleware({
admin: graphqlPath
}));
// Protect the main route for all graphql services
// Disable unauthenticated access
app.use(graphqlPath, keycloak.middleware());
return {keycloak};
}
const authDirectiveTransformer = (schema, directiveName = 'auth') => {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const authDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
if (authDirective) {
const {resolve = defaultFieldResolver} = fieldConfig;
fieldConfig.resolve = auth(resolve);
}
return fieldConfig;
}
})
}
const directive = (keys, key, directive, directiveName) => {
if (keys.length === 1 && keys[0] === key) {
let dirs = directive[keys[0]];
if (typeof dirs === 'string') dirs = [dirs];
if (Array.isArray(dirs)) {
return dirs.map((val) => String(val));
} else {
throw new Error(`invalid ${directiveName} args. ${key} must be a String or an Array of Strings`);
}
} else {
throw Error(`invalid ${directiveName} args. must contain only a ${key} argument`);
}
}
const permissionDirectiveTransformer = (schema, directiveName = 'hasPermission') => {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const permissionDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
if (permissionDirective) {
const {resolve = defaultFieldResolver} = fieldConfig;
const keys = Object.keys(permissionDirective);
let resources = directive(keys, 'resources', permissionDirective, directiveName);
fieldConfig.resolve = hasPermission(resources)(resolve);
}
return fieldConfig;
}
})
}
const roleDirectiveTransformer = (schema, directiveName = 'hasRole') => {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const roleDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
if (roleDirective) {
const {resolve = defaultFieldResolver} = fieldConfig;
const keys = Object.keys(roleDirective);
let role = directive(keys, 'role', roleDirective, directiveName);
fieldConfig.resolve = hasRole(role)(resolve);
}
return fieldConfig;
}
})
}
const applyDirectiveTransformers = (schema) => {
return authDirectiveTransformer(roleDirectiveTransformer(permissionDirectiveTransformer(schema)));
}
const typeDefs = gql`
type Query {
hello: String @hasRole(role: "developer")
}
`
const resolvers = {
Query: {
hello: (obj, args, context, info) => {
console.log(context.kauth)
console.log(context.kauth.isAuthenticated())
console.log(context.kauth.accessToken.content.preferred_username)
const name = context.kauth.accessToken.content.preferred_username || 'world'
return `Hello ${name}`
}
}
}
async function startApolloServer(typeDefs, resolvers) {
let schema = makeExecutableSchema({
typeDefs: [KeycloakTypeDefs, typeDefs],
resolvers
});
schema = applyDirectiveTransformers(schema);
const app = express();
const httpServer = http.createServer(app);
const {keycloak} = configureKeycloak(app, '/graphql')
const server = new ApolloServer({
schema,
resolvers,
context: ({req}) => {
return {
kauth: new KeycloakContext({req}, keycloak)
}
},
plugins: [ApolloServerPluginDrainHttpServer({httpServer})],
});
await server.start();
server.applyMiddleware({app});
await new Promise((resolve) => httpServer.listen({port: 4000}));
console.log(` Server ready at http://localhost:4000${server.graphqlPath}`);
}
startApolloServer(typeDefs, resolvers);
对应package.json
:
{
"dependencies": {
"@graphql-tools/schema": "^8.3.10",
"@graphql-tools/utils": "^8.6.9",
"apollo-server-core": "^3.6.7",
"apollo-server-express": "^3.6.7",
"express": "^4.17.3",
"express-session": "^1.17.2",
"graphql": "^15.8.0",
"graphql-tools": "^8.2.8",
"http": "^0.0.1-security",
"keycloak-connect": "^18.0.0",
"keycloak-connect-graphql": "^0.7.0"
}
}
与邮递员通话
可以看到,经过身份验证的调用成功了。此外,使用上面的代码,accessToken
被正确地记录到调试控制台:
这肯定不是完全符合您要求的功能。但是您可以根据您的要求逐渐对这个 运行 示例进行 desired/necessary 更改。
我正在尝试从 postman 向我的节点 apollo express 后端发出经过身份验证的请求。我收到一条错误消息,指出用户未经身份验证。当我查看上下文对象时,没有访问令牌并调用 context.kauth.isAuthenticated() returns false.
查看access token,accessToken
确实是空白,但是请求头中确实存在Bearer Token。
所以我不确定为什么没有包含访问令牌。
我正在向 post 人发出请求,我在请求中包含令牌,如下所示:
为了获得这个访问令牌,我首先向 Keycloak 发出一个 postman 请求来生成这个令牌(请注意,我故意不显示我的用户名和密码 post
我在上面的 postman 请求中使用了上面的访问令牌。
这是我的 index.js
文件的样子:
require("dotenv").config();
import { ApolloServer } from "apollo-server-express";
import { ApolloServerPluginDrainHttpServer } from "apollo-server-core";
const { makeExecutableSchema } = require('@graphql-tools/schema');
import { configureKeycloak } from "./auth/config"
import {
KeycloakContext,
KeycloakTypeDefs,
KeycloakSchemaDirectives,
} from "keycloak-connect-graphql";
import { applyDirectiveTransformers } from "./auth/transformers";
import express from "express";
import http from "http";
import typeDefs from "./graphql/typeDefs";
import resolvers from "./graphql/resolvers";
import { MongoClient } from "mongodb";
import MongoHelpers from "./dataSources/MongoHelpers";
async function startApolloServer(typeDefs, resolvers) {
const client = new MongoClient(process.env.MONGO_URI);
client.connect();
let schema = makeExecutableSchema({
typeDefs: [KeycloakTypeDefs, typeDefs],
resolvers
});
schema = applyDirectiveTransformers(schema);
const app = express();
const httpServer = http.createServer(app);
const { keycloak } = configureKeycloak(app, '/graphql')
const server = new ApolloServer({
schema,
schemaDirectives: KeycloakSchemaDirectives,
resolvers,
context: ({ req }) => {
return {
kauth: new KeycloakContext({ req }, keycloak)
}
},
plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
});
await server.start();
server.applyMiddleware({ app });
await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(` Server ready at http://localhost:4000${server.graphqlPath}`);
}
startApolloServer(typeDefs, resolvers);
这是我的 keycloak.json 文件:
我真的很困惑,我最初的想法是我没有正确地向 postman 提出请求。感谢您的指导
要求:
- 使用node、apollo、express基于
keycloak-connect
中间件 获取keycloak认证授权
- 使用 Postman 通过 Bearer 令牌进行经过身份验证的调用。
index.js
不是minimal, reproducible example,因为typeDefs
、./auth/transformers
等部分缺失
https://github.com/aerogear/keycloak-connect-graphql 上有一个很酷的描述和很好的示例代码。
因此,如果仅稍微更改您的方法(例如,不需要 mongodb),然后相应地从 Github 页面的描述中添加同样略有更改的代码,则可以获得独立的运行 index.js
.
例如,它可能看起来像这样:
"use strict";
const {ApolloServer, gql} = require("apollo-server-express")
const {ApolloServerPluginDrainHttpServer} = require("apollo-server-core")
const {makeExecutableSchema} = require('@graphql-tools/schema');
const {getDirective, MapperKind, mapSchema} = require('@graphql-tools/utils')
const {KeycloakContext, KeycloakTypeDefs, auth, hasRole, hasPermission} = require("keycloak-connect-graphql")
const {defaultFieldResolver} = require("graphql");
const express = require("express")
const http = require("http")
const fs = require('fs');
const path = require('path');
const session = require('express-session');
const Keycloak = require('keycloak-connect');
function configureKeycloak(app, graphqlPath) {
const keycloakConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'config/keycloak.json')));
const memoryStore = new session.MemoryStore();
app.use(session({
secret: process.env.SESSION_SECRET_STRING || 'this should be a long secret',
resave: false,
saveUninitialized: true,
store: memoryStore
}));
const keycloak = new Keycloak({
store: memoryStore
}, keycloakConfig);
// Install general keycloak middleware
app.use(keycloak.middleware({
admin: graphqlPath
}));
// Protect the main route for all graphql services
// Disable unauthenticated access
app.use(graphqlPath, keycloak.middleware());
return {keycloak};
}
const authDirectiveTransformer = (schema, directiveName = 'auth') => {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const authDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
if (authDirective) {
const {resolve = defaultFieldResolver} = fieldConfig;
fieldConfig.resolve = auth(resolve);
}
return fieldConfig;
}
})
}
const directive = (keys, key, directive, directiveName) => {
if (keys.length === 1 && keys[0] === key) {
let dirs = directive[keys[0]];
if (typeof dirs === 'string') dirs = [dirs];
if (Array.isArray(dirs)) {
return dirs.map((val) => String(val));
} else {
throw new Error(`invalid ${directiveName} args. ${key} must be a String or an Array of Strings`);
}
} else {
throw Error(`invalid ${directiveName} args. must contain only a ${key} argument`);
}
}
const permissionDirectiveTransformer = (schema, directiveName = 'hasPermission') => {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const permissionDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
if (permissionDirective) {
const {resolve = defaultFieldResolver} = fieldConfig;
const keys = Object.keys(permissionDirective);
let resources = directive(keys, 'resources', permissionDirective, directiveName);
fieldConfig.resolve = hasPermission(resources)(resolve);
}
return fieldConfig;
}
})
}
const roleDirectiveTransformer = (schema, directiveName = 'hasRole') => {
return mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
const roleDirective = getDirective(schema, fieldConfig, directiveName)?.[0];
if (roleDirective) {
const {resolve = defaultFieldResolver} = fieldConfig;
const keys = Object.keys(roleDirective);
let role = directive(keys, 'role', roleDirective, directiveName);
fieldConfig.resolve = hasRole(role)(resolve);
}
return fieldConfig;
}
})
}
const applyDirectiveTransformers = (schema) => {
return authDirectiveTransformer(roleDirectiveTransformer(permissionDirectiveTransformer(schema)));
}
const typeDefs = gql`
type Query {
hello: String @hasRole(role: "developer")
}
`
const resolvers = {
Query: {
hello: (obj, args, context, info) => {
console.log(context.kauth)
console.log(context.kauth.isAuthenticated())
console.log(context.kauth.accessToken.content.preferred_username)
const name = context.kauth.accessToken.content.preferred_username || 'world'
return `Hello ${name}`
}
}
}
async function startApolloServer(typeDefs, resolvers) {
let schema = makeExecutableSchema({
typeDefs: [KeycloakTypeDefs, typeDefs],
resolvers
});
schema = applyDirectiveTransformers(schema);
const app = express();
const httpServer = http.createServer(app);
const {keycloak} = configureKeycloak(app, '/graphql')
const server = new ApolloServer({
schema,
resolvers,
context: ({req}) => {
return {
kauth: new KeycloakContext({req}, keycloak)
}
},
plugins: [ApolloServerPluginDrainHttpServer({httpServer})],
});
await server.start();
server.applyMiddleware({app});
await new Promise((resolve) => httpServer.listen({port: 4000}));
console.log(` Server ready at http://localhost:4000${server.graphqlPath}`);
}
startApolloServer(typeDefs, resolvers);
对应package.json
:
{
"dependencies": {
"@graphql-tools/schema": "^8.3.10",
"@graphql-tools/utils": "^8.6.9",
"apollo-server-core": "^3.6.7",
"apollo-server-express": "^3.6.7",
"express": "^4.17.3",
"express-session": "^1.17.2",
"graphql": "^15.8.0",
"graphql-tools": "^8.2.8",
"http": "^0.0.1-security",
"keycloak-connect": "^18.0.0",
"keycloak-connect-graphql": "^0.7.0"
}
}
与邮递员通话
可以看到,经过身份验证的调用成功了。此外,使用上面的代码,accessToken
被正确地记录到调试控制台:
这肯定不是完全符合您要求的功能。但是您可以根据您的要求逐渐对这个 运行 示例进行 desired/necessary 更改。