ASP.NET Core 運行原理解剖[5]:Authentication

来源:http://www.cnblogs.com/RainingNight/archive/2017/09/10/authentication-in-asp-net-core.html
-Advertisement-
Play Games

在現代應用程式中,認證已不再是簡單的將用戶憑證保存在瀏覽器中,而要適應多種場景,如App,WebAPI,第三方登錄等等。在 ASP.NET 4.x 時代的Windows認證和Forms認證已無法滿足現代化的需求,因此在ASP.NET Core 中對認證及授權進行了全新設計,使其更加靈活,可以應付各種 ...


在現代應用程式中,認證已不再是簡單的將用戶憑證保存在瀏覽器中,而要適應多種場景,如App,WebAPI,第三方登錄等等。在 ASP.NET 4.x 時代的Windows認證和Forms認證已無法滿足現代化的需求,因此在ASP.NET Core 中對認證及授權進行了全新設計,使其更加靈活,可以應付各種場景。在上一章中,我們提到HttpContext中認證相關的功能放在了獨立的模塊中,以擴展的方式來展現,以保證HttpContext的簡潔性,本章就來介紹一下 ASP.NET Core 認證系統的整個輪廓,以及它的切入點。

目錄

本系列文章從源碼分析的角度來探索 ASP.NET Core 的運行原理,分為以下幾個章節:

ASP.NET Core 運行原理解剖[1]:Hosting

ASP.NET Core 運行原理解剖[2]:Hosting補充之配置介紹

ASP.NET Core 運行原理解剖[3]:Middleware-請求管道的構成

ASP.NET Core 運行原理解剖[4]:進入HttpContext的世界

ASP.NET Core 運行原理解剖[5]:Authentication(Current)

  1. AuthenticationHttpContextExtensions
  2. IAuthenticationSchemeProvider
  3. IAuthenticationHandlerProvider
  4. IAuthenticationService
  5. Usage

AuthenticationHttpContextExtensions

AuthenticationHttpContextExtensions 類是對 HttpContext 認證相關的擴展,它提供瞭如下擴展方法:

public static class AuthenticationHttpContextExtensions
{
    public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>
        context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);

    public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties) { }
    public static Task ForbidAsync(this HttpContext context, string scheme, AuthenticationProperties properties) { }
    public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) {}
    public static Task SignOutAsync(this HttpContext context, string scheme, AuthenticationProperties properties) { }
    public static Task<string> GetTokenAsync(this HttpContext context, string scheme, string tokenName) { }
}

主要包括如上6個擴展方法,其它的只是一些參數重載:

  • SignInAsync 用戶登錄成功後頒發一個證書(加密的用戶憑證),用來標識用戶的身份。

  • SignOutAsync 退出登錄,如清除Coookie等。

  • AuthenticateAsync 驗證在 SignInAsync 中頒發的證書,並返回一個 AuthenticateResult 對象,表示用戶的身份。

  • ChallengeAsync 返回一個需要認證的標識來提示用戶登錄,通常會返回一個 401 狀態碼。

  • ForbidAsync 禁上訪問,表示用戶許可權不足,通常會返回一個 403 狀態碼。

  • GetTokenAsync 用來獲取 AuthenticationProperties 中保存的額外信息。

它們的實現都非常簡單,與展示的第一個方法類似,從DI系統中獲取到 IAuthenticationService 介面實例,然後調用其同名方法。

因此,如果我們希望使用認證服務,那麼首先要註冊 IAuthenticationService 的實例,ASP.NET Core 中也提供了對應註冊擴展方法:

public static class AuthenticationCoreServiceCollectionExtensions
{
    public static IServiceCollection AddAuthenticationCore(this IServiceCollection services)
    {
        services.TryAddScoped<IAuthenticationService, AuthenticationService>();
        services.TryAddSingleton<IClaimsTransformation, NoopClaimsTransformation>(); // Can be replaced with scoped ones that use DbContext
        services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
        services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();
        return services;
    }

