feat(docs): add interactive demo components for technical appendices

Add placeholder Vue components for visualizing technical concepts across multiple domains including frontend routing, browser rendering, cache design, queue design, database principles, API design, cloud services, and backend evolution. These components provide interactive educational content for the documentation.

Update documentation structure to include new appendix sections and enhance existing content with visual components. Remove unused 'codex' dependency from package.json.
This commit is contained in:
sanbuphy
2026-02-06 03:34:50 +08:00
parent e8bba6f7c0
commit 7c70c37072
171 changed files with 69830 additions and 6689 deletions
@@ -0,0 +1,505 @@
<template>
<div class="access-key-management-demo">
<div class="demo-header">
<h4>访问密钥AK/SK生命周期管理</h4>
<p class="demo-desc">模拟 AK/SK 的创建使用和轮换流程</p>
</div>
<div class="lifecycle-container">
<!-- AK/SK Card -->
<div class="aksk-card">
<div class="card-header">
<span class="status-badge" :class="akStatus">{{ statusText }}</span>
<span class="age-indicator">已创建 {{ akAge }} </span>
</div>
<div class="credentials-display">
<div class="credential-row">
<span class="label">Access Key ID:</span>
<div class="value-container">
<span class="value">{{ maskedAK }}</span>
<button class="icon-btn" @click="toggleAKVisibility">
{{ showAK ? '🙈' : '👁' }}
</button>
</div>
</div>
<div class="credential-row">
<span class="label">Secret Access Key:</span>
<div class="value-container">
<span class="value">{{ maskedSK }}</span>
<button class="icon-btn" @click="toggleSKVisibility">
{{ showSK ? '🙈' : '👁' }}
</button>
</div>
</div>
</div>
<div class="usage-stats">
<div class="stat-item">
<span class="stat-value">{{ apiCalls }}</span>
<span class="stat-label">API 调用</span>
</div>
<div class="stat-item">
<span class="stat-value">{{ lastUsed }}</span>
<span class="stat-label">最后使用</span>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="action-panel">
<button
class="action-btn primary"
@click="rotateKey"
:disabled="isRotating"
>
<span class="btn-icon">🔄</span>
<span class="btn-text">轮换密钥</span>
</button>
<button
class="action-btn warning"
@click="deactivateKey"
:disabled="akStatus === 'inactive'"
>
<span class="btn-icon"></span>
<span class="btn-text">{{ akStatus === 'inactive' ? '已禁用' : '禁用密钥' }}</span>
</button>
<button
class="action-btn danger"
@click="deleteKey"
>
<span class="btn-icon">🗑</span>
<span class="btn-text">删除密钥</span>
</button>
</div>
</div>
<!-- Rotation Progress -->
<div class="rotation-progress" v-if="isRotating">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: rotationProgress + '%' }"></div>
</div>
<span class="progress-text">{{ rotationStatus }}</span>
</div>
<!-- Best Practices -->
<div class="best-practices">
<h5>🔒 AK/SK 安全管理最佳实践</h5>
<ul>
<li v-for="(tip, i) in securityTips" :key="i">
<span class="tip-icon">{{ tip.icon }}</span>
<span class="tip-text">{{ tip.text }}</span>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// AK/SK Data
const akId = ref('AKIAIOSFODNN7EXAMPLE')
const skId = ref('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY')
const akStatus = ref('active')
const akAge = ref(45)
const apiCalls = ref(123456)
const lastUsed = ref('2 小时前')
// Visibility
const showAK = ref(false)
const showSK = ref(false)
// Rotation
const isRotating = ref(false)
const rotationProgress = ref(0)
const rotationStatus = ref('')
// Computed
const maskedAK = computed(() => {
if (showAK.value) return akId.value
return akId.value.substring(0, 8) + '...'
})
const maskedSK = computed(() => {
if (showSK.value) return skId.value
return '************************************'
})
const statusText = computed(() => {
const map = {
active: '活跃',
inactive: '已禁用',
rotating: '轮换中'
}
return map[akStatus.value] || akStatus.value
})
// Methods
function toggleAKVisibility() {
showAK.value = !showAK.value
}
function toggleSKVisibility() {
showSK.value = !showSK.value
}
async function rotateKey() {
isRotating.value = true
rotationProgress.value = 0
rotationStatus.value = '生成新密钥对...'
// Step 1: Generate new key
await simulateProgress(30, '创建新 Access Key...')
const newAK = 'AKIA' + Math.random().toString(36).substring(2, 14).toUpperCase()
// Step 2: Update applications
await simulateProgress(60, '更新应用配置...')
// Step 3: Test new key
await simulateProgress(85, '验证新密钥...')
// Step 4: Disable old key
await simulateProgress(100, '禁用旧密钥...')
// Update data
akId.value = newAK
akAge.value = 0
apiCalls.value = 0
lastUsed.value = '刚刚'
isRotating.value = false
akStatus.value = 'active'
}
function simulateProgress(target, status) {
return new Promise((resolve) => {
rotationStatus.value = status
const interval = setInterval(() => {
rotationProgress.value += 1
if (rotationProgress.value >= target) {
clearInterval(interval)
resolve()
}
}, 20)
})
}
function deactivateKey() {
if (confirm('确定要禁用这个访问密钥吗?禁用后使用该密钥的应用将无法访问云服务。')) {
akStatus.value = 'inactive'
}
}
function deleteKey() {
if (confirm('警告:删除访问密钥是不可逆的操作!\n\n确定要删除这个密钥吗?')) {
alert('密钥已删除(演示模式)')
}
}
// Security Tips
const securityTips = [
{ icon: '🔄', text: '每 90 天轮换一次访问密钥' },
{ icon: '🔒', text: '绝不将 AK/SK 硬编码在代码中' },
{ icon: '👁️', text: '定期审计和监控密钥使用情况' },
{ icon: '🗑️', text: '及时删除不再使用的访问密钥' },
{ icon: '🛡️', text: '优先使用 IAM 角色替代访问密钥' }
]
</script>
<style scoped>
.access-key-management-demo {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
}
.demo-desc {
margin: 0;
opacity: 0.9;
font-size: 0.9rem;
}
.lifecycle-container {
display: grid;
grid-template-columns: 1fr auto;
gap: 20px;
margin-bottom: 20px;
}
/* AK/SK Card */
.aksk-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.status-badge {
padding: 4px 12px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-badge.active {
background: #4caf50;
color: white;
}
.status-badge.inactive {
background: #f44336;
color: white;
}
.age-indicator {
font-size: 0.75rem;
opacity: 0.7;
}
.credentials-display {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 16px;
}
.credential-row {
display: flex;
flex-direction: column;
gap: 4px;
}
.credential-row .label {
font-size: 0.7rem;
opacity: 0.7;
text-transform: uppercase;
}
.value-container {
display: flex;
align-items: center;
gap: 8px;
}
.value {
flex: 1;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.3);
border-radius: 6px;
font-family: monospace;
font-size: 0.8rem;
word-break: break-all;
}
.icon-btn {
padding: 8px;
background: rgba(255, 255, 255, 0.1);
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s;
}
.icon-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.usage-stats {
display: flex;
gap: 16px;
padding-top: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.stat-item {
display: flex;
flex-direction: column;
gap: 2px;
}
.stat-value {
font-size: 1.2rem;
font-weight: 700;
color: #4caf50;
}
.stat-label {
font-size: 0.7rem;
opacity: 0.7;
}
/* Action Panel */
.action-panel {
display: flex;
flex-direction: column;
gap: 12px;
}
.action-btn {
display: flex;
align-items: center;
gap: 10px;
padding: 14px 18px;
border: none;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
font-weight: 500;
}
.action-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.action-btn.primary {
background: linear-gradient(135deg, #4caf50 0%, #45a049 100%);
color: white;
}
.action-btn.primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(76, 175, 80, 0.3);
}
.action-btn.warning {
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
color: white;
}
.action-btn.warning:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(255, 152, 0, 0.3);
}
.action-btn.danger {
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
color: white;
}
.action-btn.danger:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(244, 67, 54, 0.3);
}
.btn-icon {
font-size: 1.2rem;
}
/* Rotation Progress */
.rotation-progress {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
margin-top: 20px;
}
.progress-bar {
height: 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
overflow: hidden;
margin-bottom: 12px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4caf50 0%, #8bc34a 100%);
border-radius: 4px;
transition: width 0.3s ease;
}
.progress-text {
display: block;
text-align: center;
font-size: 0.9rem;
opacity: 0.9;
}
/* Best Practices */
.best-practices {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
margin-top: 20px;
}
.best-practices h5 {
margin: 0 0 16px 0;
font-size: 1rem;
font-weight: 600;
}
.best-practices ul {
list-style: none;
padding: 0;
margin: 0;
}
.best-practices li {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.best-practices li:last-child {
border-bottom: none;
}
.tip-icon {
font-size: 1.2rem;
}
.tip-text {
font-size: 0.85rem;
opacity: 0.9;
}
@media (max-width: 768px) {
.lifecycle-container {
grid-template-columns: 1fr;
}
.action-panel {
flex-direction: row;
flex-wrap: wrap;
}
.action-btn {
flex: 1;
min-width: 140px;
}
}
</style>
@@ -0,0 +1,461 @@
<template>
<div class="best-practices-demo">
<div class="demo-header">
<h4>云账号权限管理最佳实践清单</h4>
<p class="demo-desc">点击查看详细的实施指南和代码示例</p>
</div>
<div class="practices-grid">
<div
v-for="(practice, index) in bestPractices"
:key="index"
class="practice-card"
:class="{ expanded: expandedCard === index }"
@click="toggleCard(index)"
>
<div class="card-header">
<div class="icon-wrapper" :style="{ background: practice.color }">
<span class="icon">{{ practice.icon }}</span>
</div>
<div class="title-wrapper">
<h5>{{ practice.title }}</h5>
<span class="priority" :class="practice.priority">{{ practice.priorityText }}</span>
</div>
<div class="expand-icon">{{ expandedCard === index ? '' : '+' }}</div>
</div>
<div class="card-body" v-if="expandedCard === index">
<p class="description">{{ practice.description }}</p>
<div class="checklist">
<h6> 检查清单</h6>
<ul>
<li v-for="(item, i) in practice.checklist" :key="i">{{ item }}</li>
</ul>
</div>
<div class="code-example" v-if="practice.code">
<h6>代码示例</h6>
<pre><code>{{ practice.code }}</code></pre>
</div>
<div class="tools" v-if="practice.tools">
<h6>推荐工具</h6>
<div class="tool-tags">
<span v-for="(tool, i) in practice.tools" :key="i" class="tool-tag">{{ tool }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const expandedCard = ref(0)
const bestPractices = [
{
icon: '👑',
title: '根账号保护',
priority: 'p0',
priorityText: 'P0 - 最高优先级',
color: '#f44336',
description: '根账号是云服务的所有者,拥有所有权限。必须实施最高级别的保护措施。',
checklist: [
'启用 MFA(推荐硬件 MFA 设备)',
'创建 IAM 管理员用户用于日常操作',
'删除或锁定根账号的访问密钥',
'配置根账号使用告警',
'设置账号恢复联系信息'
],
code: `# AWS CLI - 创建管理员用户并禁用根账号 AK
aws iam create-user --user-name AdminUser
aws iam attach-user-policy --user-name AdminUser \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# 删除根账号访问密钥(必须使用根账号登录控制台操作)`,
tools: ['硬件 MFA (YubiKey)', '虚拟 MFA (Google Authenticator)', 'AWS IAM', '阿里云 RAM']
},
{
icon: '👤',
title: '用户权限最小化',
priority: 'p0',
priorityText: 'P0 - 最高优先级',
color: '#ff9800',
description: '遵循最小权限原则,只授予用户完成工作所需的最低权限。',
checklist: [
'避免使用 AdministratorAccess 等全权限策略',
'使用 IAM 用户组批量管理权限',
'定期审查和删除未使用的 IAM 用户',
'为不同角色创建细粒度的自定义策略',
'使用 IAM Access Analyzer 识别过度宽松的权限'
],
code: `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "ap-northeast-1"
}
}
}
]
}`,
tools: ['IAM Policy Simulator', 'IAM Access Analyzer', 'AWS CloudTrail', 'AWS Config']
},
{
icon: '🎭',
title: '优先使用 IAM 角色',
priority: 'p1',
priorityText: 'P1 - 高优先级',
color: '#4caf50',
description: 'IAM 角色没有长期凭证,通过临时凭证访问,大大降低凭证泄露风险。',
checklist: [
'EC2 实例使用实例角色(Instance Profile)',
'Lambda 函数使用执行角色',
'ECS 任务使用任务角色',
'跨账号访问使用角色扮演(AssumeRole)',
'CI/CD 流水线使用 OIDC 联邦身份'
],
code: `import boto3
# EC2 实例自动使用附加的实例角色
# 无需提供任何凭证
s3 = boto3.client('s3')
# 跨账号角色扮演
sts = boto3.client('sts')
assumed_role = sts.assume_role(
RoleArn='arn:aws:iam::123456789012:role/CrossAccountRole',
RoleSessionName='MyApplication',
DurationSeconds=3600
)
# 使用临时凭证
temp_creds = assumed_role['Credentials']
s3_cross = boto3.client(
's3',
aws_access_key_id=temp_creds['AccessKeyId'],
aws_secret_access_key=temp_creds['SecretAccessKey'],
aws_session_token=temp_creds['SessionToken']
)`,
tools: ['IAM Roles', 'AWS STS', 'EC2 Instance Profiles', 'Lambda Execution Roles']
},
{
icon: '🔑',
title: '访问密钥安全管理',
priority: 'p1',
priorityText: 'P1 - 高优先级',
color: '#2196f3',
description: '如果必须使用访问密钥(AK/SK),需要实施严格的安全管理措施。',
checklist: [
'绝不将 AK/SK 硬编码在代码或配置文件中',
'使用环境变量或密钥管理服务(如 AWS Secrets Manager)',
'每 90 天轮换一次访问密钥',
'定期审查和删除未使用的访问密钥',
'启用 CloudTrail 记录所有 AK/SK 的使用情况'
],
code: `# ❌ 错误做法 - 硬编码凭证
import boto3
s3 = boto3.client(
's3',
aws_access_key_id='AKIAIOSFODNN7EXAMPLE',
aws_secret_access_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
)
# ✅ 正确做法 - 使用环境变量
import boto3
import os
s3 = boto3.client(
's3',
aws_access_key_id=os.environ.get('AWS_ACCESS_KEY_ID'),
aws_secret_access_key=os.environ.get('AWS_SECRET_ACCESS_KEY')
)
# ✅ 正确做法 - 使用 AWS Secrets Manager
import boto3
import json
secrets_client = boto3.client('secretsmanager')
secret_value = secrets_client.get_secret_value(SecretId='my-app/credentials')
credentials = json.loads(secret_value['SecretString'])
s3 = boto3.client(
's3',
aws_access_key_id=credentials['access_key_id'],
aws_secret_access_key=credentials['secret_access_key']
)`,
tools: ['AWS Secrets Manager', 'HashiCorp Vault', 'Azure Key Vault', 'GCP Secret Manager']
},
{
icon: '📊',
title: '监控与审计',
priority: 'p2',
priorityText: 'P2 - 中优先级',
color: '#9c27b0',
description: '建立全面的监控和审计机制,及时发现和响应安全事件。',
checklist: [
'启用 CloudTrail 记录所有 API 调用',
'配置关键操作的实时告警(根账号使用、策略变更等)',
'使用 IAM Access Analyzer 持续分析权限',
'定期审查 IAM 用户和权限配置',
'将日志存储到独立的审计账号,防止篡改'
],
code: `# AWS CloudTrail 配置示例
aws cloudtrail create-trail \
--name OrganizationTrail \
--s3-bucket-name my-cloudtrail-bucket \
--is-organization-trail \
--enable-log-file-validation \
--is-multi-region-trail
# CloudWatch 告警配置 - 根账号使用
aws cloudwatch put-metric-alarm \
--alarm-name RootAccountUsageAlarm \
--alarm-description "Alert when root account is used" \
--metric-name RootAccountUsage \
--namespace CloudTrailMetrics \
--statistic Sum \
--period 300 \
--evaluation-periods 1 \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold
# IAM Access Analyzer 创建分析器
aws accessanalyzer create-analyzer \
--analyzer-name MyOrgAnalyzer \
--type ORGANIZATION`,
tools: ['AWS CloudTrail', 'AWS CloudWatch', 'IAM Access Analyzer', 'AWS Config', 'AWS Security Hub']
}
]
function toggleCard(index) {
expandedCard.value = expandedCard.value === index ? null : index
}
</script>
<style scoped>
.best-practices-demo {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
}
.demo-desc {
margin: 0;
opacity: 0.9;
font-size: 0.9rem;
}
.practices-grid {
display: grid;
gap: 16px;
}
.practice-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.practice-card:hover {
background: rgba(255, 255, 255, 0.15);
transform: translateY(-2px);
}
.practice-card.expanded {
background: rgba(255, 255, 255, 0.95);
color: #333;
}
.card-header {
display: flex;
align-items: center;
gap: 16px;
}
.icon-wrapper {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
flex-shrink: 0;
}
.title-wrapper {
flex: 1;
}
.title-wrapper h5 {
margin: 0 0 4px 0;
font-size: 1.1rem;
}
.priority {
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
}
.priority.p0 {
background: #f44336;
color: white;
}
.priority.p1 {
background: #ff9800;
color: white;
}
.priority.p2 {
background: #2196f3;
color: white;
}
.expand-icon {
font-size: 1.5rem;
font-weight: 300;
opacity: 0.7;
}
.card-body {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.description {
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: 16px;
}
.checklist {
margin-bottom: 20px;
}
.checklist h6 {
margin: 0 0 12px 0;
font-size: 0.9rem;
color: #667eea;
}
.checklist ul {
list-style: none;
padding: 0;
margin: 0;
}
.checklist li {
padding: 6px 0;
padding-left: 24px;
position: relative;
font-size: 0.9rem;
}
.checklist li:before {
content: '☐';
position: absolute;
left: 0;
color: #667eea;
}
.code-example {
margin-bottom: 20px;
}
.code-example h6 {
margin: 0 0 12px 0;
font-size: 0.9rem;
color: #667eea;
}
.code-example pre {
background: #1e1e1e;
border-radius: 8px;
padding: 16px;
overflow-x: auto;
margin: 0;
}
.code-example code {
color: #d4d4d4;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 0.8rem;
line-height: 1.5;
}
.tools h6 {
margin: 0 0 12px 0;
font-size: 0.9rem;
color: #667eea;
}
.tool-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tool-tag {
padding: 4px 12px;
background: #e3f2fd;
color: #1565c0;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 500;
}
@media (max-width: 768px) {
.card-header {
flex-wrap: wrap;
}
.icon-wrapper {
width: 40px;
height: 40px;
font-size: 1.2rem;
}
.title-wrapper h5 {
font-size: 1rem;
}
}
</style>
@@ -0,0 +1,196 @@
<template>
<div class="cross-account-access-demo">
<div class="demo-header">
<h4>跨账号访问流程演示</h4>
<p class="demo-desc">角色扮演AssumeRole获取临时凭证</p>
</div>
<div class="flow-diagram">
<div class="account-box source">
<div class="account-header">账号 A源账号</div>
<div class="account-content">
<div class="entity">IAM User / Application</div>
<div class="action">调用 sts:AssumeRole</div>
</div>
</div>
<div class="arrow"></div>
<div class="account-box sts">
<div class="account-header">STS 服务</div>
<div class="account-content">
<div class="step">1. 验证源身份</div>
<div class="step">2. 检查信任策略</div>
<div class="step">3. 生成临时凭证</div>
</div>
</div>
<div class="arrow"></div>
<div class="account-box target">
<div class="account-header">账号 B目标账号</div>
<div class="account-content">
<div class="entity">CrossAccountRole</div>
<div class="resource">访问 S3 / EC2 等资源</div>
</div>
</div>
</div>
<div class="code-example">
<h5>Python 代码示例</h5>
<pre><code>import boto3
# 在账号 A 中使用 IAM 用户凭证
sts_client = boto3.client('sts')
# 扮演账号 B 的角色
assumed_role = sts_client.assume_role(
RoleArn='arn:aws:iam::123456789012:role/CrossAccountRole',
RoleSessionName='MySession',
DurationSeconds=3600
)
# 获取临时凭证
credentials = assumed_role['Credentials']
# 使用临时凭证访问账号 B 的资源
s3_client = boto3.client(
's3',
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken']
)</code></pre>
</div>
</div>
</template>
<style scoped>
.cross-account-access-demo {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
}
.demo-desc {
margin: 0;
opacity: 0.9;
font-size: 0.9rem;
}
.flow-diagram {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.account-box {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 16px;
min-width: 180px;
color: #333;
}
.account-header {
font-weight: 700;
font-size: 0.85rem;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid #eee;
}
.account-content {
font-size: 0.8rem;
}
.entity {
background: #e3f2fd;
padding: 6px 10px;
border-radius: 4px;
margin-bottom: 8px;
color: #1565c0;
font-weight: 500;
}
.action {
color: #666;
font-style: italic;
}
.step {
padding: 4px 0;
color: #666;
border-bottom: 1px solid #f0f0f0;
}
.step:last-child {
border-bottom: none;
}
.resource {
background: #e8f5e9;
padding: 6px 10px;
border-radius: 4px;
margin-top: 8px;
color: #2e7d32;
}
.arrow {
font-size: 2rem;
color: rgba(255, 255, 255, 0.8);
}
.code-example {
background: #1e1e1e;
border-radius: 12px;
padding: 20px;
}
.code-example h5 {
margin: 0 0 12px 0;
color: #fff;
font-size: 0.9rem;
}
.code-example pre {
margin: 0;
overflow-x: auto;
}
.code-example code {
color: #d4d4d4;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 0.8rem;
line-height: 1.5;
}
@media (max-width: 768px) {
.flow-diagram {
flex-direction: column;
}
.arrow {
transform: rotate(90deg);
}
.account-box {
min-width: auto;
width: 100%;
}
}
</style>
@@ -0,0 +1,368 @@
<template>
<div class="iam-ram-comparison-demo">
<div class="demo-header">
<h4>AWS IAM vs 阿里云 RAM 对比</h4>
<p class="demo-desc">点击各个模块查看详细对比</p>
</div>
<div class="comparison-container">
<!-- AWS IAM Column -->
<div class="platform-column aws-column">
<div class="platform-header aws">
<div class="logo">AWS</div>
<h5>IAM</h5>
<span class="subtitle">Identity and Access Management</span>
</div>
<div class="features-list">
<div
v-for="(feature, index) in awsFeatures"
:key="index"
class="feature-item"
:class="{ active: selectedFeature === `aws-${index}` }"
@click="selectFeature('aws', index)"
>
<div class="feature-icon">{{ feature.icon }}</div>
<div class="feature-content">
<span class="feature-name">{{ feature.name }}</span>
<span class="feature-desc">{{ feature.desc }}</span>
</div>
</div>
</div>
</div>
<!-- Comparison Details -->
<div class="comparison-details" v-if="selectedFeatureData">
<div class="detail-card">
<h6>{{ selectedFeatureData.name }}</h6>
<div class="comparison-row">
<div class="aws-detail">
<span class="label">AWS IAM</span>
<p>{{ selectedFeatureData.awsDetail }}</p>
<code v-if="selectedFeatureData.awsExample">{{ selectedFeatureData.awsExample }}</code>
</div>
<div class="vs-divider">VS</div>
<div class="ram-detail">
<span class="label">阿里云 RAM</span>
<p>{{ selectedFeatureData.ramDetail }}</p>
<code v-if="selectedFeatureData.ramExample">{{ selectedFeatureData.ramExample }}</code>
</div>
</div>
</div>
</div>
<!-- Alibaba Cloud RAM Column -->
<div class="platform-column ram-column">
<div class="platform-header ram">
<div class="logo">阿里云</div>
<h5>RAM</h5>
<span class="subtitle">Resource Access Management</span>
</div>
<div class="features-list">
<div
v-for="(feature, index) in ramFeatures"
:key="index"
class="feature-item"
:class="{ active: selectedFeature === `ram-${index}` }"
@click="selectFeature('ram', index)"
>
<div class="feature-icon">{{ feature.icon }}</div>
<div class="feature-content">
<span class="feature-name">{{ feature.name }}</span>
<span class="feature-desc">{{ feature.desc }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const selectedFeature = ref(null)
const featureDetails = [
{
name: '用户管理',
awsDetail: '使用 IAM User,支持编程访问和控制台访问,可分配独立 AK/SK',
ramDetail: '使用 RAM 用户,功能与 IAM User 类似,支持子账号登录控制台',
awsExample: 'arn:aws:iam::123456789012:user/alice',
ramExample: 'acs:ram::123456789012:user/alice'
},
{
name: '用户组管理',
awsDetail: 'IAM Group 用于批量管理用户权限,一个用户可属于多个组',
ramDetail: 'RAM 用户组功能类似,支持按部门或项目分组管理',
awsExample: 'arn:aws:iam::123456789012:group/Developers',
ramExample: 'acs:ram::123456789012:group/Developers'
},
{
name: '角色与扮演',
awsDetail: 'IAM Role 支持跨账号访问和服务角色,使用 STS AssumeRole',
ramDetail: 'RAM 角色支持跨云账号访问和临时授权,使用 STS AssumeRole',
awsExample: 'arn:aws:iam::123456789012:role/CrossAccountRole',
ramExample: 'acs:ram::123456789012:role/CrossAccountRole'
},
{
name: '权限策略',
awsDetail: 'IAM Policy 使用 JSON 格式,支持 Action/Resource/Condition',
ramDetail: 'RAM Policy 语法类似,支持阿里云服务特定的 Action',
awsExample: '"Action": "s3:GetObject"',
ramExample: '"Action": "oss:GetObject"'
},
{
name: '身份联合',
awsDetail: '支持 SAML 2.0 和 OIDC,可与 AD、Okta 等 IdP 集成',
ramDetail: '支持 SAML 2.0 和企业 AD/LDAP,支持钉钉等国内 IdP',
awsExample: 'SAML Provider: arn:aws:iam::123:saml-provider/Okta',
ramExample: 'SAML Provider: acs:ram::123:saml-provider/DingTalk'
},
{
name: '访问密钥',
awsDetail: 'IAM User 可创建 AK/SK,支持定期轮换和访问分析',
ramDetail: 'RAM 用户支持 AccessKey,提供密钥使用分析和安全建议',
awsExample: 'AKIAIOSFODNN7EXAMPLE',
ramExample: 'LTAI...'
}
]
const awsFeatures = featureDetails.map((f, i) => ({
icon: ['👤', '👥', '🎭', '📋', '🔗', '🔑'][i],
name: f.name,
desc: f.awsDetail.slice(0, 30) + '...'
}))
const ramFeatures = featureDetails.map((f, i) => ({
icon: ['👤', '👥', '🎭', '📋', '🔗', '🔑'][i],
name: f.name,
desc: f.ramDetail.slice(0, 30) + '...'
}))
const selectedFeatureData = computed(() => {
if (!selectedFeature.value) return null
const [platform, index] = selectedFeature.value.split('-')
return featureDetails[parseInt(index)]
})
function selectFeature(platform, index) {
selectedFeature.value = `${platform}-${index}`
}
</script>
<style scoped>
.iam-ram-comparison-demo {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.5rem;
}
.demo-desc {
margin: 0;
opacity: 0.9;
font-size: 0.9rem;
}
.comparison-container {
display: grid;
grid-template-columns: 1fr 1.5fr 1fr;
gap: 16px;
align-items: start;
}
.platform-column {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
overflow: hidden;
}
.platform-header {
padding: 16px;
text-align: center;
}
.platform-header.aws {
background: linear-gradient(135deg, #ff9900 0%, #ff6600 100%);
}
.platform-header.ram {
background: linear-gradient(135deg, #ff6a00 0%, #ee0979 100%);
}
.platform-header .logo {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 4px;
}
.platform-header h5 {
margin: 0;
font-size: 1.1rem;
}
.platform-header .subtitle {
font-size: 0.7rem;
opacity: 0.9;
}
.features-list {
padding: 12px;
}
.feature-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
margin-bottom: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.feature-item:hover,
.feature-item.active {
background: rgba(255, 255, 255, 0.2);
transform: translateX(4px);
}
.feature-icon {
font-size: 1.2rem;
}
.feature-content {
display: flex;
flex-direction: column;
flex: 1;
}
.feature-name {
font-weight: 600;
font-size: 0.85rem;
}
.feature-desc {
font-size: 0.7rem;
opacity: 0.8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.comparison-details {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 20px;
color: #333;
}
.detail-card {
text-align: center;
}
.detail-card h6 {
margin: 0 0 16px 0;
font-size: 1.1rem;
color: #667eea;
}
.comparison-row {
display: flex;
align-items: stretch;
gap: 16px;
}
.aws-detail,
.ram-detail {
flex: 1;
padding: 12px;
border-radius: 8px;
text-align: left;
}
.aws-detail {
background: linear-gradient(135deg, #fff8e1 0%, #ffecb3 100%);
}
.ram-detail {
background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%);
}
.aws-detail .label,
.ram-detail .label {
display: block;
font-weight: 700;
font-size: 0.8rem;
margin-bottom: 6px;
}
.aws-detail .label {
color: #ff6f00;
}
.ram-detail .label {
color: #c2185b;
}
.aws-detail p,
.ram-detail p {
margin: 0 0 8px 0;
font-size: 0.8rem;
line-height: 1.4;
}
.aws-detail code,
.ram-detail code {
display: block;
padding: 6px;
background: rgba(0, 0, 0, 0.05);
border-radius: 4px;
font-size: 0.65rem;
word-break: break-all;
}
.vs-divider {
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.9rem;
color: #999;
padding: 0 8px;
}
@media (max-width: 1024px) {
.comparison-container {
grid-template-columns: 1fr;
gap: 20px;
}
.comparison-details {
order: -1;
}
.comparison-row {
flex-direction: column;
}
.vs-divider {
padding: 8px 0;
}
}
</style>
@@ -0,0 +1,362 @@
<template>
<div class="identity-provider-demo">
<div class="demo-header">
<h4>身份提供商(IdP)集成流程</h4>
<p class="demo-desc">点击步骤查看 SSO 单点登录流程</p>
</div>
<div class="sso-flow">
<div class="flow-step" v-for="(step, index) in steps" :key="index"
:class="{ active: currentStep === index, completed: currentStep > index }"
@click="goToStep(index)">
<div class="step-number">{{ index + 1 }}</div>
<div class="step-content">
<span class="step-title">{{ step.title }}</span>
<span class="step-desc">{{ step.desc }}</span>
</div>
<div class="step-arrow" v-if="index < steps.length - 1"></div>
</div>
</div>
<div class="detail-panel" v-if="currentStepData">
<h5>{{ currentStepData.title }}</h5>
<p>{{ currentStepData.detail }}</p>
<div class="code-block" v-if="currentStepData.code">
<pre><code>{{ currentStepData.code }}</code></pre>
</div>
<div class="entity-flow" v-if="currentStepData.flow">
<div class="flow-row" v-for="(row, i) in currentStepData.flow" :key="i">
<span class="entity" :class="row.from.type">{{ row.from.name }}</span>
<span class="action">{{ row.action }}</span>
<span class="entity" :class="row.to.type">{{ row.to.name }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const currentStep = ref(0)
const steps = [
{ title: '用户访问应用', desc: '用户尝试访问企业应用' },
{ title: '重定向到 IdP', desc: '应用将用户重定向到身份提供商' },
{ title: '用户登录认证', desc: '用户在 IdP 输入企业账号密码' },
{ title: '颁发 SAML 令牌', desc: 'IdP 验证成功后颁发 SAML Assertion' },
{ title: '返回应用', desc: '携带令牌返回企业应用' },
{ title: '换取云凭证', desc: '应用使用令牌换取云临时凭证' },
{ title: '访问云资源', desc: '使用临时凭证访问云资源' }
]
const stepDetails = [
{
title: '用户访问企业应用',
detail: '用户打开浏览器,访问企业内部的业务系统(如 CRM、ERP 等)。此时用户尚未登录,应用检测到用户没有有效的会话。',
flow: [
{ from: { name: '用户', type: 'user' }, action: '访问 →', to: { name: '企业应用', type: 'app' } }
]
},
{
title: '应用重定向到 IdP',
detail: '应用发现用户未登录,生成 SAML Request,将用户浏览器重定向到企业的身份提供商(IdP,如 Azure AD、Okta 等)。',
code: `// SAML Request 示例
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
Destination="https://idp.example.com/saml/sso"
ID="_1234567890"
IssueInstant="2024-01-15T10:00:00Z">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
https://app.example.com
</saml:Issuer>
</samlp:AuthnRequest>`,
flow: [
{ from: { name: '企业应用', type: 'app' }, action: '重定向 →', to: { name: 'IdP', type: 'idp' } }
]
},
{
title: '用户在 IdP 登录',
detail: '用户在 IdP 的登录页面输入企业账号和密码。IdP 验证用户身份,可能还需要进行 MFA 多因素认证。',
flow: [
{ from: { name: '用户', type: 'user' }, action: '登录 →', to: { name: 'IdP', type: 'idp' } }
]
},
{
title: 'IdP 颁发 SAML Assertion',
detail: '用户认证成功后,IdP 生成包含用户身份和属性的 SAML Assertion(断言),并使用 IdP 的私钥签名。',
code: `<!-- SAML Response 示例 -->
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_response123"
InResponseTo="_1234567890"
IssueInstant="2024-01-15T10:01:00Z">
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_assertion456"
IssueInstant="2024-01-15T10:01:00Z">
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
user@example.com
</saml:NameID>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="Role">
<saml:AttributeValue>Admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>`,
flow: [
{ from: { name: 'IdP', type: 'idp' }, action: '颁发令牌 →', to: { name: '用户浏览器', type: 'user' } }
]
},
{
title: '返回企业应用',
detail: 'IdP 通过浏览器将 SAML Response POST 到企业应用。应用验证 SAML 断言的签名,确认用户身份,建立用户会话。',
flow: [
{ from: { name: '用户浏览器', type: 'user' }, action: 'POST 令牌 →', to: { name: '企业应用', type: 'app' } }
]
},
{
title: '换取云临时凭证',
detail: '企业应用使用 SAML Assertion 向云厂商的 STS 服务请求临时安全凭证。云服务验证 SAML 断言后,颁发临时 AK/SK/Token。',
code: `# Python 示例:使用 SAML 换取 AWS 临时凭证
import boto3
# 创建 STS 客户端
sts = boto3.client('sts')
# 使用 SAML 断言请求临时凭证
response = sts.assume_role_with_saml(
RoleArn='arn:aws:iam::123456789012:role/SAML-Role',
PrincipalArn='arn:aws:iam::123456789012:saml-provider/Okta',
SAMLAssertion='VGhpcyBpcyBhIHRlc3QgU0FNTCBhc3NlcnRpb24=',
DurationSeconds=3600
)
# 获取临时凭证
credentials = response['Credentials']
print(f"Access Key: {credentials['AccessKeyId']}")
print(f"Secret Key: {credentials['SecretAccessKey']}")
print(f"Session Token: {credentials['SessionToken']}")`,
flow: [
{ from: { name: '企业应用', type: 'app' }, action: 'AssumeRole →', to: { name: '云 STS', type: 'cloud' } }
]
},
{
title: '访问云资源',
detail: '企业应用使用获取到的临时凭证,调用云服务的 API 访问资源(如 S3、EC2、数据库等)。临时凭证有过期时间,到期后需要重新获取。',
flow: [
{ from: { name: '企业应用', type: 'app' }, action: '访问资源 →', to: { name: '云服务', type: 'cloud' } }
]
}
]
const currentStepData = computed(() => {
if (currentStep.value === null) return null
return stepDetails[currentStep.value]
})
function goToStep(index) {
currentStep.value = index
}
</script>
<style scoped>
.identity-provider-demo {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
}
.demo-desc {
margin: 0;
opacity: 0.9;
font-size: 0.9rem;
}
.sso-flow {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
margin-bottom: 24px;
}
.flow-step {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
min-width: 120px;
}
.flow-step:hover,
.flow-step.active {
background: rgba(255, 255, 255, 0.25);
transform: scale(1.02);
}
.flow-step.completed {
background: rgba(76, 175, 80, 0.3);
}
.step-number {
width: 24px;
height: 24px;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: bold;
}
.step-content {
display: flex;
flex-direction: column;
flex: 1;
}
.step-title {
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
}
.step-desc {
font-size: 0.6rem;
opacity: 0.8;
white-space: nowrap;
}
.step-arrow {
font-size: 1.2rem;
opacity: 0.6;
}
.detail-panel {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 20px;
color: #333;
}
.detail-panel h5 {
margin: 0 0 12px 0;
font-size: 1.1rem;
color: #1e3c72;
}
.detail-panel p {
margin: 0 0 16px 0;
font-size: 0.9rem;
line-height: 1.5;
}
.code-block {
background: #1e1e1e;
border-radius: 8px;
padding: 12px;
margin-bottom: 16px;
overflow-x: auto;
}
.code-block pre {
margin: 0;
}
.code-block code {
color: #d4d4d4;
font-size: 0.75rem;
line-height: 1.4;
font-family: 'Consolas', 'Monaco', monospace;
}
.entity-flow {
display: flex;
flex-direction: column;
gap: 8px;
}
.flow-row {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 8px;
background: #f5f5f5;
border-radius: 6px;
}
.entity {
padding: 4px 10px;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 500;
}
.entity.user {
background: #e3f2fd;
color: #1565c0;
}
.entity.app {
background: #f3e5f5;
color: #7b1fa2;
}
.entity.idp {
background: #e8f5e9;
color: #2e7d32;
}
.entity.cloud {
background: #fff3e0;
color: #ef6c00;
}
.action {
font-size: 0.75rem;
color: #666;
font-weight: 500;
}
@media (max-width: 768px) {
.sso-flow {
flex-direction: column;
align-items: stretch;
}
.flow-step {
min-width: auto;
}
.step-arrow {
display: none;
}
.flow-row {
flex-direction: column;
gap: 4px;
}
}
</style>
@@ -0,0 +1,350 @@
<template>
<div class="mfa-security-demo">
<div class="demo-header">
<h4>MFA 多因素认证模拟</h4>
<p class="demo-desc">体验 MFA 双因素认证流程</p>
</div>
<div class="mfa-flow">
<div class="auth-step" :class="{ active: step >= 1, completed: step > 1 }">
<div class="step-icon">🔐</div>
<div class="step-label">密码验证</div>
</div>
<div class="step-arrow"></div>
<div class="auth-step" :class="{ active: step >= 2, completed: step > 2 }">
<div class="step-icon">📱</div>
<div class="step-label">MFA 验证</div>
</div>
<div class="step-arrow"></div>
<div class="auth-step" :class="{ active: step >= 3 }">
<div class="step-icon"></div>
<div class="step-label">登录成功</div>
</div>
</div>
<div class="auth-panel" v-if="step === 1">
<h5>请输入密码</h5>
<input type="password" v-model="password" placeholder="输入密码" @keyup.enter="verifyPassword" />
<button @click="verifyPassword" :disabled="!password">验证密码</button>
</div>
<div class="auth-panel" v-if="step === 2">
<h5>MFA 验证</h5>
<div class="totp-display">
<span class="totp-code">{{ totpCode }}</span>
<div class="totp-timer">
<div class="timer-bar" :style="{ width: timerWidth + '%' }"></div>
</div>
</div>
<input type="text" v-model="userCode" placeholder="输入6位验证码" maxlength="6" @keyup.enter="verifyMFA" />
<button @click="verifyMFA" :disabled="userCode.length !== 6">验证</button>
</div>
<div class="success-message" v-if="step === 3">
<div class="success-icon">🎉</div>
<h5>登录成功</h5>
<p>已通过 MFA 双因素认证</p>
<button @click="reset">重新演示</button>
</div>
<div class="security-tips">
<h5>💡 MFA 安全提示</h5>
<ul>
<li>启用 MFA 可降低 99.9% 的账号被盗风险</li>
<li>推荐使用 TOTP 应用Google AuthenticatorMicrosoft Authenticator</li>
<li>硬件安全密钥 YubiKey提供最高级别的安全性</li>
<li>务必备份 MFA 恢复码防止设备丢失无法登录</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const step = ref(1)
const password = ref('')
const userCode = ref('')
const totpCode = ref('123456')
const timerWidth = ref(100)
let timerInterval = null
function generateTOTP() {
return Math.floor(100000 + Math.random() * 900000).toString()
}
function startTimer() {
timerWidth.value = 100
if (timerInterval) clearInterval(timerInterval)
timerInterval = setInterval(() => {
timerWidth.value -= 1.67
if (timerWidth.value <= 0) {
totpCode.value = generateTOTP()
timerWidth.value = 100
}
}, 100)
}
function verifyPassword() {
if (password.value) {
step.value = 2
totpCode.value = generateTOTP()
startTimer()
}
}
function verifyMFA() {
if (userCode.value.length === 6) {
step.value = 3
if (timerInterval) clearInterval(timerInterval)
}
}
function reset() {
step.value = 1
password.value = ''
userCode.value = ''
if (timerInterval) clearInterval(timerInterval)
}
onMounted(() => {
if (step.value === 2) startTimer()
})
onUnmounted(() => {
if (timerInterval) clearInterval(timerInterval)
})
</script>
<style scoped>
.mfa-security-demo {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
}
.demo-desc {
margin: 0;
opacity: 0.9;
font-size: 0.9rem;
}
.mfa-flow {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.auth-step {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 16px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
opacity: 0.5;
transition: all 0.3s ease;
}
.auth-step.active {
opacity: 1;
background: rgba(76, 175, 80, 0.2);
border: 1px solid rgba(76, 175, 80, 0.5);
}
.auth-step.completed {
opacity: 1;
background: rgba(76, 175, 80, 0.3);
}
.step-icon {
font-size: 2rem;
}
.step-label {
font-size: 0.8rem;
font-weight: 500;
}
.step-arrow {
font-size: 1.5rem;
opacity: 0.6;
}
.auth-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
}
.auth-panel h5 {
margin: 0 0 16px 0;
font-size: 1.1rem;
}
.auth-panel input {
width: 100%;
padding: 12px 16px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
background: rgba(0, 0, 0, 0.2);
color: white;
font-size: 1rem;
margin-bottom: 12px;
box-sizing: border-box;
}
.auth-panel input::placeholder {
color: rgba(255, 255, 255, 0.5);
}
.auth-panel button {
width: 100%;
padding: 12px 24px;
border: none;
border-radius: 8px;
background: linear-gradient(135deg, #4caf50 0%, #45a049 100%);
color: white;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.auth-panel button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.auth-panel button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(76, 175, 80, 0.3);
}
.totp-display {
background: rgba(0, 0, 0, 0.3);
border-radius: 8px;
padding: 16px;
text-align: center;
margin-bottom: 16px;
}
.totp-code {
display: block;
font-size: 2.5rem;
font-weight: 700;
font-family: monospace;
letter-spacing: 0.2em;
margin-bottom: 8px;
}
.totp-timer {
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
overflow: hidden;
}
.timer-bar {
height: 100%;
background: linear-gradient(90deg, #4caf50 0%, #8bc34a 100%);
transition: width 0.1s linear;
}
.success-message {
background: rgba(76, 175, 80, 0.2);
border: 1px solid rgba(76, 175, 80, 0.5);
border-radius: 12px;
padding: 32px;
text-align: center;
margin-bottom: 20px;
}
.success-icon {
font-size: 4rem;
margin-bottom: 16px;
}
.success-message h5 {
margin: 0 0 8px 0;
font-size: 1.5rem;
}
.success-message p {
margin: 0 0 20px 0;
opacity: 0.8;
}
.success-message button {
padding: 12px 32px;
border: none;
border-radius: 8px;
background: rgba(255, 255, 255, 0.2);
color: white;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
}
.success-message button:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.security-tips {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
}
.security-tips h5 {
margin: 0 0 12px 0;
font-size: 1rem;
}
.security-tips ul {
list-style: none;
padding: 0;
margin: 0;
}
.security-tips li {
padding: 6px 0;
font-size: 0.85rem;
opacity: 0.9;
}
@media (max-width: 768px) {
.mfa-flow {
flex-direction: column;
}
.step-arrow {
transform: rotate(90deg);
}
.lifecycle-container {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,359 @@
<template>
<div class="permission-hierarchy-demo">
<div class="demo-header">
<h4>权限层级结构</h4>
<p class="demo-desc">点击层级查看详细权限范围</p>
</div>
<div class="hierarchy-container">
<div
v-for="(level, index) in hierarchyLevels"
:key="index"
class="level-row"
:class="{ active: selectedLevel === index }"
@click="selectLevel(index)"
>
<div class="level-icon">{{ level.icon }}</div>
<div class="level-content">
<span class="level-name">{{ level.name }}</span>
<span class="level-scope">{{ level.scope }}</span>
</div>
<div class="permission-badges">
<span
v-for="(perm, i) in level.permissions.slice(0, 3)"
:key="i"
class="badge"
>
{{ perm }}
</span>
<span v-if="level.permissions.length > 3" class="badge more">
+{{ level.permissions.length - 3 }}
</span>
</div>
</div>
</div>
<div class="detail-panel" v-if="selectedLevelData">
<h5>{{ selectedLevelData.name }} 详情</h5>
<div class="detail-section">
<span class="label">权限范围:</span>
<span class="value">{{ selectedLevelData.scope }}</span>
</div>
<div class="detail-section">
<span class="label">典型场景:</span>
<span class="value">{{ selectedLevelData.scenario }}</span>
</div>
<div class="detail-section permissions-list">
<span class="label">拥有权限:</span>
<div class="permissions-grid">
<span
v-for="(perm, i) in selectedLevelData.permissions"
:key="i"
class="perm-tag"
:class="perm.type"
>
{{ perm.name }}
</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const selectedLevel = ref(0)
const hierarchyLevels = [
{
icon: '👑',
name: '根账号 (Root)',
scope: '全账号最高权限',
scenario: '账号所有者,拥有云服务的所有权限',
permissions: [
{ name: '完全管理权限', type: 'full' },
{ name: '账单管理', type: 'billing' },
{ name: '组织架构管理', type: 'org' },
{ name: '关闭账号', type: 'critical' },
{ name: '恢复已删除资源', type: 'admin' }
]
},
{
icon: '👤',
name: 'IAM 管理员',
scope: 'IAM 全权限',
scenario: '管理所有 IAM 用户、角色、策略',
permissions: [
{ name: '创建/删除用户', type: 'user' },
{ name: '创建/删除角色', type: 'role' },
{ name: '管理策略', type: 'policy' },
{ name: '查看凭证报告', type: 'audit' }
]
},
{
icon: '👥',
name: '普通 IAM 用户',
scope: '受限权限',
scenario: '日常开发人员,只能访问特定资源',
permissions: [
{ name: '只读访问 EC2', type: 'read' },
{ name: '读写指定 S3 桶', type: 'limited' },
{ name: '查看 CloudWatch 日志', type: 'read' },
{ name: '无法创建 IAM 资源', type: 'deny' }
]
},
{
icon: '🎭',
name: '临时角色 (Role)',
scope: '按策略定义',
scenario: '跨账号访问、服务角色、临时授权',
permissions: [
{ name: '临时凭证 (1-12小时)', type: 'temp' },
{ name: '按信任策略授权', type: 'conditional' },
{ name: '可跨账号使用', type: 'cross' },
{ name: '无长期凭证', type: 'secure' }
]
},
{
icon: '🔑',
name: '服务账号 / 应用',
scope: 'API 访问权限',
scenario: '应用程序、CI/CD 流水线、自动化脚本',
permissions: [
{ name: 'AK/SK 或临时凭证', type: 'api' },
{ name: '特定服务 API 权限', type: 'service' },
{ name: '无控制台访问', type: 'programmatic' },
{ name: '建议定期轮换密钥', type: 'security' }
]
}
]
const selectedLevelData = computed(() => {
if (selectedLevel.value === null) return null
return hierarchyLevels[selectedLevel.value]
})
function selectLevel(index) {
selectedLevel.value = index
}
</script>
<style scoped>
.permission-hierarchy-demo {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
}
.demo-desc {
margin: 0;
opacity: 0.9;
font-size: 0.9rem;
}
.hierarchy-container {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 24px;
}
.level-row {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
}
.level-row:hover,
.level-row.active {
background: rgba(255, 255, 255, 0.2);
transform: translateX(8px);
}
.level-icon {
font-size: 1.6rem;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.15);
border-radius: 10px;
}
.level-content {
display: flex;
flex-direction: column;
flex: 1;
}
.level-name {
font-weight: 600;
font-size: 0.95rem;
}
.level-scope {
font-size: 0.75rem;
opacity: 0.8;
}
.permission-badges {
display: flex;
gap: 4px;
flex-wrap: wrap;
justify-content: flex-end;
max-width: 150px;
}
.badge {
padding: 2px 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
font-size: 0.65rem;
white-space: nowrap;
}
.badge.more {
background: rgba(255, 255, 255, 0.4);
}
.detail-panel {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 20px;
color: #333;
}
.detail-panel h5 {
margin: 0 0 16px 0;
font-size: 1.1rem;
color: #667eea;
padding-bottom: 8px;
border-bottom: 2px solid #eee;
}
.detail-section {
margin-bottom: 12px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.detail-section .label {
font-weight: 600;
color: #666;
min-width: 80px;
font-size: 0.85rem;
}
.detail-section .value {
color: #333;
font-size: 0.9rem;
flex: 1;
}
.permissions-grid {
display: flex;
flex-wrap: wrap;
gap: 6px;
flex: 1;
}
.perm-tag {
padding: 4px 10px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 500;
}
.perm-tag.full {
background: #f44336;
color: white;
}
.perm-tag.read,
.perm-tag.user,
.perm-tag.readonly {
background: #4caf50;
color: white;
}
.perm-tag.limited,
.perm-tag.role,
.perm-tag.limited {
background: #ff9800;
color: white;
}
.perm-tag.deny,
.perm-tag.critical {
background: #9c27b0;
color: white;
}
.perm-tag.temp,
.perm-tag.conditional,
.perm-tag.service {
background: #2196f3;
color: white;
}
.perm-tag.admin,
.perm-tag.org,
.perm-tag.billing {
background: #673ab7;
color: white;
}
.perm-tag.api,
.perm-tag.programmatic,
.perm-tag.security {
background: #607d8b;
color: white;
}
.perm-tag.cross,
.perm-tag.secure,
.perm-tag.audit,
.perm-tag.policy {
background: #795548;
color: white;
}
@media (max-width: 768px) {
.level-row {
flex-wrap: wrap;
}
.permission-badges {
width: 100%;
justify-content: flex-start;
max-width: none;
margin-top: 8px;
}
.detail-section {
flex-direction: column;
gap: 4px;
}
}
</style>
@@ -0,0 +1,470 @@
<template>
<div class="role-policy-demo">
<div class="demo-header">
<h4>角色与策略关系可视化</h4>
<p class="demo-desc">拖动查看角色如何关联多个策略</p>
</div>
<div class="visualization-container">
<!-- Central Role -->
<div class="central-role">
<div class="role-core" @click="toggleRoleDetails"
:class="{ expanded: showRoleDetails }">
<div class="role-icon">🎭</div>
<div class="role-info">
<span class="role-name">{{ roleName }}</span>
<span class="role-type">{{ roleType }}</span>
</div>
<div class="expand-icon">{{ showRoleDetails ? '▼' : '▶' }}</div>
</div>
<!-- Trust Policy -->
<div class="trust-policy" v-if="showRoleDetails">
<div class="policy-header">
<span class="policy-icon">🔐</span>
<span class="policy-title">信任策略 (Trust Policy)</span>
</div>
<div class="policy-content">
<div class="policy-item" v-for="(trust, i) in trustPolicy" :key="i">
<span class="principal">{{ trust.principal }}</span>
<span class="action">可执行: {{ trust.action }}</span>
<span class="condition" v-if="trust.condition">条件: {{ trust.condition }}</span>
</div>
</div>
</div>
</div>
<!-- Connection Lines (SVG) -->
<svg class="connection-lines" v-if="mounted">
<line
v-for="(line, index) in connectionLines"
:key="index"
:x1="line.x1"
:y1="line.y1"
:x2="line.x2"
:y2="line.y2"
:class="['connection-line', line.type, { active: hoveredPolicy === line.policyIndex }]"
@mouseenter="hoveredPolicy = line.policyIndex"
@mouseleave="hoveredPolicy = null"
/>
</svg>
<!-- Attached Policies -->
<div class="attached-policies">
<div
v-for="(policy, index) in attachedPolicies"
:key="index"
class="policy-card"
:class="{ active: hoveredPolicy === index, selected: selectedPolicy === index }"
:style="getPolicyPosition(index)"
@mouseenter="hoveredPolicy = index"
@mouseleave="hoveredPolicy = null"
@click="selectPolicy(index)"
>
<div class="policy-header">
<span class="policy-icon">{{ policy.icon }}</span>
<span class="policy-name">{{ policy.name }}</span>
</div>
<div class="policy-permissions" v-if="selectedPolicy === index">
<div class="permission-item" v-for="(perm, i) in policy.permissions" :key="i">
<span class="perm-effect" :class="perm.effect">{{ perm.effect }}</span>
<span class="perm-action">{{ perm.action }}</span>
<span class="perm-resource">{{ perm.resource }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
// Role Data
const roleName = ref('CrossAccountS3AccessRole')
const roleType = ref('跨账号访问角色')
const showRoleDetails = ref(false)
const trustPolicy = ref([
{ principal: '账号 A (123456789012)', action: 'sts:AssumeRole', condition: 'ExternalId 匹配' },
{ principal: '特定 IAM 用户', action: 'sts:AssumeRole', condition: 'IP 白名单' }
])
// Policies Data
const attachedPolicies = ref([
{
name: 'S3ReadWritePolicy',
icon: '📦',
permissions: [
{ effect: 'Allow', action: 's3:GetObject', resource: 'arn:aws:s3:::bucket/*' },
{ effect: 'Allow', action: 's3:PutObject', resource: 'arn:aws:s3:::bucket/*' },
{ effect: 'Allow', action: 's3:ListBucket', resource: 'arn:aws:s3:::bucket' }
]
},
{
name: 'CloudWatchLogsPolicy',
icon: '📊',
permissions: [
{ effect: 'Allow', action: 'logs:CreateLogGroup', resource: '*' },
{ effect: 'Allow', action: 'logs:CreateLogStream', resource: '*' },
{ effect: 'Allow', action: 'logs:PutLogEvents', resource: '*' }
]
},
{
name: 'DenySensitiveData',
icon: '🚫',
permissions: [
{ effect: 'Deny', action: 's3:GetObject', resource: 'arn:aws:s3:::bucket/sensitive/*' },
{ effect: 'Deny', action: 's3:DeleteObject', resource: 'arn:aws:s3:::bucket/*' }
]
}
])
// State
const hoveredPolicy = ref(null)
const selectedPolicy = ref(0)
const mounted = ref(false)
const connectionLines = ref([])
// Methods
function toggleRoleDetails() {
showRoleDetails.value = !showRoleDetails.value
}
function selectPolicy(index) {
selectedPolicy.value = index
}
function selectFeature(platform, index) {
// For compatibility with other demos
}
function getPolicyPosition(index) {
const positions = [
{ top: '0%', right: '0%' },
{ top: '35%', right: '5%' },
{ top: '70%', right: '0%' }
]
return positions[index] || positions[0]
}
function calculateConnections() {
// Simplified connection calculation
connectionLines.value = attachedPolicies.value.map((_, index) => ({
x1: 50,
y1: 50,
x2: 80 + (index * 5),
y2: 20 + (index * 30),
type: index === 2 ? 'deny' : 'allow',
policyIndex: index
}))
}
// Lifecycle
onMounted(() => {
nextTick(() => {
mounted.value = true
calculateConnections()
})
})
onUnmounted(() => {
mounted.value = false
})
</script>
<style scoped>
.role-policy-demo {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
min-height: 600px;
}
.demo-header {
text-align: center;
margin-bottom: 24px;
}
.demo-header h4 {
margin: 0 0 8px 0;
font-size: 1.4rem;
}
.demo-desc {
margin: 0;
opacity: 0.9;
font-size: 0.9rem;
}
.visualization-container {
position: relative;
min-height: 500px;
}
/* Central Role */
.central-role {
position: absolute;
left: 5%;
top: 50%;
transform: translateY(-50%);
width: 280px;
z-index: 10;
}
.role-core {
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
padding: 20px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.role-core:hover {
transform: scale(1.02);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
}
.role-core.expanded {
border-radius: 16px 16px 0 0;
}
.role-icon {
font-size: 2.5rem;
text-align: center;
margin-bottom: 8px;
}
.role-info {
text-align: center;
}
.role-name {
display: block;
color: #333;
font-weight: 700;
font-size: 1rem;
margin-bottom: 4px;
}
.role-type {
display: block;
color: #666;
font-size: 0.8rem;
}
.expand-icon {
text-align: center;
margin-top: 8px;
color: #999;
font-size: 0.8rem;
}
/* Trust Policy */
.trust-policy {
background: rgba(255, 255, 255, 0.95);
border-radius: 0 0 16px 16px;
padding: 16px 20px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.policy-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.policy-icon {
font-size: 1.2rem;
}
.policy-title {
font-weight: 700;
color: #333;
font-size: 0.85rem;
}
.policy-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.policy-item {
background: rgba(102, 126, 234, 0.1);
border-radius: 6px;
padding: 8px;
font-size: 0.75rem;
display: flex;
flex-direction: column;
gap: 2px;
}
.principal {
font-weight: 600;
color: #667eea;
}
.action {
color: #4caf50;
}
.condition {
color: #ff9800;
}
/* Connection Lines SVG */
.connection-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.connection-line {
stroke: rgba(255, 255, 255, 0.3);
stroke-width: 2;
fill: none;
pointer-events: stroke;
cursor: pointer;
transition: all 0.3s ease;
}
.connection-line.allow {
stroke: #4caf50;
}
.connection-line.deny {
stroke: #f44336;
stroke-dasharray: 5, 5;
}
.connection-line:hover,
.connection-line.active {
stroke-width: 4;
opacity: 1;
}
/* Attached Policies */
.attached-policies {
position: absolute;
right: 5%;
top: 50%;
transform: translateY(-50%);
width: 240px;
}
.policy-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
position: relative;
}
.policy-card:hover,
.policy-card.active {
transform: translateX(-8px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}
.policy-card.selected {
border: 2px solid #667eea;
}
.policy-card .policy-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.policy-card .policy-icon {
font-size: 1.4rem;
}
.policy-card .policy-name {
font-weight: 700;
color: #333;
font-size: 0.9rem;
}
.policy-permissions {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.permission-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px;
margin-bottom: 4px;
background: rgba(0, 0, 0, 0.03);
border-radius: 4px;
font-size: 0.7rem;
}
.perm-effect {
padding: 2px 6px;
border-radius: 3px;
font-weight: 600;
font-size: 0.65rem;
text-transform: uppercase;
}
.perm-effect.Allow {
background: #4caf50;
color: white;
}
.perm-effect.Deny {
background: #f44336;
color: white;
}
.perm-action {
font-family: monospace;
color: #667eea;
}
.perm-resource {
color: #999;
font-size: 0.6rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 1024px) {
.visualization-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.central-role,
.attached-policies {
position: static;
transform: none;
width: 100%;
}
.connection-lines {
display: none;
}
}
</style>