1

我目前正在从事一个个人项目,该项目涉及一个 Quarkus REST API 作为后端,Keycloak 作为 OpenId Connect Provider 和一个 Vue 应用程序作为前端。我只是不知道如何使这三个组件在用户身份验证中很好地协同工作,同时保持适当的安全性。

根据OAuth 2.0 for Browser-Based Apps的 v8 草案,SPA 不应该是保留访问和(可能)刷新令牌的地方,因为在这种情况下它们很难安全地存储。这意味着,后端必须充当依赖方,启动 OIDC 授权代码流。然后我会在我的 SPA 中保留一个会话 cookie(我不想这样做,以保持 API 无状态),或者将令牌存储在 Secure、SameSite、HttpOnly cookie 中。这种方法是我想要完成的,到目前为止收效甚微。

原型实现

我的 Quarkus 应用程序使用该quarkus-oicd扩展。我理解它的方式,我必须将以下配置添加到 Quarkus' application.properties

quarkus.oidc.application-type=web-app
quarkus.oidc.client-id=myClientId
quarkus.oidc.credentials.secret=********
quarkus.oidc.auth-server-url=http://127.0.0.1:8082/auth/realms/myRealm

application-type=web-app告诉 Quarkus 它负责启动授权代码流。另一种选择是service,在这种情况下,Quarkus 仅验证客户端发送到 API 的不记名令牌。

该 API 在端口 8081 上运行,仅公开一个示例资源:

@Path("/hello")
@Authenticated
public class ReactiveGreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello RESTEasy Reactive";
    }
}

这个简单的 Vue 组件是一个概念验证:

// FetchComponent.vue

<template>
  <div>
    <button v-on:click="fetchFromBackend">Fetch</button>
    <p><b>Output:</b>{{ message }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  data() {
    return {
      message: "Click the button to fetch.",
    };
  },
  methods: {
    fetchFromBackend(): void {
      this.message = "Waiting...";
      fetch("http://localhost:8081/hello", {
        credentials: "include",
      })
        .then((resp) => {
          console.log(resp);
          if (resp.redirected) {
            window.location.assign(resp.url);
          } else {
            return resp.text().then((text) => (this.message = text));
          }
        })
        .catch((reason) => (this.message = "Caught error: " + reason));
    },
  },
});
</script>

<style></style>

期望的结果

我原以为没有有效令牌的后端调用会被重定向到 Keycloak 的身份验证页面。然后,用户将输入他们的凭据,登录并使用身份验证代码重定向回 SPA。它再次调用 API。在反向通道上,Quarkus 将身份验证代码与令牌交换,并以 cookie 的形式将其转发给客户端。此 cookie 将用于对用户进行任何进一步的 API 调用的身份验证。

实际结果

当后端在没有有效令牌的情况下被调用时,它会按预期重定向到 Keycloak 的登录页面。但是,显然,根本没有办法从 JS 导航到重定向的 URL。提取规范指出“重定向(其状态或内部响应(如果有)状态为重定向状态的响应)不会暴露给 API。暴露重定向可能会泄漏通过跨站点脚本攻击无法获得的信息。” 获取请求中的redirect: 'manual'选项有点像红鲱鱼。它不符合人们的预期,当然也不允许我访问重定向 URL。

相反,浏览器透明地遵循重定向并尝试获取登录 URL。那根本行不通。它会导致 CORS 错误,因为 Keycloak 没有设置相关的标头(我想它不应该,因为这不是它应该如何工作的)。

我不知道如何从这里开始,但我认为答案对于更有经验的人来说是非常明显的。

作为结束语,我想补充一点,这种架构并不是一个非常明智的决策制定过程的结果。我选择它主要是因为:

  • Quarkus:Java 目前是我工作的主要语言
  • Keycloak:我想尝试一下适当的外部化 IAM 和 SSO 有一段时间了。这似乎是一个很好的机会。
  • Vue:我想要一些东西来训练我的 JS 技能,并且在我的简历上看起来不错。当前的任何一批热门 SPA 框架都符合要求。

因此,任何类似于“这是一个糟糕的设置,只是不要这样做,而是尝试 X”的答案肯定也是受欢迎的,尽管我仍然希望以自豪的方式解决这个难题。

4

0 回答 0