    public static IServiceCollection AddAuthenticationCore(this IServiceCollection services, Action<AuthenticationOptions> configureOptions) 
    {
        services.AddAuthenticationCore();
        services.Configure(configureOptions);
        return services;
    }
}

如上,AddAuthenticationCore 中註冊了認證系統的三大核心對象:IAuthenticationSchemeProviderIAuthenticationHandlerProviderIAuthenticationService,以及一個對Claim進行轉換的 IClaimsTransformation(不常用),下麵就來介紹一下這三大對象。

IAuthenticationSchemeProvider

首先來解釋一下 Scheme 是用來做什麼的。因為在 ASP.NET Core 中可以支持各種各樣的認證方式(如,cookie, bearer, oauth, openid 等等),而 Scheme 用來標識使用的是哪種認證方式,不同的認證方式其處理方式是完全不一樣的,所以Scheme是非常重要的。

IAuthenticationSchemeProvider 用來提供對Scheme的註冊和查詢,定義如下:

public interface IAuthenticationSchemeProvider
{
    void AddScheme(AuthenticationScheme scheme);
    Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync();
    Task<AuthenticationScheme> GetSchemeAsync(string name);
    Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync();

    Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync();
    Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync();
    Task<AuthenticationScheme> GetDefaultForbidSchemeAsync();
    Task<AuthenticationScheme> GetDefaultSignInSchemeAsync();
    Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync();
}

AddScheme 方法,用來註冊Scheme,而每一種Scheme最終體現為一個 AuthenticationScheme 類型的對象:

public class AuthenticationScheme
{
    public AuthenticationScheme(string name, string displayName, Type handlerType)
    {
        if (!typeof(IAuthenticationHandler).IsAssignableFrom(handlerType))
        {
            throw new ArgumentException("handlerType must implement IAuthenticationSchemeHandler.");
        }
        ...
    }

    public string Name { get; }

    public string DisplayName { get; }

    public Type HandlerType { get; }
}

每一個Scheme中還包含一個對應的IAuthenticationHandler類型的Handler,由它來完成具體的處理邏輯,看一下它的預設實現:

public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
{
    private IDictionary<string, AuthenticationScheme> _map = new Dictionary<string, AuthenticationScheme>(StringComparer.Ordinal);

    public AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options)
    {
        _options = options.Value;

        foreach (var builder in _options.Schemes)
        {
            var scheme = builder.Build();
            AddScheme(scheme);
        }
    }

    private Task<AuthenticationScheme> GetDefaultSchemeAsync()
        => _options.DefaultScheme != null
        ? GetSchemeAsync(_options.DefaultScheme)
        : Task.FromResult<AuthenticationScheme>(null);
    ....
}

如上,通過一個內部的字典來保存我們所註冊的Scheme,key為Scheme名稱,然後提供一系列對該字典的查詢。它還提供了一系列的GetDefaultXXXSchemeAsync方法,所使用的Key是通過構造函數中接收的AuthenticationOptions對象來獲取的,如果未配置,則返回為null

對於 AuthenticationOptions 對象,大家可能會比較熟悉,在上面介紹的 AddAuthenticationCore 擴展方法中,也是使用該對象來配置認證系統:

public class AuthenticationOptions
{
    private readonly IList<AuthenticationSchemeBuilder> _schemes = new List<AuthenticationSchemeBuilder>();
    public IEnumerable<AuthenticationSchemeBuilder> Schemes => _schemes;
    public IDictionary<string, AuthenticationSchemeBuilder> SchemeMap { get; } = new Dictionary<string, AuthenticationSchemeBuilder>(StringComparer.Ordinal);

    public void AddScheme(string name, Action<AuthenticationSchemeBuilder> configureBuilder)
    {
        if (SchemeMap.ContainsKey(name))
        {
            throw new InvalidOperationException("Scheme already exists: " + name);
        }
        var builder = new AuthenticationSchemeBuilder(name);
        configureBuilder(builder);
        _schemes.Add(builder);
        SchemeMap[name] = builder;
    }

