发布于 2026-01-06 3 阅读
0

How to Implement Nuxt.js/Vue.js OAuth2 Authentication With an External REST-API Server (based on Vert.x/Kotlin) and Keycloak 🐬 Introduction Authentication via OAuth2 👾 Nuxt.js Setup 🚀 SirixDB HTTP-Server: Vert.x based REST API 💚 Setting up Keycloak Conclusion An Embeddable, Bitemporal, Append-Only Database System and Event Store SirixDB Web frontend - An Evolutionary, Versioned, Temporal NoSQL Document Store

如何使用外部 REST API 服务器(基于 Vert.x/Kotlin)和 Keycloak 实现 Nuxt.js/Vue.js OAuth2 身份验证 🐬

介绍

通过 OAuth2 进行身份验证

👾 Nuxt.js 设置

🚀 SirixDB HTTP 服务器:基于 Vert.x 的 REST API

💚 设置 Keycloak

结论

一个可嵌入的、双时态的、仅追加的数据库系统和事件存储系统

SirixDB Web 前端 - 一个演进式、版本化、时间化的 NoSQL 文档存储库

介绍

身份验证很复杂。因此,最好将身份验证委托给专门的软件。我们选择使用 Keycloak。

我们希望SirixDB (一个时态文档存储系统)构建一个基于Nuxt.js 的前端,以便高效地存储和查询数据快照。HTTP 服务器提供非阻塞、异步的 REST API。我们决定使用 Kotlin(大量使用协程)和Vert.x来实现 API 服务器。

通过 OAuth2 进行身份验证

OAuth2 定义了几种所谓的流程。对于基于浏览器的应用程序,授权码流程是最佳且最安全的流程,我们将使用此流程。

💚 使用 Nuxt.js 实现 OAuth2 授权码流程

我们有一个工作流,其中只有 SirixDB HTTP 服务器直接与 Keycloak 交互(除了重定向到 Node.js 服务器)。因此,我们的前端只需要知道 SirixDB HTTP 服务器的两个路由:GET /user/authorizePOST /token

一般来说,我们的工作流程如下:

  1. 身份验证中间件控制用户是否应该/login首先被重定向到登录路由。
  2. /login路由包含一个简单的按钮,用于向 SirixDB HTTP 服务器发出请求。Nuxt.js 会生成一个唯一的、无法猜测的stateURL ,并将其作为 URL 参数redirect_uri传递给该路由。GET /user/authorize
  3. HTTP 服务器重定向到 Keycloak 的登录页面,并发送这两个参数。
  4. 用户正确填写凭据后,Keycloak 会将浏览器重定向到给定的 redirect_url,该 URL 首先由 Nuxt.js 发送(并发送到 SirixDB HTTP 服务器)。
  5. 在基于 Node.js 的服务器上,基于 Nuxt.js 的前端通过 Keycloak 的重定向 URL 来访问回调路由。
  6. Nuxt.js 随后提取 URL 参数code并检查该state参数的有效性。
  7. 接下来,Nuxt.js 会向SirixDB HTTP 服务器上的端点发送一个POSTHTTP 请求,并再次传递参数,该参数指向相同的回调路由。此外,它还会发送一个我们设置为代码的 JWT 访问令牌,以便 Nuxt.js 能够识别该访问令牌。/tokencoderedirect_uriresponse_type
  8. SirixDB HTTP 服务器随后将给定的代码与 Keycloak 生成的 JWT 访问令牌交换,并将其包含在 HTTP 响应中发送给基于 Nuxt.js 的前端。

请注意,如果我们处于通用模式(非单页应用模式),则可以简化此工作流程。正如我们稍后将看到的,Nuxt.js 中的 Node.js 服务器也可以直接与 Keycloak 通信。在这种设置下,SirixDB HTTP 服务器将仅根据已颁发的 JWT 令牌检查其路由的授权。这样一来,前端无需了解 Keycloak 的具体情况,也无需知道主机/端口和端点详细信息。此外,我们将看到 Nuxt.js 本身并不支持 Keycloak。

