采集-搭建小程序端日志直传服务

为了深入洞察用户行为,企业通常会从小程序中收集小程序日志,分析用户的设备信息、浏览历史等关键数据,进而改善产品功能并优化用户体验。通过启用阿里云日志服务(SLS)的Web Tracking功能,企业能自动将终端用户的小程序日志收集到SLS,既提高了数据处理效率,又减轻了业务服务器的负担。尽管便捷,但由于允许Logstore支持匿名写入,如小程序中的配置信息泄露可能引致数据污染。为了降低该风险,推荐使用阿里云安全令牌服务(STS),为小程序端提供时间和权限受限的访问令牌,从而增强数据上传过程的安全性。本文将以用户浏览行为日志收集为例,介绍如何利用SLS和STS为小程序构建安全、高效的小程序日志直传服务。

背景信息

企业A开发的这款小程序为用户提供了一个功能丰富、界面友好的在线平台,旨在吸引用户前来浏览、购买商品或服务。为了更好地理解用户的需求、优化用户体验,并进一步提升小程序的转化率,企业A认识到了对用户行为进行深入分析的重要性。这种分析需要基于用户在小程序上的实际操作行为,比如点击、滑动、停留、搜索习惯以及购买行为等。因此,企业A的技术团队决定采集小程序端日志,并将这些日志上传到SLS进行更高效的数据分析和处理。

安全风险

为了减轻业务服务器的负担并提高处理效率,该小程序被设计为允许用户的日志数据直接上传到SLS,避免了日志采集与上传过程中经过业务服务器的中转。

企业A计划采取以下方案搭建小程序日志直传服务:

image

然而,在以上方案中,允许小程序直接上传日志到SLS的同时,由于SLS的Logstore开启了支持匿名写入的Web Tracking功能,企业将面临以下风险:

  • 数据污染风险:恶意用户可能会上传虚假或蓄意破坏的数据,影响数据质量和分析结果的准确性。

  • 服务滥用风险:未受限的写入访问可能会被利用进行拒绝服务(DoS)攻击,通过大量写入请求耗尽资源。

解决方案

为了应对上述风险,企业在原有方案的基础上增加临时授权。通过这种方式,企业A能够在确保数据直传效率的同时,实现以下效果:

  • 增强的身份验证和授权:通过STS生成的具有时间限制的令牌,即便在短时间内泄露,也极大降低了安全风险。因为这些凭证很快就会失效,降低了被不当利用的可能性。

  • 精细化权限控制:STS允许根据最小权限原则配置权限,仅授权小程序必需的访问权限。这种精细化的权限控制方法限制了潜在泄露的影响范围,防止了过度权限的风险。

企业A最终采取以下方案搭建小程序日志直传服务:

image

方案部署

下面将以一个简单的用户浏览行为日志收集场景为例,引导您一步步使用SLS和STS为小程序部署小程序日志直传服务。本方案部署的示例工程:sls-mini-tracking-sts.zip

手动部署

准备工作

在开始部署前,您需要先创建云资源。

  • 创建一个SLS Project和Logstore,用于存储和分析从小程序采集的日志。

    参数

    示例值

    所属地域

    华东2(上海)

    Project名称

    sls-webtracking-mini

    Logstore名称

    web-tracking-logstore

    具体步骤,请参见步骤二:创建Project和Logstore

  • 创建一台ECS实例作为业务服务器,用于生成临时身份凭证。

    说明

    在实际部署时,您可以将调用STS服务的接口集成到自己的业务服务器的接口中,而无需创建该ECS实例。

    参数

    示例值

    付费类型

    按量付费

    地域

    华东2(上海)

    公网 IP

    分配公网 IPv4 地址

    安全组

    开放HTTP (TCP:80)端口

    具体步骤,请参见通过控制台使用ECS实例(快捷版)

部署步骤

执行以下操作手动部署示例工程。

步骤一:在访问控制创建RAM用户