    public void AddScheme<THandler>(string name, string displayName) where THandler : IAuthenticationHandler
        => AddScheme(name, b =>
        {
            b.DisplayName = displayName;
            b.HandlerType = typeof(THandler);
        });


    public string DefaultScheme { get; set; }
    public string DefaultAuthenticateScheme { get; set; }
    public string DefaultSignInScheme { get; set; }
    public string DefaultSignOutScheme { get; set; }
    public string DefaultChallengeScheme { get; set; }
    public string DefaultForbidScheme { get; set; }
}

該對象可以幫助我們更加方便的註冊Scheme,提供泛型和 AuthenticationSchemeBuilder 兩種方式配置方式。

到此,我們瞭解到,要想使用認證系統,必要先註冊Scheme,而每一個Scheme必須指定一個Handler,否則會拋出異常,下麵我們就來瞭解一下Handler。

IAuthenticationHandlerProvider

在 ASP.NET Core 的認證系統中,AuthenticationHandler 負責對用戶憑證的驗證,它定義瞭如下介面:

public interface IAuthenticationHandler
{
    Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
    Task<AuthenticateResult> AuthenticateAsync();
    Task ChallengeAsync(AuthenticationProperties properties);
    Task ForbidAsync(AuthenticationProperties properties);
}

AuthenticationHandler的創建是通過 IAuthenticationHandlerProvider 來完成的:

public interface IAuthenticationHandlerProvider
{
    Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme);
}

Provider 只定義了一個 GetHandlerAsync 方法,來獲取指定的Scheme的Hander,在 ASP.NET Core 中,很多地方都使用了類似的 Provider 模式。

而HandlerProvider的實現,我們通過對上面SchemeProvider的瞭解,應該可以猜到一二,因為在 AuthenticationScheme 中已經包含了Hander:

public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
{
    public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
    {
        Schemes = schemes;
    }

    public IAuthenticationSchemeProvider Schemes { get; }

    private Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal);

    public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme)
    {
        if (_handlerMap.ContainsKey(authenticationScheme))
        {
            return _handlerMap[authenticationScheme];
        }

        var scheme = await Schemes.GetSchemeAsync(authenticationScheme);
        if (scheme == null)
        {
            return null;
        }
        var handler = (context.RequestServices.GetService(scheme.HandlerType) ??
            ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType))
            as IAuthenticationHandler;
        if (handler != null)
        {
            await handler.InitializeAsync(scheme, context);
            _handlerMap[authenticationScheme] = handler;
        }
        return handler;
    }
}

可以看到,AuthenticationHandlerProvider 首先使用 IAuthenticationSchemeProvider 獲取到當前Scheme,然後先從DI中查找是否有此Scheme中的Handler,如果未註冊到DI系統中,則使用 ActivatorUtilities 來創建其實例,並緩存到內部的 _handlerMap 字典中。

IAuthenticationService

IAuthenticationService 本質上是對 IAuthenticationSchemeProvider 和 IAuthenticationHandlerProvider 封裝,用來對外提供一個統一的認證服務介面:

public interface IAuthenticationService
{
    Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme);
    Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);
    Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
    Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
    Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
}

這5個方法中,都需要接收一個 scheme 參數,因為只有先指定你要使用的認證方式,才能知道該如何進行認證。

對於上面的前三個方法,我們知道在IAuthenticationHandler中都有對應的實現,而SignInAsyncSignOutAsync則使用了獨立的定義介面:

public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler
{
    Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
}

public interface IAuthenticationSignOutHandler : IAuthenticationHandler
{
    Task SignOutAsync(AuthenticationProperties properties);
}

SignInAsync 和 SignOutAsync 之所以使用獨立的介面,是因為在現代架構中,通常會提供一個統一的認證中心,負責證書的頒發及銷毀(登入和登出),而其它服務只用來驗證證書,並用不到SingIn/SingOut。

