Skip to content
  1. cognito 注册/登录/选择租户
  2. 回调前端url, 前端保存jwt等相关身份令牌
  3. 前端需要获取资源数据时,需携带jwt等身份令牌信息,发送请求到 Lambda Authorizer
  4. Lambda Authorizer 验证JWT:检查签名、有效期、受众(aud)等、查询DynamoDB从role_resources表获取custom:role对应的资源ID列表,返回类似以下结构:
python
   # Lambda Authorizer返回结构
   return {
     "principalId": "user123",
     "policyDocument": { "Effect": "Allow", "Resource": "*" },
     "context": {
   	"resources": json.dumps(filtered_resources)  # 序列化的资源列表
     }
   }
  1. 通过apigateway将请求转发到后端处理的lambda,例如rsource-api,进行业务数据包装处理, 从event.requestContext.authorizer.resources解析资源列表 进一步处理(如按visibility_rule过滤)后返回
  2. 前端根据返回的resources和ui_config渲染组件。

总结:4~5 lambda可以选择合并,之所以分开是为了将鉴权逻辑和返回后端的业务处理逻辑分离,以后在改动和扩展时无需修改鉴权的逻辑,只修改对应的返回逻辑扩展就行。


  • 租户选择中间件Lambda的实现逻辑参考
python
#ambda_function.py
import boto3
import json
from urllib.parse import urlencode

def lambda_handler(event, context):
    # 1. 验证JWT有效性(使用aws-jwt-verify库)
    token = json.loads(event['body'])['token']
    claims = verify_jwt(token)  # 实现需替换为实际验证逻辑
    
    # 2. 查询DynamoDB验证邀请码
    invite_code = json.loads(event['body'])['invite_code']
    tenant = boto3.client('dynamodb').get_item(
        TableName='tenants',
        Key={'invite_code': {'S': invite_code}},
        ProjectionExpression='tenant_id, frontend_url'
    ).get('Item')
    
    if not tenant:
        return {'statusCode': 400, 'body': 'Invalid invite code'}
    
    # 3. 更新Cognito用户属性
    cognito = boto3.client('cognito-idp')
    cognito.admin_update_user_attributes(
        UserPoolId=claims['iss'].split('/')[-1],
        Username=claims['sub'],
        UserAttributes=[{'Name': 'custom:tenant_id', 'Value': tenant['tenant_id']['S']}]
    )
    
    # 4. 返回重定向响应
    return {
        'statusCode': 302,
        'headers': {
            'Location': tenant['frontend_url']['S'] + '?' + urlencode({'token': token}),
            'Cache-Control': 'no-cache'
        }
    }
  • 验证登录之后的JWT有效性(使用aws-jwt-verify库)
  • 查询DynamoDB验证页面输入的租户验证码或者识别码
  • 更新Cognito用户属性
  • 确认租户信息后,返回重定向响应跳转到具体的业务service

详细解释一下这些数据表之间的关系以及分区键的构成。

数据表与键的设计

  1. Resources表

    • 分区键: tenant_id#resource_id
    • 例如: tenant001#menu:dashboardtenant001#button:scene:save
    • 包含资源的详细信息、UI配置、可见性规则等
  2. Role_Resources表

    • 分区键: tenant_id#role_id
    • 例如: tenant001#admintenant001#editor
    • 包含角色可访问的资源ID列表
  3. Model_States表

    • 分区键: tenant_id#model_id
    • 例如: tenant001#model001
    • 记录模型的状态

键的构成解析

资源ID的格式是: [类型]:[子类型]:[标识符],例如 button:scene:save

  • 第1部分(button): 资源类型,如menu、button、region等
  • 第2部分(scene): 资源所属的功能区域或上下文
  • 第3部分(save): 具体的操作或标识符

这不是表示权限层次,而是资源的标识方式。分号后面的内容是资源ID的一部分,用于描述和区分不同资源。

权限校验流程

  1. 认证阶段

    • 用户登录时获取JWT令牌,包含custom:rolecustom:tenant_id属性
    • 例如: 角色为"admin",租户为"tenant001"
  2. 授权阶段 (Lambda Authorizer)

    • 验证JWT令牌
    • 从role_resources表查询tenant001#admin记录
    • 获取该角色可访问的资源ID列表
    • 将资源列表传递给后端Lambda
  3. 资源过滤阶段 (Resource API)

    • 根据请求参数(如model_id)进一步过滤资源
    • 应用可见性规则(如model.loaded状态)
    • 返回用户最终可见的资源

多层级资源ID的处理

对于像button:scene:save这样的多层级资源ID:

  1. 整体匹配

    • 资源ID是作为整体进行匹配的
    • 如果角色有权限访问tenant001#button:scene:save,那么该角色就可以访问这个资源
  2. 父级资源权限

    • 如果需要实现继承机制,可以使用hierarchy_path字段
    • 例如,拥有/scene路径权限的角色可能自动获得所有/scene/下资源的权限
  3. 资源绑定

    • 通过model_bindings字段,可以将资源与特定模型关联
    • 只有当用户访问该模型时,资源才会显示

示例场景

假设用户是"tenant001"租户的"editor"角色:

  1. 系统从role_resources表获取该角色的资源权限列表
  2. 列表中包含tenant001#button:scene:save
  3. 当用户加载model001模型时,系统检查:
    • 这个按钮是否与当前模型绑定 (通过model_bindings字段)
    • 当前模型状态是否满足显示条件 (通过visibility_rule字段)
  4. 如果条件满足,前端将显示"保存场景"按钮

这种设计允许非常灵活的权限控制,可以基于角色、租户、模型状态和其他条件来决定用户可以看到和使用哪些资源。

值得注意的是,资源ID的分层结构不是为了表示权限层次,而是为了提供更清晰的资源组织和标识。实际的权限授予是通过role_resources表中的显式列表实现的。

--- 和service集成的流程与细节

lambda 1 resource list

lambda 2 api true false