Appearance
- cognito 注册/登录/选择租户
- 回调前端url, 前端保存jwt等相关身份令牌
- 前端需要获取资源数据时,需携带jwt等身份令牌信息,发送请求到 Lambda Authorizer
- 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) # 序列化的资源列表
}
}- 通过apigateway将请求转发到后端处理的lambda,例如rsource-api,进行业务数据包装处理, 从event.requestContext.authorizer.resources解析资源列表 进一步处理(如按visibility_rule过滤)后返回
- 前端根据返回的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
详细解释一下这些数据表之间的关系以及分区键的构成。
数据表与键的设计
Resources表
- 分区键:
tenant_id#resource_id - 例如:
tenant001#menu:dashboard、tenant001#button:scene:save - 包含资源的详细信息、UI配置、可见性规则等
- 分区键:
Role_Resources表
- 分区键:
tenant_id#role_id - 例如:
tenant001#admin、tenant001#editor - 包含角色可访问的资源ID列表
- 分区键:
Model_States表
- 分区键:
tenant_id#model_id - 例如:
tenant001#model001 - 记录模型的状态
- 分区键:
键的构成解析
资源ID的格式是: [类型]:[子类型]:[标识符],例如 button:scene:save
- 第1部分(
button): 资源类型,如menu、button、region等 - 第2部分(
scene): 资源所属的功能区域或上下文 - 第3部分(
save): 具体的操作或标识符
这不是表示权限层次,而是资源的标识方式。分号后面的内容是资源ID的一部分,用于描述和区分不同资源。
权限校验流程
认证阶段
- 用户登录时获取JWT令牌,包含
custom:role和custom:tenant_id属性 - 例如: 角色为"admin",租户为"tenant001"
- 用户登录时获取JWT令牌,包含
授权阶段 (Lambda Authorizer)
- 验证JWT令牌
- 从role_resources表查询
tenant001#admin记录 - 获取该角色可访问的资源ID列表
- 将资源列表传递给后端Lambda
资源过滤阶段 (Resource API)
- 根据请求参数(如model_id)进一步过滤资源
- 应用可见性规则(如model.loaded状态)
- 返回用户最终可见的资源
多层级资源ID的处理
对于像button:scene:save这样的多层级资源ID:
整体匹配
- 资源ID是作为整体进行匹配的
- 如果角色有权限访问
tenant001#button:scene:save,那么该角色就可以访问这个资源
父级资源权限
- 如果需要实现继承机制,可以使用
hierarchy_path字段 - 例如,拥有
/scene路径权限的角色可能自动获得所有/scene/下资源的权限
- 如果需要实现继承机制,可以使用
资源绑定
- 通过
model_bindings字段,可以将资源与特定模型关联 - 只有当用户访问该模型时,资源才会显示
- 通过
示例场景
假设用户是"tenant001"租户的"editor"角色:
- 系统从role_resources表获取该角色的资源权限列表
- 列表中包含
tenant001#button:scene:save - 当用户加载model001模型时,系统检查:
- 这个按钮是否与当前模型绑定 (通过model_bindings字段)
- 当前模型状态是否满足显示条件 (通过visibility_rule字段)
- 如果条件满足,前端将显示"保存场景"按钮
这种设计允许非常灵活的权限控制,可以基于角色、租户、模型状态和其他条件来决定用户可以看到和使用哪些资源。
值得注意的是,资源ID的分层结构不是为了表示权限层次,而是为了提供更清晰的资源组织和标识。实际的权限授予是通过role_resources表中的显式列表实现的。
--- 和service集成的流程与细节
lambda 1 resource list
lambda 2 api true false