而 IAuthenticationService 的預設實現 AuthenticationService 中的邏輯就非常簡單了,只是調用Handler中的同名方法:

public class AuthenticationService : IAuthenticationService
{
    public IAuthenticationSchemeProvider Schemes { get; }
    public IAuthenticationHandlerProvider Handlers { get; }
    public IClaimsTransformation Transform { get; }

    public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
    {
        if (scheme == null)
        {
            var defaultScheme = await Schemes.GetDefaultAuthenticateSchemeAsync();
            scheme = defaultScheme?.Name;
            if (scheme == null)
            {
                throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found.");
            }
        }

        var handler = await Handlers.GetHandlerAsync(context, scheme);
        var result = await handler.AuthenticateAsync();
        if (result != null && result.Succeeded)
        {
            var transformed = await Transform.TransformAsync(result.Principal);
            return AuthenticateResult.Success(new AuthenticationTicket(transformed, result.Properties, result.Ticket.AuthenticationScheme));
        }
        return result;
    }
}

AuthenticationService中對這5個方法的實現大致相同,首先會在我們傳入的scheme為null時,來獲取我們所註冊的預設scheme,然後獲取調用相應Handler的即可。針對 SignInAsyncSignOutAsync 的實現則會判斷Handler是否實現了對應的介面,若未實現則拋出異常。

不過在這裡還涉及到如下兩個對象:

AuthenticateResult

AuthenticateResult 用來表示認證的結果:

public class AuthenticateResult
{
    public AuthenticationTicket Ticket { get; protected set; }

    public bool Succeeded => Ticket != null;
    public ClaimsPrincipal Principal => Ticket?.Principal;
    public AuthenticationProperties Properties => Ticket?.Properties;
    public Exception Failure { get; protected set; }
    public bool None { get; protected set; }
    public static AuthenticateResult Success(AuthenticationTicket ticket) => new AuthenticateResult() { Ticket = ticket };
    public static AuthenticateResult NoResult() => new AuthenticateResult() { None = true };
    public static AuthenticateResult Fail(Exception failure) => new AuthenticateResult() { Failure = failure };
    public static AuthenticateResult Fail(string failureMessage) => new AuthenticateResult() { Failure = new Exception(failureMessage) };
}

它主要包含一個核心屬性 AuthenticationTicket

public class AuthenticationTicket
{ 
    public string AuthenticationScheme { get; private set; }
    public ClaimsPrincipal Principal { get; private set; }
    public AuthenticationProperties Properties { get; private set; }
}

我們可以把AuthenticationTicket看成是一個經過認證後頒發的證書,

ClaimsPrincipal 屬性我們較為熟悉,表示證書的主體,在基於聲明的認證中,用來標識一個人的身份(如:姓名,郵箱等等),後續會詳細介紹一下基於聲明的認證。

AuthenticationProperties 屬性用來表示證書頒發的相關信息,如頒發時間,過期時間,重定向地址等等:

public class AuthenticationProperties
{
    public IDictionary<string, string> Items { get; }

    public string RedirectUri
    {
        get
        {
            string value;
            return Items.TryGetValue(RedirectUriKey, out value) ? value : null;
        }
        set
        {
            if (value != null) Items[RedirectUriKey] = value;
            else
            {
                if (Items.ContainsKey(RedirectUriKey)) Items.Remove(RedirectUriKey);
            }
        }
    }

    ...
}

在上面最開始介紹的HttpContext中的 GetTokenAsync 擴展方法便是對AuthenticationProperties的擴展:

public static class AuthenticationTokenExtensions
{
    private static string TokenNamesKey = ".TokenNames";
    private static string TokenKeyPrefix = ".Token.";

    public static void StoreTokens(this AuthenticationProperties properties, IEnumerable<AuthenticationToken> tokens) {}
    public static bool UpdateTokenValue(this AuthenticationProperties properties, string tokenName, string tokenValue) {}
    public static IEnumerable<AuthenticationToken> GetTokens(this AuthenticationProperties properties) { }