首先,创建一个调用方式为OpenAPI调用的RAM用户,并获取对应的访问密钥,作为业务服务器的应用程序的长期身份凭证。

  1. 使用云账号或账号管理员登录RAM控制台

  2. 在左侧导航栏,选择身份管理 > 用户

  3. 单击创建用户

  4. 输入登录名称显示名称

  5. 调用方式区域下,选择OpenAPI调用,然后单击确定

  6. 单击操作下的复制,保存访问密钥(AccessKey ID和AccessKey Secret)。

步骤二:在访问控制为RAM用户授予调用AssumeRole接口的权限

创建RAM用户后,需要授予RAM用户调用STS服务的AssumeRole接口的权限,使其可以通过扮演RAM角色来获取临时身份凭证。

  1. 在左侧导航栏,选择身份管理 > 用户

  2. 用户页面,找到目标RAM用户,然后单击RAM用户右侧的添加权限

  3. 新增授权页面,选择AliyunSTSAssumeRoleAccess系统策略。

    说明

    授予RAM用户调用STS服务AssumeRole接口的固定权限是AliyunSTSAssumeRoleAccess,与后续获取临时身份凭证以及通过临时身份凭证发起SLS请求所需权限无关。

  4. 单击确认新增授权

步骤三:在访问控制创建RAM角色

创建RAM用户要扮演的RAM角色,并获取对应的角色的ARN(Aliyun Resource Name,阿里云资源名称),用于之后的角色扮演。

  1. 在左侧导航栏,选择身份管理 > 角色

  2. 单击创建角色,可信实体类型选择阿里云账号,单击下一步

  3. 填写角色名称,选择当前云账号

  4. 单击完成。完成角色创建后,单击关闭

  5. 在RAM角色管理页面,搜索框输入角色名称,例如sls-web-tracking

  6. 单击复制,保存角色的ARN。

    2024-05-09_17-45-50.png

步骤四:在访问控制创建自定义权限策略

按照最小授权原则,为RAM角色创建一个自定义权限策略,限制只能向指定的SLS Logstore上传日志。

  1. 在左侧导航栏,选择权限管理 > 权限策略

  2. 单击创建权限策略

  3. 创建权限策略页面,单击脚本编辑,将以下脚本中的<Project名称><Logstore名称>替换为准备工作中创建的Project名称和Logstore名称。

    重要

    以下示例仅供参考。您需要根据实际需求配置更细粒度的授权策略,防止出现权限过大的风险。关于更细粒度的授权策略配置详情,请参见RAM自定义授权场景

    {
      "Version":"1",
      "Statement":[
        {
          "Effect":"Allow",
          "Action":[
            "log:PostLogStoreLogs"
          ],
          "Resource":[
            "acs:log:*:*:project/<Project名称>/logstore/<Logstore名称>"
          ]
        }
      ]
    }
  4. 策略配置完成后,单击继续编辑基本信息

  5. 基本信息区域,填写策略名称,然后单击确定

步骤五:在访问控制为RAM角色授予权限

为RAM角色授予权限,以便该RAM角色被RAM用户扮演时能获取所需的权限。

  1. 在左侧导航栏,选择身份管理 > 角色

  2. 角色页面,找到目标RAM角色,然后单击RAM角色右侧的新增授权

  3. 新增授权页面下的自定义策略页签,选择已创建的自定义权限策略。

  4. 单击确认新增授权

步骤六:在业务服务器获取临时身份凭证

在小程序中,通过在业务服务器集成STS SDK,实现一个获取临时STS身份凭证的接口。当这个接口(/get_sts_token)通过HTTP GET方法被访问时,它会生成一个临时身份凭证,并将其返回给请求者。