👾 Nuxt.js 设置

在 Nuxt.js 配置文件中,nuxt.config.js我们需要添加以下模块:

['@nuxtjs/axios',  { baseURL: 'https://localhost:9443' }], '@nuxtjs/auth', '@nuxtjs/proxy'
Enter fullscreen mode Exit fullscreen mode

然后我们再补充:

  axios: {
    baseURL: 'https://localhost:9443',
    browserBaseURL: 'https://localhost:9443',
    proxyHeaders: true,
    proxy: true,
  },
  auth: {
    strategies: {
      keycloak: {
        _scheme: 'oauth2',
        authorization_endpoint: 'https://localhost:9443/user/authorize',
        userinfo_endpoint: false,
        access_type: 'offline',
        access_token_endpoint: 'https://localhost:9443/token',
        response_type: 'code',
        token_type: 'Bearer',
        token_key: 'access_token',
      },
    },
    redirect: {
      login: '/login',
      callback: '/callback',
      home: '/'
    },
  },
  router: {
    middleware: ['auth']
  }
Enter fullscreen mode Exit fullscreen mode

https://localhost:9443这是 SirixDB HTTP 服务器正在监听的主机/端口。

默认情况下,我们的 Nuxt.js 配置会在所有路由上启用身份验证中间件。如果用户未通过身份验证,则会执行第一步,Nuxt.js 的身份验证模块会将用户重定向到相应的GET /login路由。

我们将定义一个简单的login页面:

<template>
  <div>
    <h3>Login</h3>
    <el-button type="primary" @click="login()">Login via Keycloak</el-button>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";

@Component
export default class Login extends Vue {
  private login(): void {
    this.$auth.loginWith('keycloak')
  }
}
</script>

<style lang="scss">
</style>
Enter fullscreen mode Exit fullscreen mode

为了定义要使用的正确 TypeScript 类型,this.$auth我们需要添加

"typings": "types/index.d.ts",
"files": ["types/*.d.ts"]
Enter fullscreen mode Exit fullscreen mode

添加到package.json文件中。此外,我们将创建types目录并添加index.d.ts文件。

在 Nuxt.js 应用程序的插件文件夹中,我们将添加一个文件来扩展 axios 客户端:

export default function ({ $axios, redirect }) {
  $axios.defaults.httpsAgent = new https.Agent({ rejectUnauthorized: false })

  $axios.onRequest(config => {
    config.headers.common['Origin'] = 'http://localhost:3005';
    config.headers.common['Content-Type'] = 'application/json';
    config.headers.common['Accept'] = 'application/json';

    config.headers.put['Origin'] = 'http://localhost:3005';
    config.headers.put['Content-Type'] = 'application/json';
    config.headers.put['Accept'] = 'application/json';

    config.headers.post['Origin'] = 'http://localhost:3005';
    config.headers.post['Content-Type'] = 'application/json';
    config.headers.post['Accept'] = 'application/json';

    config.headers.del['Origin'] = 'http://localhost:3005';
    config.headers.del['Content-Type'] = 'application/json';
    config.headers.del['Accept'] = 'application/json';
  });

  $axios.onError(error => {
    const code = parseInt(error.response && error.response.status);
    if (code === 401) {
      redirect('https://localhost:9443/user/authorize');
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

现在我们已经完成了 Nuxt.js 部分。接下来,我们将研究 SirixDB HTTP 服务器。

🚀 SirixDB HTTP 服务器:基于 Vert.x 的 REST API

我们需要设置 OAuth2 登录路由以及所有其他与 OAuth2 配置相关的内容。

但首先,我们将为 OAuth2 身份验证码流程添加 CORS 处理程序:

if (oauth2Config.flow == OAuth2FlowType.AUTH_CODE) {
    val allowedHeaders = HashSet<String>()
    allowedHeaders.add("x-requested-with")
    allowedHeaders.add("Access-Control-Allow-Origin")
    allowedHeaders.add("origin")
    allowedHeaders.add("Content-Type")
    allowedHeaders.add("accept")
    allowedHeaders.add("X-PINGARUNER")
    allowedHeaders.add("Authorization")

    val allowedMethods = HashSet<HttpMethod>()
    allowedMethods.add(HttpMethod.GET)
    allowedMethods.add(HttpMethod.POST)
    allowedMethods.add(HttpMethod.OPTIONS)

    allowedMethods.add(HttpMethod.DELETE)
    allowedMethods.add(HttpMethod.PATCH)
    allowedMethods.add(HttpMethod.PUT)

    this.route().handler(CorsHandler.create("*")
                .allowedHeaders(allowedHeaders)
                .allowedMethods(allowedMethods))
}

Enter fullscreen mode Exit fullscreen mode

OAuth2 配置通过以下方式读取:

val oauth2Config = oAuth2ClientOptionsOf()
    .setFlow(OAuth2FlowType.valueOf(config.getString("oAuthFlowType", "PASSWORD")))
    .setSite(config.getString("keycloak.url"))
    .setClientID("sirix")
    .setClientSecret(config.getString("client.secret"))
    .setTokenPath(config.getString("token.path", "/token"))
    .setAuthorizationPath(config.getString("auth.path", "/user/authorize"))

val keycloak = KeycloakAuth.discoverAwait(
    vertx, oauth2Config
)
Enter fullscreen mode Exit fullscreen mode

配置文件内容如下:

{
  "https.port": 9443,
  "keycloak.url": "http://localhost:8080/auth/realms/sirixdb",
  "auth.path": "http://localhost:8080/auth/realms/sirixdb/protocol/openid-connect/auth",
  "token.path": "/token",
  "client.secret": "2e54cfdf-909b-47ca-b385-4c44886f04f0",
  "oAuthFlowType" : "AUTH_CODE",
  "redirect.uri" : "http://localhost:3005/callback"
}
Enter fullscreen mode Exit fullscreen mode

请注意,通常情况下,Nuxt.js 会指定重定向 URI,在这种情况下,SirixDB HTTP 服务器会从 URL 查询参数中读取该 URI。

HTTP 服务器使用以下扩展函数来提供协程处理程序,而挂起函数则在 Vert.x 事件循环中运行:


/**
 * An extension method for simplifying coroutines usage with Vert.x Web routers.
 */
private fun Route.coroutineHandler(fn: suspend (RoutingContext) -> Unit): Route {
    return handler { ctx ->
        launch(ctx.vertx().dispatcher()) {
            try {
                fn(ctx)
            } catch (e: Exception) {
                ctx.fail(e)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

路径GET /user/authorize(步骤 2)。浏览器将重定向到 Keycloak 登录页面。

get("/user/authorize").coroutineHandler { rc ->
    if (oauth2Config.flow != OAuth2FlowType.AUTH_CODE) {
        rc.response().statusCode = HttpStatus.SC_BAD_REQUEST
    } else {
        val redirectUri =
            rc.queryParam("redirect_uri").getOrElse(0) { config.getString("redirect.uri") }
        val state = rc.queryParam("state").getOrElse(0) { java.util.UUID.randomUUID().toString() }

        val authorizationUri = keycloak.authorizeURL(
            JsonObject()
                .put("redirect_uri", redirectUri)
                .put("state", state)
        )
        rc.response().putHeader("Location", authorizationUri)
            .setStatusCode(HttpStatus.SC_MOVED_TEMPORARILY)
            .end()
    }
}

Enter fullscreen mode Exit fullscreen mode

提供凭据后,浏览器会被重定向到 redirect_uri(即 /callback 路由),并带有给定的状态(该状态最初由 Nuxt.js 生成)。然后,Nuxt.js 的身份验证模块会从 URL 查询参数中提取状态码statecode响应类型。如果状态与生成的状态相同,则会继续 POST 请求,并将重定向到 redirect_uri 和响应类型作为表单参数存储起来。

路线POST /token(步骤 7):

post("/token").handler(BodyHandler.create()).coroutineHandler { rc ->
    try {
        val dataToAuthenticate: JsonObject =
            when (rc.request().getHeader(HttpHeaders.CONTENT_TYPE)) {
                "application/json" -> rc.bodyAsJson
                "application/x-www-form-urlencoded" -> formToJson(rc)
                else -> rc.bodyAsJson
            }

        val user = keycloak.authenticateAwait(dataToAuthenticate)
        rc.response().end(user.principal().toString())
    } catch (e: DecodeException) {
        rc.fail(
            HttpStatusException(
                HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
                "\"application/json\" and \"application/x-www-form-urlencoded\" are supported Content-Types." +
                        "If none is specified it's tried to parse as JSON"
            )
        )
    }
}

private fun formToJson(rc: RoutingContext): JsonObject {
    val formAttributes = rc.request().formAttributes()
    val code =
        formAttributes.get("code")
    val redirectUri =
        formAttributes.get("redirect_uri")
    val responseType =
        formAttributes.get("response_type")

    return JsonObject()
        .put("code", code)
        .put("redirect_uri", redirectUri)
        .put("response_type", responseType)
}
Enter fullscreen mode Exit fullscreen mode

SirixDB HTTP 服务器从 Keycloak 获取 JWT 令牌并将其发送回前端。

之后,Nuxt.js 会将令牌存储在其会话、存储等位置。

最后,Axios 需要将每个 API 请求的令牌作为 Bearer 令牌发送到 Authorization-Header 中。我们可以通过以下方式检索令牌this.$auth.getToken('keycloak')

请注意,Nuxt.js/Node.js 可以直接与 Keycloak 交互,而无需使用 SirixDB HTTP 服务器进行间接交互,SirixDB HTTP 服务器只需验证 JWT 令牌即可。

在这种情况下,nuxt.config.jsKeycloak 认证对象如下所示:

keycloak: {
    _scheme: 'oauth2',
    authorization_endpoint: 'http://localhost:8080/auth/realms/sirixdb/protocol/openid-connect/auth',
    userinfo_endpoint: false,
    access_type: 'offline',
    access_token_endpoint: 'http://localhost:8080/auth/realms/sirixdb/protocol/openid-connect/token',
    response_type: 'code',
    token_type: 'Bearer',
    token_key: 'access_token',
    client_secret: '2e54cfdf-909b-47ca-b385-4c44886f04f0',
    client_id: 'sirix'
}
Enter fullscreen mode Exit fullscreen mode

在这种情况下,我们需要将以下内容添加http://localhost:3005到 Keycloak 中允许的 Web 源中,我们将在下一节中看到。

然而,我无法使其正常工作,因为Nuxt.js 的身份验证模块不知何故没有将 client_secret 发送到 Keycloaktoken端点:

错误:"unauthorized_client"
错误描述:"请求中未提供客户端密钥"

💚 设置 Keycloak

您可以按照这篇优秀的教程中的说明配置 Keycloak 。以下是对 SirixDB 的简要概述(您可以使用 SirixDB 的 docker-compose 文件跳过某些部分)。不过,它应该与其他项目的 Keycloak 配置几乎相同。

简而言之 :

  • 打开浏览器。网址:http://localhost:8080使用用户名admin和密码登录admin以访问 Keycloaks Web 配置界面
  • 创建一个名为的新领域sirixdb
  • 前往“客户”=>“帐户”
  • 将客户端 ID 更改为sirix
  • 请确保访问类型设置为“机密”。
  • 转到“凭据”选项卡
  • 将客户端密钥添加到 SirixDB HTTP 服务器配置文件(如上所示)中。将值更改client.secret为 Keycloak 设置的任何值。
  • 必须在设置选项卡中启用标准流程。
  • 将有效的重定向 URI 设置为http://localhost:3005/*或端口 3000 或 Nuxt.js 应用程序运行的任何位置
  • 请确保设置正确的值,以Web Origins允许来自这些域的 CORS。

Keycloak 配置图像

结论

让所有组件协同工作带来了一些麻烦。一个简化的办法是让 Nuxt.js 先完成所有身份验证,然后让外部 API 服务器检查令牌。

如果您觉得这篇文章对您有帮助,或者我把整个授权过程描述得太复杂了,请告诉我。

关于SirixDB前端,我非常希望得到一些意见甚至贡献,那将是莫大的荣幸 :-) 我是一名后端工程师,目前正在利用业余时间学习 Nuxt.js/Vue.js、TypeScript 和 D3,以用于这个项目。这是一个全新的项目,所以我们可以使用 Vue.js 的组合式 API 等。🐣

如果你喜欢这个项目,不妨在推特之类的社交媒体上分享一下,让更多人知道!🙈

在GitHub 上为 SirixDBGitHub SirixDB Web Frontend 项目做贡献💚

GitHub 标志 sirixdb / sirix

SirixDB 是一个可嵌入的、双时态的、仅追加的数据库系统和事件存储系统,用于存储不可变的轻量级快照。它保留了每个资源的完整历史记录。每次提交都通过结构共享存储一个空间高效的快照。它采用日志结构,并且永远不会覆盖数据。SirixDB 使用了一种新颖的页面级版本控制方法。

一个可嵌入的、双时态的、仅追加的数据库系统和事件存储系统

它以仅追加的方式存储小型、不可变的数据快照。这便于查询和重建完整的历史记录,也便于进行审计。

鸣叫

跟随

下载 ZIP 文件|加入我们的 Discord 服务器|社区论坛|文档|架构与概念

准备提交你的第一个 Pull Request 吗?你可以通过这个免费系列教程《如何在 GitHub 上为开源项目做贡献》以及另一篇教程《如何为开源软件做贡献:入门指南》来学习如何操作。

“记住,即使你不这么认为,你也很幸运,因为总有一些事情值得你感恩。”——埃丝特·格蕾丝·厄尔(http://tswgo.org

我们希望与您共同构建数据库系统。加入我们,成为维护者吧!为什么?您可能喜欢这款软件,并想为我们提供帮助。此外,您还能学到很多东西。您可能还想……




GitHub 标志 sirixdb / sirix-web-frontend

基于 Nuxt.js/Vue.js、D3.js 和 Typescript 的 SirixDB Web 前端

欢迎公关稿 所有贡献者

鸣叫

跟随

加入我们的 Slack 频道|社区论坛

准备提交你的第一个 Pull Request 吗?你可以通过这个免费系列教程《如何在 GitHub 上为开源项目做贡献》以及另一篇教程《如何为开源软件做贡献:入门指南》来学习如何操作。

SirixDB Web 前端 - 一个演进式、版本化、时间化的 NoSQL 文档存储库

高效地存储和查询数据版本

“记住,即使你不这么认为,你也很幸运,因为总有一些事情值得你感恩。”——埃丝特·格蕾丝·厄尔(http://tswgo.org

介绍

请在社区论坛中讨论。

这是一个基于Vue.jsD3.jsTypeScript 的Web 前端代码库。

它将提供多种交互方式,用于在 SirixDB 中存储、更新和查询数据库。此外,前端还将提供交互式可视化功能,以便基于不同视图探索和比较 SirixDB 中存储的资源版本。

一些想法……

此致敬礼,
约翰内斯

文章来源:https://dev.to/johanneslichtenberger/how-to-implement-nuxt-js-vue-js-oauth2-authentication-with-an-external-rest-api-server-based-on-vert-x-kotlin-and-keycloak-3c1h