组件
- 手风琴 Accordion
- 警告 Alert
- 警告对话框 Alert Dialog
- 宽高比 Aspect Ratio
- 头像 Avatar
- 徽章 Badge
- 面包屑 Breadcrumb
- 按钮 Button
- 按钮组 Button Group
- 日历 Calendar
- 卡片 Card
- 轮播图 Carousel
- 图表 Chart
- 复选框 Checkbox
- 折叠面板 Collapsible
- 组合框 Combobox
- 命令菜单 Command
- 上下文菜单 Context Menu
- 数据表格 Data Table
- 日期选择器 Date Picker
- 对话框 Dialog
- 抽屉 Drawer
- 下拉菜单 Dropdown Menu
- 空状态 Empty
- 字段 Field
- 表单 Form
- 悬停卡片 Hover Card
- 输入 Input
- 输入组 Input Group
- 一次性密码 OTP
- 条目 Item
- 快捷键 Kbd
- 标签 Label
- 菜单栏 Menubar
- 导航菜单 Navigation Menu
- 分页 Pagination
- 弹出层 Popover
- 进度 Progress
- 单选框组 Radio Group
- 可调整大小 Resizable
- 滚动区域 Scroll Area
- 选择框 Select
- 分隔符 Separator
- 侧边栏 Sheet
- 侧边栏 Sidebar
- 骨架屏 Skeleton
- 滑块 Slider
- 通知 Sonner
- 加载指示器 Spinner
- 开关 Switch
- 表格 Table
- 标签页 Tabs
- 文本区域 Textarea
- 消息 Toast
- 切换按钮 Toggle
- 切换组 Toggle Group
- 提示 Tooltip
- 排版 Typography
认证让您可以运行私有注册表,控制谁可以访问您的组件,并为不同的团队或用户提供不同的内容。本指南展示了常见的认证模式及其设置方法。
认证支持以下用例:
- 私有组件:保护您的业务逻辑和内部组件的安全
- 团队专属资源:为不同团队分配不同组件
- 访问控制:限制谁可以查看敏感或试验性组件
- 使用分析:查看组织中是谁在使用哪些组件
- 授权许可:控制谁能获得高级或授权组件
常见认证模式
基于 Token 的认证
最常用的方法是使用 Bearer token 或 API 密钥:
{
"registries": {
"@private": {
"url": "https://registry.company.com/{name}.json",
"headers": {
"Authorization": "Bearer ${REGISTRY_TOKEN}"
}
}
}
}
在环境变量中设置您的 token:
REGISTRY_TOKEN=your_secret_token_here
API 密钥认证
有些注册表使用请求头中的 API 密钥:
{
"registries": {
"@company": {
"url": "https://api.company.com/registry/{name}.json",
"headers": {
"X-API-Key": "${API_KEY}",
"X-Workspace-Id": "${WORKSPACE_ID}"
}
}
}
}
查询参数认证
对于简单的设置,也可以使用查询参数:
{
"registries": {
"@internal": {
"url": "https://registry.company.com/{name}.json",
"params": {
"token": "${ACCESS_TOKEN}"
}
}
}
}
这将形成如下 URL:https://registry.company.com/button.json?token=your_token
服务器端实现
下面介绍如何为您的注册表服务器添加认证:
Next.js API 路由示例
import { NextRequest, NextResponse } from "next/server"
export async function GET(
request: NextRequest,
{ params }: { params: { name: string } }
) {
// 从 Authorization 请求头中获取 token。
const authHeader = request.headers.get("authorization")
const token = authHeader?.replace("Bearer ", "")
// 或从查询参数中获取。
const queryToken = request.nextUrl.searchParams.get("token")
// 验证 token 是否有效。
if (!isValidToken(token || queryToken)) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
// 检查 token 是否有权访问该组件。
if (!hasAccessToComponent(token, params.name)) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
}
// 返回组件数据。
const component = await getComponent(params.name)
return NextResponse.json(component)
}
function isValidToken(token: string | null) {
// 添加您的 token 验证逻辑。
// 比如数据库查询、JWT 验证等。
return token === process.env.VALID_TOKEN
}
function hasAccessToComponent(token: string, componentName: string) {
// 添加基于角色的访问控制逻辑。
// 验证 token 是否能访问指定组件。
return true // 这里替换为您的逻辑。
}
Express.js 示例
app.get("/registry/:name.json", (req, res) => {
const token = req.headers.authorization?.replace("Bearer ", "")
if (!isValidToken(token)) {
return res.status(401).json({ error: "Unauthorized" })
}
const component = getComponent(req.params.name)
if (!component) {
return res.status(404).json({ error: "Component not found" })
}
res.json(component)
})
高级认证模式
基于团队的访问控制
为不同团队分配不同组件:
async function GET(request: NextRequest) {
const token = extractToken(request)
const team = await getTeamFromToken(token)
// 获取该团队可访问的组件。
const components = await getComponentsForTeam(team)
return NextResponse.json(components)
}
用户个性化注册表
根据用户偏好为其提供组件:
async function GET(request: NextRequest) {
const user = await authenticateUser(request)
// 获取用户的风格及框架偏好。
const preferences = await getUserPreferences(user.id)
// 获取个性化组件版本。
const component = await getPersonalizedComponent(params.name, preferences)
return NextResponse.json(component)
}
临时访问 Token
使用带过期时间的 token 以增强安全性:
interface TemporaryToken {
token: string
expiresAt: Date
scope: string[]
}
async function validateTemporaryToken(token: string) {
const tokenData = await getTokenData(token)
if (!tokenData) return false
if (new Date() > tokenData.expiresAt) return false
return true
}
多注册表认证
借助 命名空间注册表,您可以设置多个具有不同认证的注册表:
{
"registries": {
"@public": "https://public.company.com/{name}.json",
"@internal": {
"url": "https://internal.company.com/{name}.json",
"headers": {
"Authorization": "Bearer ${INTERNAL_TOKEN}"
}
},
"@premium": {
"url": "https://premium.company.com/{name}.json",
"headers": {
"X-License-Key": "${LICENSE_KEY}"
}
}
}
}
这样可以实现:
- 混合使用公共和私有注册表
- 针对不同注册表使用不同的认证方式
- 按访问权限组织组件
安全最佳实践
使用环境变量
切勿将 tokens 提交至版本控制。始终使用环境变量:
REGISTRY_TOKEN=your_secret_token_here
API_KEY=your_api_key_here
然后在 components.json
中引用它们:
{
"registries": {
"@private": {
"url": "https://registry.company.com/{name}.json",
"headers": {
"Authorization": "Bearer ${REGISTRY_TOKEN}"
}
}
}
}
使用 HTTPS
始终使用 HTTPS URL 以保护传输中的 token:
{
"@secure": "https://registry.company.com/{name}.json" // ✅
"@insecure": "http://registry.company.com/{name}.json" // ❌
}
添加限流
保护您的注册表免受滥用:
import rateLimit from "express-rate-limit"
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 每个 IP 在窗口期内最多 100 次请求
})
app.use("/registry", limiter)
轮换 Token
定期更换访问 token:
// 生成带过期时间的新 token。
function generateToken() {
const token = crypto.randomBytes(32).toString("hex")
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 天。
return { token, expiresAt }
}
记录访问日志
跟踪注册表访问以便安全和分析:
async function logAccess(request: Request, component: string, userId: string) {
await db.accessLog.create({
timestamp: new Date(),
userId,
component,
ip: request.ip,
userAgent: request.headers["user-agent"],
})
}
测试认证
本地测试您的认证注册表:
# 使用 curl 测试。
curl -H "Authorization: Bearer your_token" \
https://registry.company.com/button.json
# 使用 CLI 测试。
REGISTRY_TOKEN=your_token npx shadcn@latest add @private/button
错误处理
shadcn CLI 会优雅地处理认证错误:
- 401 Unauthorized:Token 无效或缺失
- 403 Forbidden:Token 无访问权限
- 429 Too Many Requests:请求频率超限
自定义错误消息
您的注册表服务器可以在响应体中返回自定义错误消息,CLI 会展示给用户:
// 注册表服务器返回自定义错误
return NextResponse.json(
{
error: "Unauthorized",
message:
"您的订阅已过期。请访问 company.com/billing 续订",
},
{ status: 403 }
)
用户将看到:
您的订阅已过期。请访问 company.com/billing 续订
这有助于提供针对性提示:
// 针对不同场景的不同错误消息
if (!token) {
return NextResponse.json(
{
error: "Unauthorized",
message:
"需要认证。请在您的 .env.local 文件中设置 REGISTRY_TOKEN",
},
{ status: 401 }
)
}
if (isExpiredToken(token)) {
return NextResponse.json(
{
error: "Unauthorized",
message: "Token 已过期。请访问 company.com/tokens 申请新 token",
},
{ status: 401 }
)
}
if (!hasTeamAccess(token, component)) {
return NextResponse.json(
{
error: "Forbidden",
message: `组件 '${component}' 仅限设计团队访问`,
},
{ status: 403 }
)
}
后续步骤
要设置多注册表及高级认证模式,请参阅 命名空间注册表 文档。内容包括:
- 多个认证注册表的设置
- 不同命名空间的认证方式
- 跨注册表依赖解析
- 高级认证模式
Deploy your shadcn/ui app on Vercel
Trusted by OpenAI, Sonos, Adobe, and more.
Vercel provides tools and infrastructure to deploy apps and features at scale.
Deploy to Vercel