在ECS实例上,使用Flask框架快速搭建小程序,实现一个获取临时STS身份凭证的接口的操作示例如下:

  1. 连接ECS实例。

    具体操作,请参见通过控制台使用ECS实例(快捷版)

  2. 安装Python3

  3. 配置RAM用户的ALIBABA_CLOUD_ACCESS_KEY_IDALIBABA_CLOUD_ACCESS_KEY_SECRET,操作步骤请参见在Linux、macOS和Windows系统配置环境变量

  4. 创建项目文件夹,然后切换到项目目录。

    mkdir my_web_sample
    cd my_web_sample
  5. 安装依赖。

    pip3 install Flask
    pip3 install attr
    pip3 install yarl
    pip3 install async_timeout
    pip3 install idna_ssl
    pip3 install attrs
    pip3 install aiosignal
    pip3 install charset_normalizer
    pip3 install alibabacloud_tea_openapi
    pip3 install alibabacloud_sts20150401
    pip3 install alibabacloud_credentials
  6. 编写后端代码。

    1. 创建一个main.py文件。

    2. 在这个文件中,添加以下Python代码。

      import json
      from flask import Flask, render_template
      from alibabacloud_tea_openapi.models import Config
      from alibabacloud_sts20150401.client import Client as Sts20150401Client
      from alibabacloud_sts20150401 import models as sts_20150401_models
      from alibabacloud_credentials.client import Client as CredentialClient
      
      app = Flask(__name__)
      
      # 将<YOUR_ROLE_ARN>替换为RAM角色的ARN。
      role_arn_for_oss_upload = '<YOUR_ROLE_ARN>'
      # 设置为STS服务的地域,例如cn-shanghai。
      region_id = 'cn-shanghai'
      
      @app.route('/get_sts_token', methods=['GET'])
      def get_sts_token():
          # 初始化 CredentialClient 时不指定参数,代表使用默认凭据链。
          # 在本地运行程序时,可以通过环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID、ALIBABA_CLOUD_ACCESS_KEY_SECRET 指定 AK;
          # 在 ECS\ECI\容器服务上运行时,可以通过环境变量 ALIBABA_CLOUD_ECS_METADATA 来指定绑定的实例节点角色,SDK 会自动换取 STS 临时凭证。
          config = Config(region_id=region_id, credential=CredentialClient())
          sts_client = Sts20150401Client(config=config)
          assume_role_request = sts_20150401_models.AssumeRoleRequest(
              role_arn=role_arn_for_oss_upload,
              # 将<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称。
              role_session_name='<YOUR_ROLE_SESSION_NAME>'
          )
          response = sts_client.assume_role(assume_role_request)
          token = json.dumps(response.body.credentials.to_map())
          return token
      
      
      app.run(host="0.0.0.0", port=80)
    3. 将代码中的<YOUR_ROLE_ARN>替换为步骤三获取的角色ARN。

    4. 将代码中的<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称,例如role_session_test

  7. 启动应用程序。

    python3 main.py
  8. 在浏览器中访问http://<ECS实例公网IP地址>/get_sts_token

    成功返回示例如下:

    sts token.png

步骤七:在小程序使用临时身份凭证上传日志到SLS

重要

小程序的开发流程和代码构成,请参见支付宝文档中心。本文档的小程序客户端代码,基于支付宝小程序的Todo示例和日志服务的使用WebTracking JavaScript SDK的STS插件上传日志,示例代码的下载地址sls-mini-tracking-sts.zip

在业务服务器配置了获取STS临时身份凭证的接口后,在小程序的app.js文件中集成SLS的埋点SDK(@aliyun-sls/web-track-mini)和STS插件(@aliyun-sls/web-sts-plugin),实现实时监控用户行为并上传相关日志。

