如何通过OIDC授权码流实现Quarkus、Keycloak与Vue SPA的安全身份认证
解决方案
你遇到的核心问题是跨域场景下用AJAX请求触发OIDC登录的302重定向,浏览器自动跟随重定向到Keycloak地址时触发CORS错误,且fetch规范不允许读取重定向地址手动跳转。以下是符合你安全要求的落地方案:
方案1:后端托管OIDC流+HttpOnly Cookie(推荐,匹配你的初始需求)
该方案完全符合你「前端不接触令牌、令牌存在带安全属性的Cookie、API无状态」的要求,无需修改技术栈。
1. Quarkus侧配置调整
在原有application.properties基础上补充以下配置:
# 允许Vue前端跨域访问,开启凭证支持 quarkus.http.cors.origins=http://localhost:3000 quarkus.http.cors.credentials=true quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS quarkus.http.cors.headers=* # OIDC Cookie安全属性配置 quarkus.oidc.authentication.cookie.secure=true quarkus.oidc.authentication.cookie.http-only=true quarkus.oidc.authentication.cookie.same-site=strict # 登录成功后跳转回SPA的地址,替换为你Vue实际运行的地址 quarkus.oidc.authentication.redirect-path=http://localhost:3000
新增公开的认证接口,专门处理登录、登出、状态校验逻辑:
@Path("/api/auth") public class AuthController { @Inject OidcAuthenticationContext authContext; // 登录触发端点:公开访问,跳转至Keycloak认证页 @GET @Path("/login") public Uni<Response> login() { return authContext.redirectToAuthorizationEndpoint(); } // 登出端点:清理Cookie,跳转至Keycloak登出页 @GET @Path("/logout") @Authenticated public Uni<Response> logout() { return authContext.logout() .map(redirectUri -> Response.seeOther(URI.create(redirectUri)).build()); } // 登录状态校验端点:公开访问,给前端判断登录状态用 @GET @Path("/status") public Response checkStatus(@Context SecurityContext securityContext) { boolean isLoggedIn = securityContext.getUserPrincipal() != null; return Response.ok(Map.of("loggedIn", isLoggedIn)).build(); } }
原有受保护接口和OIDC配置不需要修改,quarkus.oidc.application-type保持web-app即可。
2. 前端逻辑调整
不要通过AJAX请求被动触发登录跳转,改为主动整页跳转登录端点,避免跨域问题。修改后的组件逻辑参考:
<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) => { // 未登录直接跳转登录端点,不用处理302 if (resp.status === 401) { window.location.assign("http://localhost:8081/api/auth/login"); return; } return resp.text().then((text) => (this.message = text)); }) .catch((reason) => (this.message = "Caught error: " + reason)); }, }, }); </script>
你也可以在SPA初始化时先调用/api/auth/status接口判断用户登录状态,未登录直接引导跳转登录。
方案2:SPA侧走带PKCE的授权码流(可选,体验更流畅)
如果你不想做整页跳转,可以选择当前SPA认证的主流方案:
- Quarkus侧将
quarkus.oidc.application-type改为service,只做令牌校验 - 前端使用
oidc-client-ts这类库,走带PKCE的授权码流,拿到的access token只存在内存中,不存入本地存储 - 调用接口时将令牌放在
Authorization: Bearer <token>头中,设置较短的令牌有效期(5分钟以内),过期后通过静默iframe刷新令牌
该方案的缺点是令牌存在前端内存,有XSS泄露风险,优点是无需整页跳转,用户体验更好。
内容的提问来源于stack exchange,提问作者vizmo
相关产品推荐
相关产品推荐

