采集-搭建浏览器端日志直传服务

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

背景信息

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

安全风险

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

企业A计划采取以下方案搭建浏览器日志直传服务:

image

然而,在以上方案中,允许Web应用在浏览器环境直接上传日志到SLS的同时,由于SLS的Logstore开启了支持匿名写入的WebTracking功能,企业将面临以下风险:

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

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

解决方案

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

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

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

企业A最终采取以下方案搭建浏览器日志直传服务:

image

方案部署

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

一键部署

执行以下操作使用资源编排一键部署示例工程。

  1. 单击一键部署链接

  2. 在页面左上角的地域,选择地域,例如华东1(杭州)

  3. 配置参数页签,完成日志服务SLS配置云服务器ECS配置,然后单击下一步

  4. 单击创建

  5. 创建完成后,单击输出页签,然后单击WebTrackingUrl,在页面中输入用户名、选择商品、并点击下单,模拟真实用户在浏览器的行为。

    1.png

完成及清理

方案验证

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

  1. 登录日志服务控制台

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

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

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

    1.png

清理资源

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

  1. 登录资源编排控制台

  2. 在左侧导航栏,单击资源栈

  3. 资源栈列表页面,找到创建的资源栈,然后在其操作列单击删除

手动部署

准备工作

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

  • 创建一个日志服务Project和Logstore,用于存储和分析从浏览器采集的日志。具体操作,请参见创建项目Project创建Logstore

    参数

    示例值

    所属地域

    华东1(杭州)

    Project名称

    web-tracking-project

    Logstore名称

    web-tracking-logstore

  • 创建一台ECS实例作为业务服务器,用于生成临时身份凭证。具体步骤,请参见创建ECS实例

    说明

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

    参数

    示例值

    付费类型

    按量付费

    地域

    华东1(杭州)

    公网 IP

    分配公网 IPv4 地址

    安全组

    开放HTTP (TCP:80)端口

步骤一:创建RAM用户

  1. 使用阿里云账号(主账号)或RAM管理员登录RAM控制台

  2. 创建一个RAM用户。具体操作,请参见创建RAM用户

    重要
    • 创建RAM用户时,请选择访问方式使用永久 AccessKey 访问,并保存AccessKey信息。

    • RAM用户的AccessKey Secret只在创建时显示,不支持查询,请妥善保管,谨防泄露。

步骤二:授权RAM用户授予调用AssumeRole接口

为RAM用户添加AliyunSTSAssumeRoleAccess权限,使其可以通过扮演RAM角色来获取临时身份凭证。具体操作,请参见为RAM用户授权

步骤三:创建RAM角色

创建RAM用户要扮演的RAM角色,例如命名为:sls-web-tracking具体操作,请参见创建可信实体为阿里云账号的RAM角色

步骤四:创建自定义权限策略

创建一个自定义权限策略,例如命名为:post-logs-policy。其中在脚本编辑页签,请使用以下脚本替换配置框中的原有内容。具体操作,请参见通过脚本编辑模式创建自定义权限策略

重要

将以下脚本中的<Project名称><Logstore名称>替换为准备工作中创建的Project名称和Logstore名称。

该策略只能向指定的SLS Logstore上传日志。您可根据实际需求配置更细粒度的授权策略,防止出现权限过大的风险。更多信息,请参见RAM自定义授权示例

{
  "Version":"1",
  "Statement":[
    {
      "Effect":"Allow",
      "Action":[
        "log:PostLogStoreLogs"
      ],
      "Resource":[
        "acs:log:*:*:project/<Project名称>/logstore/<Logstore名称>"
      ]
    }
  ]
}

步骤五:为RAM角色授予权限

为RAM角色sls-web-tracking授予创建的自定义权限post-logs-policy,以便被RAM用户扮演时能获取所需的权限。具体操作,请参见为RAM角色授权

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

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

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

  1. 登录ECS实例。并在ECS实例中安装Python3。

  2. 创建项目目录,并切换到项目目录。

    mkdir my_web_sample
    cd my_web_sample
  3. 安装依赖。

    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
  4. 编写后端代码。

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

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

      重要

      将代码中的<YOUR_ROLE_ARN>替换为步骤三中创建的RAM角色sls-web-tracking的ARN。ARN获取方式请参见查看RAM角色

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

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

      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-hangzhou。
      region_id = 'cn-hangzhou'
      
      @app.route("/")
      def hello_world():
          return render_template('index.html')
      
      @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)
  5. 使用步骤一中创建的RAM用户的访问密钥启动应用程序。

    ALIBABA_CLOUD_ACCESS_KEY_ID=<YOUR_AK_ID> ALIBABA_CLOUD_ACCESS_KEY_SECRET=<YOUR_AK_SECRET> python3 main.py
  6. 在浏览器中访问http://<ECS实例公网IP地址>/get_sts_token

    成功返回示例如下:

    sts token.png

步骤七:在浏览器使用临时身份凭证上传日志到日志服务Logstore

在业务服务器配置了获取STS临时身份凭证的接口后,在Web应用的前端集成SLS的前端埋点SDK(@aliyun-sls/web-track-browser)和STS插件(@aliyun-sls/web-sts-plugin),实现实时监控用户行为并上传相关日志。当创建埋点跟踪器(tracker)实例时,它将自动请求之前部署的/get_sts_token接口,以便获取必要的临时STS凭证。这种机制保证了用户在网站交互过程中,如登录、浏览商品或提交订单等活动的数据会被安全地传输至SLS。