    public static string GetTokenValue(this AuthenticationProperties properties, string tokenName)
    {
        var tokenKey = TokenKeyPrefix + tokenName;
        return properties.Items.ContainsKey(tokenKey) ? properties.Items[tokenKey] : null;
    }

    public static Task<string> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string tokenName) 
        => auth.GetTokenAsync(context, scheme: null, tokenName: tokenName);

    public static async Task<string> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string scheme, string tokenName)
    {
        var result = await auth.AuthenticateAsync(context, scheme);
        return result?.Properties?.GetTokenValue(tokenName);
    }
}

如上,Token擴展只是對AuthenticationProperties中的 Items 屬性進行添加和讀取。

IClaimsTransformation

IClaimsTransformation 用來對由我們的應用程式傳入的 ClaimsPrincipal 進行轉換,它只定義了一個 Transform 方法:

public interface IClaimsTransformation
{
    Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal);
}

其預設實現,不做任何處理,直接返回。它適合於全局的為 ClaimsPrincipal 添加一些預定義的聲明,如添加當前時間等,然後在DI中把我們的實現註冊進去即可。

Usage

下麵我們演示一下 ASP.NET Core 認證系統的實際用法:

首先,我們要定義一個Handler:

public class MyHandler : IAuthenticationHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler
{
    public AuthenticationScheme Scheme { get; private set; }
    protected HttpContext Context { get; private set; }

    public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
    {
        Scheme = scheme;
        Context = context;
        return Task.CompletedTask;
    }

    public async Task<AuthenticateResult> AuthenticateAsync()
    {
        var cookie = Context.Request.Cookies["mycookie"];
        if (string.IsNullOrEmpty(cookie))
        {
            return AuthenticateResult.NoResult();
        }
        return AuthenticateResult.Success(Deserialize(cookie));
    }

    public Task ChallengeAsync(AuthenticationProperties properties)
    {
        Context.Response.Redirect("/login");
        return Task.CompletedTask;
    }

    public Task ForbidAsync(AuthenticationProperties properties)
    {
        Context.Response.StatusCode = 403;
        return Task.CompletedTask;
    }

    public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
    {
        var ticket = new AuthenticationTicket(user, properties, Scheme.Name);
        Context.Response.Cookies.Append("myCookie", Serialize(ticket));
        return Task.CompletedTask;
    }

    public Task SignOutAsync(AuthenticationProperties properties)
    {
        Context.Response.Cookies.Delete("myCookie");
        return Task.CompletedTask;
    }
}

如上,在 SignInAsync 中將用戶的Claim序列化後保存到Cookie中,在 AuthenticateAsync 中從Cookie中讀取並反序列化成用戶Claim。

然後在DI系統中註冊我們的Handler和Scheme:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthenticationCore(options => options.AddScheme<MyHandler>("myScheme", "demo scheme"));
}

最後,便可以通過HttpContext來調用認證系統了:

public void Configure(IApplicationBuilder app)
{
    // 登錄
    app.Map("/login", builder => builder.Use(next =>
    {
        return async (context) =>
        {
            var claimIdentity = new ClaimsIdentity();
            claimIdentity.AddClaim(new Claim(ClaimTypes.Name, "jim"));
            await context.SignInAsync("myScheme", new ClaimsPrincipal(claimIdentity));
        };
    }));

    // 退出
    app.Map("/logout", builder => builder.Use(next =>
    {
        return async (context) =>
        {
            await context.SignOutAsync("myScheme");
        };
    }));

    // 認證
    app.Use(next =>
    {
        return async (context) =>
        {
            var result = await context.AuthenticateAsync("myScheme");
            if (result?.Principal != null) context.User = result.Principal;
            await next(context);
        };
    });

    // 授權
    app.Use(async (context, next) =>
    {
        var user = context.User;
        if (user?.Identity?.IsAuthenticated ?? false)
        {
            if (user.Identity.Name != "jim") await context.ForbidAsync("myScheme");
            else await next();
        }
        else
        {
            await context.ChallengeAsync("myScheme");
        }
    });

    // 訪問受保護資源
    app.Map("/resource", builder => builder.Run(async (context) => await context.Response.WriteAsync("Hello, ASP.NET Core!")));
}

