Umami.Net和Bot探测 (中文 (Chinese Simplified))

Umami.Net和Bot探测

Comments

NOTE: Apart from English (and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.

Friday, 13 September 2024

//

5 minute read

一. 导言 导言 导言 导言 导言 导言 一,导言 导言 导言 导言 导言 导言

所以我已经 张贴的LOT 过去曾使用Umami在自托管的环境中进行分析, Umami. Net Nuget Pakakge 网.. 然而,我有一个问题,我想追踪我的简易新闻聚合(RSS)的用户;这篇文章谈到我为什么以及如何解决这个问题。

[技选委

问题

问题在于 RSS 种子阅读器试图通过 有用 用户代理在请求输入时使用 。 这样可以 遵守情况 提供方跟踪用户数量和用户使用种子的类型。 然而,这也意味着Umami将确定这些请求为: 立方体体 请求。 这是一个供我使用的问题,因为它导致请求被忽视,没有跟踪。

Feedbin 用户代理器看起来是这样 :

Feedbin feed-id:1234 - 21 subscribers

它传达了一些有用的细节 说明你的种子识别码是什么 用户和用户代理的数量 然而,这也是个问题, 因为它意味着Umami会无视请求; 事实上它会返回200个状态, 但内容包含 {"beep": "boop"} 意思是,这被确定为机器人请求。 这很烦人,因为我无法通过正常的错误处理(这是200美元,不是403美元等)来解决这个问题。

当我们在做出这一修改后查看Umami Dashboard时,

Umami 仪表板

所以我们可以看到现在有一群人 触发了机器人检测 但我们现在能看到这些 追踪请求

解决方案

那么,有什么解决办法呢? 我无法手动分析所有这些请求, 检测Umami是否检测为机器人; 它使用IsBot(https://www.npmjs.com/package/isbot)检测请求是否为机器人。 我甚至不能使用这份名单(将来我可能会变得聪明, 用这份名单来检测请求是否为机器人)。 所以,我需要拦截请求 之前,它到达乌马米 并改变用户代理 将U马米会接受的东西 对于具体的请求。

现在,我在Umami.Net的追踪方法中增加了一些额外的参数。 这些允许您指定新的“ Default 用户代理” 将发送到 Umami, 而不是原用户代理 。 这使我能够具体说明,用户代理应修改为具体请求的具体价值。

方法和方法

在我的上 UmamiBackgroundSender 我补充了以下内容:

   public async Task TrackPageView(string url, string title, UmamiPayload? payload = null,
        UmamiEventData? eventData = null, bool useDefaultUserAgent = false)

它存在于所有的跟踪方法上 仅设置一个参数 UmamiPayload 对象。

日期 于 UmamiClient 这些标准可规定如下:

    [Fact]
    public async Task TrackPageView_WithUrl()
    {
        var handler = JwtResponseHandler.Create();
        var umamiClient = GetServices(handler);
        var response = await umamiClient.TrackPageViewAndDecode("https://example.com", "Example Page",
            new UmamiPayload { UseDefaultUserAgent = true });
        Assert.NotNull(response);
        Assert.Equal(UmamiDataResponse.ResponseStatus.Success, response.Status);
    }

在这个测试中,我用的是新的 TrackPageViewAndDecode 返回 UmamiDataResponse 对象。 此对象包含已解码的 JWT 质象( 如果是机器人, 无效) 和请求状态 。

PayloadService

这一切都是处理在 Payload 负责装载有效载荷物体的服务 这里就是 UseDefaultUserAgent 设置了。

默认情况下I 将有效载荷从 HttpContext 所以通常你会得到正确的设置; 我稍后会显示这是从Umami拿回来的。

    private UmamiPayload GetPayload(string? url = null, UmamiEventData? data = null)
    {
        var httpContext = httpContextAccessor.HttpContext;
        var request = httpContext?.Request;

        var payload = new UmamiPayload
        {
            Website = settings.WebsiteId,
            Data = data,
            Url = url ?? httpContext?.Request?.Path.Value,
            IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString(),
            UserAgent = request?.Headers["User-Agent"].FirstOrDefault(),
            Referrer = request?.Headers["Referer"].FirstOrDefault(),
            Hostname = request?.Host.Host
        };

        return payload;
    }

然后,我有一个代码 叫做 PopulateFromPayload 这是请求对象获得数据的地方:

    public static string DefaultUserAgent =>
        $"Mozilla/5.0 (Windows 11)  Umami.Net/{Assembly.GetAssembly(typeof(UmamiClient))!.GetName().Version}";

    public UmamiPayload PopulateFromPayload(UmamiPayload? payload, UmamiEventData? data)
    {
        var newPayload = GetPayload(data: data);
        ...
        
        newPayload.UserAgent = payload.UserAgent ?? DefaultUserAgent;

        if (payload.UseDefaultUserAgent)
        {
            var userData = newPayload.Data ?? new UmamiEventData();
            userData.TryAdd("OriginalUserAgent", newPayload.UserAgent ?? "");
            newPayload.UserAgent = DefaultUserAgent;
            newPayload.Data = userData;
        }


        logger.LogInformation("Using UserAgent: {UserAgent}", newPayload.UserAgent);
     }        
        

你会看到,这定义了一个新的用户代理器 在文件的顶部(我证实这不是 目前 被检测为机器人)。 然后在方法中,它检测用户代理器是否无效(除非没有 HttpContext 的代码被调用,否则不应发生),或者如果 UseDefaultUserAgent 设置了。 如果是,则将用户代理设定为默认值,并将原始用户代理添加到数据对象中。

然后将此记录下来, 这样您就可以看到用户代理正在使用什么 。

解码反应。

在Umami.Net 0.3.0中,我添加了一些新的“十二月”方法,这些方法返回了 UmamiDataResponse 对象。 此对象包含已解码的 JWT 符号 。

    public async Task<UmamiDataResponse?> TrackPageViewAndDecode(
        string? url = "",
        string? title = "",
        UmamiPayload? payload = null,
        UmamiEventData? eventData = null)
    {
        var response = await TrackPageView(url, title, payload, eventData);
        return await DecodeResponse(response);
    }
    
        private async Task<UmamiDataResponse?> DecodeResponse(HttpResponseMessage responseMessage)
    {
        var responseString = await responseMessage.Content.ReadAsStringAsync();

        switch (responseMessage.IsSuccessStatusCode)
        {
            case false:
                return new UmamiDataResponse(UmamiDataResponse.ResponseStatus.Failed);
            case true when responseString.Contains("beep") && responseString.Contains("boop"):
                logger.LogWarning("Bot detected data not stored in Umami");
                return new UmamiDataResponse(UmamiDataResponse.ResponseStatus.BotDetected);

            case true:
                var decoded = await jwtDecoder.DecodeResponse(responseString);
                if (decoded == null)
                {
                    logger.LogError("Failed to decode response from Umami");
                    return null;
                }

                var payload = UmamiDataResponse.Decode(decoded);

                return payload;
        }
    }

你可以看到,这要求 进入正常 TrackPageView 方法,然后调用一种叫做方法的方法 DecodeResponse 用于检查应答的 beepboop 字符串( 用于检测机器人) 。 如果发现它们,它会记录警告并返回 BotDetected 状态。 如果找不到它们,它会解码JWT标志 并返回有效载荷。

JWT标志本身只是一个Base64编码的字符串,其中载有Umami储存的数据。 编码解码, 以 a 格式返回 UmamiDataResponse 对象。

完整的资料来源如下:

Response Decoder ```csharp using System.IdentityModel.Tokens.Jwt;

namespace Umami.Net.Models;

public class UmamiDataResponse { public enum ResponseStatus { Failed, BotDetected, Success }

public UmamiDataResponse(ResponseStatus status)
{
    Status = status;
}

public ResponseStatus Status { get; set; }

public Guid Id { get; set; }
public Guid WebsiteId { get; set; }
public string? Hostname { get; set; }
public string? Browser { get; set; }
public string? Os { get; set; }
public string? Device { get; set; }
public string? Screen { get; set; }
public string? Language { get; set; }
public string? Country { get; set; }
public string? Subdivision1 { get; set; }
public string? Subdivision2 { get; set; }
public string? City { get; set; }
public DateTime CreatedAt { get; set; }
public Guid VisitId { get; set; }
public long Iat { get; set; }

public static UmamiDataResponse Decode(JwtPayload? payload)
{
    if (payload == null) return new UmamiDataResponse(ResponseStatus.Failed);
    payload.TryGetValue("visitId", out var visitIdObj);
    payload.TryGetValue("iat", out var iatObj);
    //This should only happen then the payload is dummy.
    if (payload.Count == 2)
    {
        var visitId = visitIdObj != null ? Guid.Parse(visitIdObj.ToString()!) : Guid.Empty;
        var iat = iatObj != null ? long.Parse(iatObj.ToString()!) : 0;

        return new UmamiDataResponse(ResponseStatus.Success)
        {
            VisitId = visitId,
            Iat = iat
        };
    }

    payload.TryGetValue("id", out var idObj);
    payload.TryGetValue("websiteId", out var websiteIdObj);
    payload.TryGetValue("hostname", out var hostnameObj);
    payload.TryGetValue("browser", out var browserObj);
    payload.TryGetValue("os", out var osObj);
    payload.TryGetValue("device", out var deviceObj);
    payload.TryGetValue("screen", out var screenObj);
    payload.TryGetValue("language", out var languageObj);
    payload.TryGetValue("country", out var countryObj);
    payload.TryGetValue("subdivision1", out var subdivision1Obj);
    payload.TryGetValue("subdivision2", out var subdivision2Obj);
    payload.TryGetValue("city", out var cityObj);
    payload.TryGetValue("createdAt", out var createdAtObj);

    return new UmamiDataResponse(ResponseStatus.Success)
    {
        Id = idObj != null ? Guid.Parse(idObj.ToString()!) : Guid.Empty,
        WebsiteId = websiteIdObj != null ? Guid.Parse(websiteIdObj.ToString()!) : Guid.Empty,
        Hostname = hostnameObj?.ToString(),
        Browser = browserObj?.ToString(),
        Os = osObj?.ToString(),
        Device = deviceObj?.ToString(),
        Screen = screenObj?.ToString(),
        Language = languageObj?.ToString(),
        Country = countryObj?.ToString(),
        Subdivision1 = subdivision1Obj?.ToString(),
        Subdivision2 = subdivision2Obj?.ToString(),
        City = cityObj?.ToString(),
        CreatedAt = createdAtObj != null ? DateTime.Parse(createdAtObj.ToString()!) : DateTime.MinValue,
        VisitId = visitIdObj != null ? Guid.Parse(visitIdObj.ToString()!) : Guid.Empty,
        Iat = iatObj != null ? long.Parse(iatObj.ToString()!) : 0
    };
}

}


</details>
你可以看到,这里面有一堆有用的信息 关于Umami存储的请求。 如果您想要显示基于语区、语言、浏览器等的不同内容, 您可以这样做 。

```csharp
    public Guid Id { get; set; }
    public Guid WebsiteId { get; set; }
    public string? Hostname { get; set; }
    public string? Browser { get; set; }
    public string? Os { get; set; }
    public string? Device { get; set; }
    public string? Screen { get; set; }
    public string? Language { get; set; }
    public string? Country { get; set; }
    public string? Subdivision1 { get; set; }
    public string? Subdivision2 { get; set; }
    public string? City { get; set; }
    public DateTime CreatedAt { get; set; }
    public Guid VisitId { get; set; }
    public long Iat { get; set; }

在结论结论中

所以只要一个短位, 覆盖 Umami. Net 0. 0. 0. 0. 0. 0 中的一些新功能, 这样您就可以指定一个默认用户代理, 用于特定请求 。 这有助于追踪Umami否则会忽略的请求。

logo

©2024 Scott Galloway