当创建埋点跟踪器(tracker)实例时,它将自动请求之前部署的/get_sts_token接口,以便获取必要的临时STS凭证。这种机制保证了用户在小程序交互过程中,如登录、浏览商品或提交订单等活动的数据会被安全地传输至SLS。

  1. 下载小程序开发者工具

  2. 创建和体验Todo App 小程序

  3. 安装npm

  4. 安装项目依赖。

    npm install --save @aliyun-sls/web-track-mini
    npm install --save @aliyun-sls/web-sts-plugin
  5. 修改Todo示例代码,将用户数据通过Webtracking上传到Logstore。修改后的示例代码sls-mini-tracking-sts.zip,修改的文件如下。

    文件

    修改代码的说明

    app.js

    • onLaunch方法中调用了initSlsTracker 方法,当小程序启动时会初始化SlsTracker。

    • initSlsTracker 方法中定义日志服务的Project、Logstore等参数,参数说明参见webtracking参数名称

    • stsOpt配置和refreshSTSToken函数用于动态获取和更新阿里云的安全令牌服务(Security Token Service,简称 STS)的临时访问凭证。STS相关参数说明参见STS参数名称

    mini.project.json

    STS插件(@aliyun-sls/web-sts-plugin)包含ES6特性,在mini.project.json文件中设置编译选项 compileOptions.transpiletrue,让小程序的开发工具将 ES6 代码转换为ES5代码,确保兼容性。

    todo.xml

    <view class="todo-footer">
        <add-button text="Add Todo" onClickMe="handleAddTodo"></add-button>
      </view>

    定义了用户界面底部的一个按钮,界面词为Add Todoadd-button是自定义按钮。onClickMe="handleAddTodo"代表按钮被单击时调用todo.js中的方法handleAddTodo

    todo.js

    • handleAddTodo()是事件处理函数。当用户在页面上添加新的待办事项时,这个函数被触发。它依次执行两个操作:

      1. 调用this.addTodo()方法添加一个新的待办事项。

      2. 调用this.onAddTodoButtonClick()发送关于用户点击添加按钮的日志消息。

    • addTodo()调用小程序的 my.navigateTo 函数,实现从当前页面跳转到应用内 /pages/add-todo/add-todo 路径对应的页面。

    • onAddTodoButtonClick()调用app.js中定义的SlsTrackersend方法发送一条日志事件,这条日志的内容为 eventType: 'addtodo_click'

    add-todo.js

    add方法中

    • 创建一个新对象newTodo,代表一个待办事项,newTodo包括两个属性:

      • text:待办事项的内容,这里从this.data.inputValue获取,意味着它可能是来自用户输入。

      • completed:表示这个待办事项是否已完成,初始化为false,意味着新建的待办事项默认状态是未完成。

    • 调用app.js中定义的SlsTrackersend方法发送一条日志事件,这条日志的内容为

      eventType: 'add_todo'
      todoText: newTodo.text

    add-button.js

    自定义按钮组件。onClickMe方法首先检查this.props.onClickMe是否为一个函数类型。只有当确定this.props.onClickMe是函数时,才执行调用。

  6. 配置小程序app.js文件的参数。

    // app.js
    import SlsTracker from '@aliyun-sls/web-track-mini';
    import createStsPlugin from '@aliyun-sls/web-sts-plugin';
    
    App({
      todos: [
        { text: 'Learning Javascript', completed: true },
        { text: 'Learning ES2016', completed: true },
        { text: 'Learning 支付宝小程序', completed: false },
      ],
      userInfo: null,
      tracker: null,
    
      onLaunch: function() {
        // 在应用启动时进行初始化
        this.initSlsTracker();
      },
    
      initSlsTracker: function() {
        const opts = {
          host: 'cn-shanghai.log.aliyuncs.com', // 替换为你的服务入口
          project: '${project}', // 替换为你的 Project 名称
          logstore: '${Logstore}', // 替换为你的 Logstore 名称
          time: 10,
          count: 10,
          topic: 'topic',
          source: 'source',
          tags: {
            tags: 'tags',
          },
        };
    
        const stsOpt = {
          accessKeyId: '',
          accessKeySecret: '',
          securityToken: '',
          refreshSTSToken: () => new Promise((resolve, reject) => {
            my.request({
              url: 'http://${ECS实例的公网IP}}/get_sts_token', // 替换为后端 STS Token 提供服务的真实地址
              method: 'GET',
              success: (res) => {
                if (res.statusCode === 200) {
                  let credential;
    
                  // 检查返回的数据类型确保是字符串,若不是字符串则可能已是对象
                  if (typeof res.data === "string") {
                    //解析为JSON对象
                    credential = JSON.parse(res.data);
                  } else {
                    // 直接使用对象
                    credential = res.data;
                  }
                  stsOpt.accessKeyId = credential.AccessKeyId;
                  stsOpt.accessKeySecret = credential.AccessKeySecret;
                  stsOpt.securityToken = credential.SecurityToken;
                  resolve(credential);
                } else {
                  reject(new Error('Failed to refresh STS token with status code: ' + res.statusCode));
                }
              },
              fail: (err) => {
                reject(new Error('Failed to refresh STS token', err));
              },
            });
          }),
        };
    
        const tracker = new SlsTracker(opts);
        const stsPlugin = createStsPlugin(stsOpt);
        tracker.useStsPlugin(stsPlugin);
    
        // 以单条日志上传为例,只要启动小程序,就会发送一条日志。
        // tracker.send({
        //   eventType:'view_product',
        //   productName: 'Tablet',
        //   price: 500  
        // });
    
    
        this.tracker = tracker;
      },
    
      
    });
    
    1. 关于optsstsOpt的参数说明,请参见使用WebTracking JavaScript SDK的STS插件上传日志

    2. 将代码中的${project}${logstore}替换为准备工作中创建的Project名称和Logstore名称。

    3. 将代码中的<ECS实例公网IP地址>替换为准备工作中创建的ECS实例的公网IP地址。如何查看公网IP地址,请参见查看IP地址

  7. 运行客户端示例代码。在开发者工具,单击页面右上角的编译image。在右侧小程序预览页面,单击小程序的Todo页面的Add Todo按钮或输入Todo项时,小程序会直接向日志服务的Logstore上传日志,无需经过服务器。

    image