在這裡完整演示了 ASP.NET Core 認證系統的基本用法,當然,在實際使用中要比這更加複雜,如安全性,易用性等方面的完善,但本質上也就這麼多東西。

總結

本章基於 HttpAbstractions 對 ASP.NET Core 認證系統做了一個簡單的介紹,但大多是一些抽象層次的定義,並未涉及到具體的實現。因為現實中有各種各樣的場景無法預測,HttpAbstractions 提供了統一的認證規範,在我們的應用程式中,可以根據具體需求來靈活的擴展適合的認證方式。不過在 Security 提供了更加具體的實現方式,也包含了 Cookie, JwtBearer, OAuth, OpenIdConnect 等較為常用的認證實現。在下個系列會來詳細介紹一下 ASP.NET Core 的認證與授權,更加偏向於實戰,敬請期待!

ASP.NET Core 在GitHub上的開源地址為:https://github.com/aspnet,包含了100多個項目,ASP.NET Core 的核心是 HttpAbstractions ,其它的都是圍繞著 HttpAbstractions 進行的擴展。本系列文章所涉及到的源碼只包含 HostingHttpAbstractions ,它們兩個已經構成了一個完整的 ASP.NET Core 運行時,不需要其它模塊,就可以輕鬆應對一些簡單的場景。當然,更多的時候我們還會使用比較熟悉的 Mvc 來大大提高開發速度和體驗,後續再來介紹一下MVC的運行方式。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Markdown逐漸成為大家文章編輯的首選,這裡推薦兩個比較冷門的Markdown工具。 用什麼當做Markdown的主力工具? 網上有很多人推薦的Markdown的工具包括專業的Markdown工具,如Mou,macdown等,也有人用編輯器裝上插件後就使用,sublime、atom、vs cod ...
  • 環境:ubuntu16.04 今天遇到dns被劫持的情況,在此記錄一下: 1.首先如何確定是否被劫持: 那麼查詢一個並不存在的功能變數名稱 nslookup notexit.comrrrr 如果返回了一個ip地址,說明dns被劫持了,假設此ip地址為:123.34.5.6 那麼用8.8.8.8功能變數名稱伺服器解析 ...
  • 這段時間磨刀霍霍痛下決心學習嵌入式系統的開發,再安裝了linux虛擬機,下載了u-boot準備打補丁重新編譯時遇到了棘手的問題。 即arm-linux-gcc:命令未找到。 這個問題困擾了我一天,請教很多大神之後終於把它解決了,下麵我寫出解決的過程給大家分享。 1.查看有沒安裝arm-linux-g ...
  • 原文發表於cu:2016-06-22 Zabbix discoverer processes more than 75% busy原因及處理。 一.現象 配置了discovery任務後,zabbix dashboard 告警如下: 二.原因 1. 配置的每個discovery任務在一定時間內占用1個 ...
  • 原文發表於cu:2016-06-21 Zabbix自動發現功能從配置流程上比較簡單:Discovery與Action。 在做Zabbix的自動發現驗證時,使用"ICMP ping"的check方式時,自動發現功能並不生效。 一.環境 1. zabbix環境 Zabbix:zabbix-3.0.1se ...
  • 上面第二步要有寫文件的許可權,如果沒有可以切換為root賬戶進行操作 上面的第一個路徑根據自己安裝後的python地址確認 ...
  • NodeJS,MongoDB,Vue,VSCode 集成學習 開源項目地址:http://www.mangdot.com ...
  • 使用環境:Win7+VS2017 一、新建一個.NET Core2.0的MVC項目 二、使用Nuget添加EF的依賴 輸入命令:Install-Package Microsoft.EntityFrameworkCore.SqlServer 三、如果是使用db first,需要根據資料庫生成model ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...