从KeyCloak上下文中缺少访问令牌

发布于 2025-01-21 22:29:00 字数 2690 浏览 5 评论 0 原文

我正在尝试从Postman到我的节点Apollo的Apello,Express Backend的认证请求。我正在遇到一个错误,说用户未经验证。当我查看上下文对象时,没有访问令牌和调用context.kauth.isauthenticated()返回false。

查看访问令牌,我可以看到 AccessToken 确实是空白的,但是确实存在请求标题中的携带者令牌。

所以我不确定为什么不包括访问令牌。

我是从Postman提出的请求,我在请求中包括令牌:

”在此处输入映像说明“

为了获得此访问令牌,我首先提出邮递员请求keycloak,以生成此标记,因此(请注意,我故意没有显示此帖子的用户名和密码

“在此处输入映像”

我在上面的邮递员请求中使用上述访问令牌。

这是我的 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(`
              

I am trying to make an authenticated request from postman to my node, apollo, express backend. I am gettting an error saying that the user is unauthenticated. When I look at the context object, there is no access token and calling context.kauth.isAuthenticated() returns false.

Looking at the access token, I can see that accessToken is indeed blank, but there does exist the Bearer Token in the request header.

enter image description here
enter image description here

So I am not sure why the access token is not being included.

I am making the request from postman, I am including the token in the request like so:

enter image description here

In order to get this access token, I am first making a postman request to Keycloak to generate this token like so (note that I am intentionally not showing my username and password for this post

enter image description here

I am using the above access token in my postman request above.

This is what my index.js file looks like:

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);

And this is my keycloak.json file:

enter image description here

I am really quite stummped, my initial thought is that I am not making the reqest from postman correctly. Am grateful for any guidance

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

魔法唧唧 2025-01-28 22:29:00

要求:

  • 使用Node,Apollo,Express基于 keyCloak-connect
  • 使用Postman的中间件获得KeyCloak身份验证和授权,以使用携带者令牌进行身份验证的调用。

index.js 在问题中不是 typedefs , ./ auth/auth/transformers 等零件缺少。

有一个很酷的描述,请 https://github.com/aerogear/aerogear/keycloak-colak-connect-gonnect-graphql-graphql-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(`

Requirements:

  • use node, apollo, express to get keycloak Authentication and Authorization based on the keycloak-connect middleware
  • using Postman to make an authenticated call with a Bearer token.

index.js in the question is not a minimal, reproducible example because, for example, the parts in typeDefs, ./auth/transformers and so on are missing.

There is a cool description at https://github.com/aerogear/keycloak-connect-graphql with nice example code.

So if one changes your approach only slightly (e.g. mongodb is not needed) and then adds the also slightly changed code from the description of the Github page accordingly, one can get a standalone running index.js.

For example, it might look something like this:

"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);

The corresponding 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"
  }
}

Call With Postman

demo

As one can see, the authenticated call is then successful. Also, with the above code, the accessToken is logged correctly to the debug console:

debug output

This is certainly not the functionality that exactly meets your requirements. But you may be able to gradually make the desired/necessary changes from this running example depending on your requirements.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文