You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Keycloak-js在Next.js中实现Keycloak用户注册

实现Next.js自定义页面对接Keycloak自助注册

第一步:Keycloak后台配置(必须完成)

要允许匿名请求创建用户,需先在Keycloak控制台完成以下配置:

  • 开启用户自助注册:进入目标Realm(AppName)→ 登录设置 → 勾选「允许用户注册」
  • 配置匿名访问用户API:
    1. 进入客户端「appname-web」→ 权限标签页 → 开启「授权」开关
    2. 点击「添加权限」→ 选择「用户」资源类型,为create权限设置策略为「匿名」
  • 开启邮件验证:进入Realm设置 → 登录 → 勾选「要求电子邮件验证」,确保注册后自动发送验证邮件
  • 配置跨域:进入客户端「appname-web」→ 设置 → Web Origins,添加你的Next.js应用地址(如http://localhost:3000),开发环境可临时设为*,生产环境务必指定具体域名

第二步:前端实现方案

方案1:直接调用Keycloak REST API(推荐)

在Next.js客户端组件中编写注册逻辑,直接请求Keycloak的用户创建接口:

'use client';

import { useState } from 'react';

export default function RegisterPage() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: '',
    firstName: '',
    lastName: ''
  });

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const res = await fetch('http://localhost:8081/realms/AppName/users', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Origin': 'http://localhost:3000'
        },
        body: JSON.stringify({
          username: formData.username,
          email: formData.email,
          enabled: true,
          credentials: [{
            type: 'password',
            value: formData.password,
            temporary: false // 设为true则用户首次登录需修改密码
          }],
          firstName: formData.firstName,
          lastName: formData.lastName,
          emailVerified: false // 交由Keycloak发送验证邮件
        })
      });

      if (res.ok) {
        alert('注册成功,请查收邮箱完成验证');
        // 跳转至登录页
        window.location.href = '/login';
      } else {
        const error = await res.json();
        alert(`注册失败:${error.errorMessage || '未知错误'}`);
      }
    } catch (err) {
      console.error('注册请求失败:', err);
      alert('网络异常,请稍后重试');
    }
  };

  return (
    <div className="register-form">
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="用户名"
          value={formData.username}
          onChange={(e) => setFormData({ ...formData, username: e.target.value })}
          required
        />
        <input
          type="email"
          placeholder="邮箱"
          value={formData.email}
          onChange={(e) => setFormData({ ...formData, email: e.target.value })}
          required
        />
        <input
          type="password"
          placeholder="密码"
          value={formData.password}
          onChange={(e) => setFormData({ ...formData, password: e.target.value })}
          required
        />
        <input
          type="text"
          placeholder="名字"
          value={formData.firstName}
          onChange={(e) => setFormData({ ...formData, firstName: e.target.value })}
          required
        />
        <input
          type="text"
          placeholder="姓氏"
          value={formData.lastName}
          onChange={(e) => setFormData({ ...formData, lastName: e.target.value })}
          required
        />
        <button type="submit">完成注册</button>
      </form>
    </div>
  );
}

方案2:复用Keycloak-js配置

如果已初始化Keycloak实例,可复用其配置拼接API地址,避免硬编码:

'use client';

import { useState, useEffect } from 'react';
import Keycloak from 'keycloak-js';

export default function RegisterPage() {
  const [keycloak, setKeycloak] = useState(null);
  const [formData, setFormData] = useState({/* 同方案1的formData结构 */});

  useEffect(() => {
    const kcInstance = new Keycloak({
      url: "http://localhost:8081",
      realm: "AppName",
      clientId: "appname-web"
    });
    setKeycloak(kcInstance);
  }, []);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!keycloak) return;

    try {
      const apiUrl = `${keycloak.authServerUrl}/realms/${keycloak.realm}/users`;
      const res = await fetch(apiUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Origin': 'http://localhost:3000'
        },
        body: JSON.stringify({/* 同方案1的请求体 */})
      });

      // 后续处理同方案1
    } catch (err) {
      console.error(err);
    }
  };

  // 表单部分同方案1
}

进阶优化:Next.js API路由代理

生产环境建议通过Next.js的API路由代理请求,避免前端直接暴露Keycloak地址:

// app/api/register/route.js
export async function POST(request) {
  const formData = await request.json();
  const keycloakUrl = 'http://localhost:8081/realms/AppName/users';

  try {
    const res = await fetch(keycloakUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        username: formData.username,
        email: formData.email,
        enabled: true,
        credentials: [{
          type: 'password',
          value: formData.password,
          temporary: false
        }],
        firstName: formData.firstName,
        lastName: formData.lastName,
        emailVerified: false
      })
    });

    const data = await res.json();
    return new Response(JSON.stringify(data), { status: res.status });
  } catch (err) {
    return new Response(JSON.stringify({ error: '代理请求失败' }), { status: 500 });
  }
}

之后前端只需请求自己的API路由:

// 前端请求替换为:
const res = await fetch('/api/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(formData)
});

注意事项

  • 确保Keycloak的密码策略符合需求(Realm设置 → 安全 → 密码策略)
  • 自定义验证邮件内容可在Keycloak控制台的Realm设置 → 邮件 → 模板中修改
  • 生产环境务必关闭Web Origins的*配置,指定具体允许的域名
  • 如果需要额外字段,可在Keycloak的用户属性中添加,并在请求体中传入对应字段

内容的提问来源于stack exchange,提问作者Sérgio Serra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 14:52:37