完成及清理

方案验证

完成以上操作后,您可以通过预览方式查看数据是否已上传到SLS。

  1. 登录日志服务控制台

  2. 在Project列表区域,单击目标Project。

  3. 在日志库中,选择目标Logstore右侧的修改日志库图标 >消费预览

  4. 消费预览面板,查看成功上传的日志。

    image

清理资源

在本方案中,您创建了1台ECS实例、1个SLS Project和Logstore、1个RAM用户和1个RAM角色。测试完方案后,您可以参考以下规则处理对应产品的资源,避免继续产生费用或产生安全风险。

后续操作

  • 本文在开发者工具中对示例程序进行本地调试,如果需要正式上线小程序,必须完成以下步骤:

    1. 创建开发者账号,创建小程序,将小程序提交审核等,具体参见开发者账号注册

    2. 在开发者工具中忽略域名合法性,检查便于调试,步骤请参见接入准备

      image

    3. 支付宝控制台中配置以下的域名白名单。

      1. 添加日志服务的服务器域名(request合法域名)。格式为https://${project}.${host}project是日志服务的Project名称,host是Project域名,本文示例为sls-webtracking-mini.cn-shanghai.log.aliyuncs.com,操作步骤参见服务器域名白名单

        image

      2. 添加小程序后端服务器的域名,本文示例只有小程序客户端代码,正式上线小程序还需要开发服务端代码。服务器域名白名单只支持HTTPS协议,配置HTTPS协议需要在服务器部署SSL证书,步骤请参见SSL证书安装指南。本文示例在开发者工具中忽略域名合法性,小程序正式上线前必须配置域名检查。

  • 将终端用户的小程序日志收集到SLS后:

    • 您可以对日志进行查询和分析,深入了解用户的行为。具体操作,请参见查询和分析日志

    • 您可以使用仪表盘、报表、第三方可视化工具展示查询和分析结果。具体操作,请参见可视化概述

    • 您可以分析用户行为日志来检测潜在的安全威胁,如登录失败尝试、异常的访问模式、疑似数据泄露活动等。一旦检测到这类行为,立即触发告警,安全团队可以迅速采取行动。具体操作,请参见什么是日志服务告警