在ECS上,使用Parcel打包工具来编译并打包SLS的前端埋点SDK和STS插件,然后将这些资源集成到Web应用的前端代码的操作示例如下:

  1. Ctrl + C停止应用程序。

  2. 安装npm。

    yum install npm
  3. 安装Parcel。

    npm install -g parcel-bundler
  4. 创建前端项目文件。

    mkdir templates static src
  5. 安装项目依赖。

    npm install --save @aliyun-sls/web-track-browser
    npm install --save @aliyun-sls/web-sts-plugin
  6. 编写前端代码。

    1. src目录中创建一个src/index.js文件。

      vim src/index.js
    2. 在这个文件中,添加以下JavaScript代码。

      import SlsTracker from "@aliyun-sls/web-track-browser";
      import createStsPlugin from "@aliyun-sls/web-sts-plugin";
      
      const opts = {
        host: "cn-hangzhou.log.aliyuncs.com", // 所在地域的服务入口。例如cn-hangzhou.log.aliyuncs.com
        project: "${project}", // Project 名称
        logstore: "${logstore}", // Logstore 名称
        time: 10, // 发送日志的时间间隔,默认是10秒
        count: 10, // 发送日志的数量大小,默认是10条
        topic: "topic", // 自定义日志主题
        source: "source",
        tags: {
          tags: "tags",
        },
      };
      
      const stsOpt = {
        accessKeyId: "",
        accessKeySecret: "",
        securityToken: "",
        // 以下是一个 stsToken 刷新函数的简单示例
        refreshSTSToken: () =>
          new Promise((resolve, reject) => {
            const xhr = new window.XMLHttpRequest();
            xhr.open("GET", "http://<ECS实例公网IP地址>/get_sts_token", true);
            xhr.send();
            xhr.onreadystatechange = () => {
              if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                  let credential = JSON.parse(xhr.response);
                  // 函数的本质目的:设置 stsOpt 的临时密钥和令牌
                  stsOpt.accessKeyId = credential.AccessKeyId;
                  stsOpt.accessKeySecret = credential.AccessKeySecret;
                  stsOpt.securityToken = credential.SecurityToken;
                  resolve();
                } else {
                  reject("Wrong status code.");
                }
              }
            };
          }),
        // refreshSTSTokenInterval: 300000,
        // stsTokenFreshTime: undefined,
      };
      
      const tracker = new SlsTracker(opts);
      // 创建 sts 插件
      const stsPlugin = createStsPlugin(stsOpt);
      // 使用 sts 插件
      tracker.useStsPlugin(stsPlugin);
      
      // 用户登录跟踪
      document.getElementById("loginButton").addEventListener("click", () => {
        const username = document.getElementById("username").value;
        tracker.send({
          eventType: "login",
          username: username,
        });
        console.log("Login event tracked for:", username);
      });
      
      // 商品浏览跟踪
      document.querySelectorAll(".product").forEach((productButton) => {
        productButton.addEventListener("click", (event) => {
          const productName = event.target.getAttribute("data-product-name");
          const productPrice = event.target.getAttribute("data-price");
          tracker.send({
            eventType: "view_product",
            productName: productName,
            price: productPrice,
          });
          console.log("Product view tracked for:", productName);
        });
      });
      
      // 订单提交跟踪
      document.getElementById("orderButton").addEventListener("click", () => {
        tracker.send({
          eventType: "place_order",
          orderDetails: "Order placed for example items",
        });
        console.log("Order placed event tracked");
      });
      

      关于optsstsOpt的参数说明,请参见使用WebTracking JavaScript SDK的STS插件上传日志

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

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

  7. 创建HTML模板文件。

    1. templates目录中创建一个index.html文件。

      vim templates/index.html
    2. 在这个文件中,添加以下HTML代码。

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>WebTracking Static Example</title>
      </head>
      <body>
          <h1>Welcome to Simple Web Store</h1>
          
          <!-- 用户登录 -->
          <div>
              <label for="username">Username:</label>
              <input type="text" id="username" name="username">
              <button id="loginButton">Login</button>
          </div>
          
          <!-- 商品列表 -->
          <div>
              <h2>Products</h2>
              <button class="product" data-product-name="Laptop" data-price="1200">Laptop - $1200</button>
              <button class="product" data-product-name="Smartphone" data-price="800">Smartphone - $800</button>
              <button class="product" data-product-name="Tablet" data-price="500">Tablet - $500</button>
          </div>
          
          <!-- 提交订单 -->
          <div>
              <h2>Your Order</h2>
              <button id="orderButton">Place Order</button>
          </div>
          
          <script type="module" src="{{ url_for('static', filename='js/index.js') }}"></script>
      </body>
      </html>
  8. 在项目根目录my_web_sample下,打包静态资源。

  9. parcel build src/index.js --out-dir static/js --public-url ./js
  10. 使用步骤一获取的访问密钥启动应用程序。

  11. ALIBABA_CLOUD_ACCESS_KEY_ID=<YOUR_AK_ID> ALIBABA_CLOUD_ACCESS_KEY_SECRET=<YOUR_AK_SECRET> python3 main.py
  12. 在浏览器中访问http://<ECS实例公网IP地址>,然后在页面中输入用户名、选择商品、并点击下单,模拟真实用户在浏览器的行为。

    1.png

完成及清理

方案验证

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

  1. 登录日志服务控制台

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

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

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

    1.png

清理资源

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

后续操作

将终端用户的浏览器日志收集到SLS后:

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

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

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