主頁 > .NET開發 > AspNetCore3.1_Secutiry原始碼決議_4_Authentication_JwtBear

AspNetCore3.1_Secutiry原始碼決議_4_Authentication_JwtBear

2020-09-16 03:58:20 .NET開發

文章目錄

  • AspNetCore3.1_Secutiry原始碼決議_1_目錄
  • AspNetCore3.1_Secutiry原始碼決議_2_Authentication_核心專案
  • AspNetCore3.1_Secutiry原始碼決議_3_Authentication_Cookies
  • AspNetCore3.1_Secutiry原始碼決議_4_Authentication_JwtBear
  • AspNetCore3.1_Secutiry原始碼決議_5_Authentication_OAuth
  • AspNetCore3.1_Secutiry原始碼決議_6_Authentication_OpenIdConnect
  • AspNetCore3.1_Secutiry原始碼決議_7_Authentication_其他
  • AspNetCore3.1_Secutiry原始碼決議_8_Authorization_授權框架

JwtBear簡介

首先回想一下Cookie認證,Cookie認證在用戶登錄成功之后將用戶資訊加密后寫入瀏覽器Cookie中,服務端通過決議Cookie內容來驗證用戶登錄狀態,這樣做有幾個缺陷:

  • Cookie加密方式是微軟自己定義的,并非國際標準,其他語言無法識別,
  • 依賴Cookie,在跨域場景下,存在諸多限制,
    • CORS除非設定白名單否則是不允許帶Cookie的;
    • 大部分瀏覽器對跨域設定Cookie有嚴格的限制,比如:A網站使用iframe嵌套B網站來實作集成,B網站依賴Cookie來維持登錄態,如果是Chrome瀏覽器,需要將Cookie的Secure設定為true,即必須使用https,同時將SameSite設定為None,這樣可以解決問題但是存在跨站訪問攻擊(CSRF)的安全漏洞,而Safari則是完全禁止設定跨站Cookie的)

JwtBear可以解決上面的缺點

  • Jwt是國際標準
  • Jwt不依賴Cookie,不存在跨站訪問攻擊問題

依賴注入

提供了四個多載方法,主要設定配置類 JwtBearerOptions,
默認添加名稱為Bearer的認證Schema,JwtBearerHandler為處理器類,

  public static class JwtBearerExtensions
    {
        public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder)
            => builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ => { });

        public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, Action<JwtBearerOptions> configureOptions)
            => builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions);

        public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action<JwtBearerOptions> configureOptions)
            => builder.AddJwtBearer(authenticationScheme, displayName: null, configureOptions: configureOptions);

        public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<JwtBearerOptions> configureOptions)
        {
            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
            return builder.AddScheme<JwtBearerOptions, JwtBearerHandler>(authenticationScheme, displayName, configureOptions);
        }
    }

通常來說用默認配置就夠了,

    public class JwtBearerOptions : AuthenticationSchemeOptions
    {
        /// <summary>
        /// Gets or sets if HTTPS is required for the metadata address or authority.
        /// The default is true. This should be disabled only in development environments.
        /// </summary>
        public bool RequireHttpsMetadata { get; set; } = true;

        /// <summary>
        /// Gets or sets the discovery endpoint for obtaining metadata
        /// </summary>
        public string MetadataAddress { get; set; }

        /// <summary>
        /// Gets or sets the Authority to use when making OpenIdConnect calls.
        /// </summary>
        public string Authority { get; set; }

        /// <summary>
        /// Gets or sets a single valid audience value for any received OpenIdConnect token.
        /// This value is passed into TokenValidationParameters.ValidAudience if that property is empty.
        /// </summary>
        /// <value>
        /// The expected audience for any received OpenIdConnect token.
        /// </value>
        public string Audience { get; set; }

        /// <summary>
        /// Gets or sets the challenge to put in the "WWW-Authenticate" header.
        /// </summary>
        public string Challenge { get; set; } = JwtBearerDefaults.AuthenticationScheme;

        /// <summary>
        /// The object provided by the application to process events raised by the bearer authentication handler.
        /// The application may implement the interface fully, or it may create an instance of JwtBearerEvents
        /// and assign delegates only to the events it wants to process.
        /// </summary>
        public new JwtBearerEvents Events
        {
            get { return (JwtBearerEvents)base.Events; }
            set { base.Events = value; }
        }

        /// <summary>
        /// The HttpMessageHandler used to retrieve metadata.
        /// This cannot be set at the same time as BackchannelCertificateValidator unless the value
        /// is a WebRequestHandler.
        /// </summary>
        public HttpMessageHandler BackchannelHttpHandler { get; set; }

        /// <summary>
        /// Gets or sets the timeout when using the backchannel to make an http call.
        /// </summary>
        public TimeSpan BackchannelTimeout { get; set; } = TimeSpan.FromMinutes(1);

        /// <summary>
        /// Configuration provided directly by the developer. If provided, then MetadataAddress and the Backchannel properties
        /// will not be used. This information should not be updated during request processing.
        /// </summary>
        public OpenIdConnectConfiguration Configuration { get; set; }

        /// <summary>
        /// Responsible for retrieving, caching, and refreshing the configuration from metadata.
        /// If not provided, then one will be created using the MetadataAddress and Backchannel properties.
        /// </summary>
        public IConfigurationManager<OpenIdConnectConfiguration> ConfigurationManager { get; set; }

        /// <summary>
        /// Gets or sets if a metadata refresh should be attempted after a SecurityTokenSignatureKeyNotFoundException. This allows for automatic
        /// recovery in the event of a signature key rollover. This is enabled by default.
        /// </summary>
        public bool RefreshOnIssuerKeyNotFound { get; set; } = true;

        /// <summary>
        /// Gets the ordered list of <see cref="ISecurityTokenValidator"/> used to validate access tokens.
        /// </summary>
        public IList<ISecurityTokenValidator> SecurityTokenValidators { get; } = new List<ISecurityTokenValidator> { new JwtSecurityTokenHandler() };

        /// <summary>
        /// Gets or sets the parameters used to validate identity tokens.
        /// </summary>
        /// <remarks>Contains the types and definitions required for validating a token.</remarks>
        /// <exception cref="ArgumentNullException">if 'value' is null.</exception>
        public TokenValidationParameters TokenValidationParameters { get; set; } = new TokenValidationParameters();

        /// <summary>
        /// Defines whether the bearer token should be stored in the
        /// <see cref="AuthenticationProperties"/> after a successful authorization.
        /// </summary>
        public bool SaveToken { get; set; } = true;

        /// <summary>
        /// Defines whether the token validation errors should be returned to the caller.
        /// Enabled by default, this option can be disabled to prevent the JWT handler
        /// from returning an error and an error_description in the WWW-Authenticate header.
        /// </summary>
        public bool IncludeErrorDetails { get; set; } = true;
    }

這里會對配置做校驗,JwtBear默認是沒有提供發放Token的方法的,需要我們自己實作,這個后面再說,發放Token可以本地發放,也可以請求遠程地址,

很多配置都是使用OpenConnectId協議來實作遠程認證需要的,如果是本地發放token則不要配置,

 /// <summary>
/// Invoked to post configure a JwtBearerOptions instance.
/// </summary>
/// <param name="name">The name of the options instance being configured.</param>
/// <param name="options">The options instance to configure.</param>
public void PostConfigure(string name, JwtBearerOptions options)
{
    if (string.IsNullOrEmpty(options.TokenValidationParameters.ValidAudience) && !string.IsNullOrEmpty(options.Audience))
    {
        options.TokenValidationParameters.ValidAudience = options.Audience;
    }

    if (options.ConfigurationManager == null)
    {
        if (options.Configuration != null)
        {
            options.ConfigurationManager = new StaticConfigurationManager<OpenIdConnectConfiguration>(options.Configuration);
        }
        else if (!(string.IsNullOrEmpty(options.MetadataAddress) && string.IsNullOrEmpty(options.Authority)))
        {
            if (string.IsNullOrEmpty(options.MetadataAddress) && !string.IsNullOrEmpty(options.Authority))
            {
                options.MetadataAddress = options.Authority;
                if (!options.MetadataAddress.EndsWith("/", StringComparison.Ordinal))
                {
                    options.MetadataAddress += "/";
                }

                options.MetadataAddress += ".well-known/openid-configuration";
            }

            if (options.RequireHttpsMetadata && !options.MetadataAddress.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException("The MetadataAddress or Authority must use HTTPS unless disabled for development by setting RequireHttpsMetadata=https://www.cnblogs.com/holdengong/p/false.");
            }

            var httpClient = new HttpClient(options.BackchannelHttpHandler ?? new HttpClientHandler());
            httpClient.Timeout = options.BackchannelTimeout;
            httpClient.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB

            options.ConfigurationManager = new ConfigurationManager(options.MetadataAddress, new OpenIdConnectConfigurationRetriever(),
                new HttpDocumentRetriever(httpClient) { RequireHttps = options.RequireHttpsMetadata });
        }
    }
}

發放Token

上面提到了JwtBear專案沒有提供發放Token的方法,可以使用微軟的擴展庫來實作,
SymmetricSecurityKey :表示使用對稱演算法生成的所有密鑰的抽象基類,

using Microsoft.AspNetCore.Mvc;
using System;
using System.Text;

using IdentityModel;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

[Route("api/user/login")]
[HttpPost]
public IActionResult Login([FromBody]UserDto dto)
{
    //驗證username.password等邏輯..略
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes("this is a SecretKey");
    var authTime = DateTime.UtcNow;
    var expiresAt = authTime.AddDays(7);
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new Claim[]
        {
            new Claim(JwtClaimTypes.Id, "1"),
            //誰用token
            new Claim(JwtClaimTypes.Audience,"http://localhost:5000"),
            //誰發token
            new Claim(JwtClaimTypes.Issuer,"http://localhost:5000"),
        }),
        Expires = expiresAt,
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
    };
    var token = tokenHandler.CreateToken(tokenDescriptor);
    var tokenString = tokenHandler.WriteToken(token);
    return Ok(tokenString);
}

HS256演算法要求key大于128bit即16位元組,否則會出錯
擴展庫原始碼地址:

https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues

上面的代碼只實作了很簡單的token頒發的功能,重繪token,scope的校驗,單點登錄等都沒有實作,不建議生產環境使用(除非你的需求十分簡單已經可以滿足),實作這些十分麻煩,通常需要借助框架比如IdentityServer,這個后面再聊,

Cookie認證與Jwt認證對比

Cookie認證簡圖
Cookie認證需要通知瀏覽器操作cookie,以及302跳轉,所以前后端同域的web場景比較合適,

sequenceDiagram client->>server: 校驗用戶名密碼后登錄(HttpContext.SignInAsync()) server->>server: Cookie維護登錄資訊 server->>client: 302跳轉RedirectUrl client->>server: 登出(HttpContext.SignOutAsync()) server->>client: 清除Cookie,302跳轉LogoutUrl

Jwt認證簡圖
可以看到服務端只負責頒發token、校驗token,校驗失敗回傳標準401,至于401怎么處理在于客戶端,服務端不依賴于瀏覽器,所以用于非web端、或者前后端分離的場景比較合適

sequenceDiagram client->>server: 登錄(Login) server->>server: 校驗資訊 server->>client: 頒發token client->>server: 訪問受保護api server->>server: 校驗token,將jwt中的claims資訊寫入HttpContext server->>client: 回傳api結果 or 401 client->>client: 處理401,自行跳到登錄頁或其他操作

JwtBearerHandler原始碼分析

JwtBearerHandler繼承自AuthenticationHandler,比CookieHandler少了SignIn和Signout的實作,它只處理認證(Authenticate)、質詢(Chanllenge)和拒絕(Forbid),上面已經說明過原因了,

classDiagram class AuthenticationHandler{ AuthenticationScheme Scheme TOptions Options HttpContext Context HttpRequest Request HttpResponse Response PathString OriginalPath PathString OriginalPathBase ILogger Logger UrlEncoder UrlEncoder ISystemClock Clock object Events string ClaimsIssuer string CurrentUri InitializeAsync() +Task AuthenticateAsync() +Task ChallengeAsync(AuthenticationProperties properties) +Task ForbidAsync(AuthenticationProperties properties) } class IAuthenticationHandler{ HandleAsync() } JwtBearerHandler-->AuthenticationHandler AuthenticationHandler-->IAuthenticationHandler

Authenticate - 認證

  • 觸發MessageReceived事件,相當于是個鉤子,開發可以直接攔截回傳認證結果,或者設定token取代header中的token
  • 從header中取token
  • 獲取配置和校驗配置
  • 回圈Option.SecurityTokenValidators執行每個校驗器的校驗邏輯(默認校驗器邏輯等下說)
  • 如果配置 Options.SaveToken=true, 則會將access_token保存在HttpContext.Properties中
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
    string token = null;
    try
    {
        // Give application opportunity to find from a different location, adjust, or reject token
        var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);

        // event can set the token
        await Events.MessageReceived(messageReceivedContext);
        if (messageReceivedContext.Result != null)
        {
            return messageReceivedContext.Result;
        }

        // If application retrieved token from somewhere else, use that.
        token = messageReceivedContext.Token;

        if (string.IsNullOrEmpty(token))
        {
            string authorization = Request.Headers[HeaderNames.Authorization];

            // If no authorization header found, nothing to process further
            if (string.IsNullOrEmpty(authorization))
            {
                return AuthenticateResult.NoResult();
            }

            if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
            {
                token = authorization.Substring("Bearer ".Length).Trim();
            }

            // If no token found, no further work possible
            if (string.IsNullOrEmpty(token))
            {
                return AuthenticateResult.NoResult();
            }
        }

        if (_configuration == null && Options.ConfigurationManager != null)
        {
            _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
        }

        var validationParameters = Options.TokenValidationParameters.Clone();
        if (_configuration != null)
        {
            var issuers = new[] { _configuration.Issuer };
            validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;

            validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
                ?? _configuration.SigningKeys;
        }

        List<Exception> validationFailures = null;
        SecurityToken validatedToken;
        foreach (var validator in Options.SecurityTokenValidators)
        {
            if (validator.CanReadToken(token))
            {
                ClaimsPrincipal principal;
                try
                {
                    principal = validator.ValidateToken(token, validationParameters, out validatedToken);
                }
                catch (Exception ex)
                {
                    Logger.TokenValidationFailed(ex);

                    // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
                    if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
                        && ex is SecurityTokenSignatureKeyNotFoundException)
                    {
                        Options.ConfigurationManager.RequestRefresh();
                    }

                    if (validationFailures == null)
                    {
                        validationFailures = new List<Exception>(1);
                    }
                    validationFailures.Add(ex);
                    continue;
                }

                Logger.TokenValidationSucceeded();

                var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
                {
                    Principal = principal,
                    SecurityToken = validatedToken
                };

                await Events.TokenValidated(tokenValidatedContext);
                if (tokenValidatedContext.Result != null)
                {
                    return tokenValidatedContext.Result;
                }

                if (Options.SaveToken)
                {
                    tokenValidatedContext.Properties.StoreTokens(new[]
                    {
                        new AuthenticationToken { Name = "access_token", Value = https://www.cnblogs.com/holdengong/p/token }
                    });
                }

                tokenValidatedContext.Success();
                return tokenValidatedContext.Result;
            }
        }

        if (validationFailures != null)
        {
            var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
            {
                Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
            };

            await Events.AuthenticationFailed(authenticationFailedContext);
            if (authenticationFailedContext.Result != null)
            {
                return authenticationFailedContext.Result;
            }

            return AuthenticateResult.Fail(authenticationFailedContext.Exception);
        }

        return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
    }
    catch (Exception ex)
    {
        Logger.ErrorProcessingMessage(ex);

        var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
        {
            Exception = ex
        };

        await Events.AuthenticationFailed(authenticationFailedContext);
        if (authenticationFailedContext.Result != null)
        {
            return authenticationFailedContext.Result;
        }

        throw;
    }
}

JwtBearOptions配置類的這段代碼可以看到, 默認校驗類是JwtSecurityTokenHandler,這是上面提到的擴展包里面的類,命名空間是System.IdentityModel.Tokens.Jwt

/// <summary>
/// Gets the ordered list of <see cref="ISecurityTokenValidator"/> used to validate access tokens.
/// </summary>
public IList<ISecurityTokenValidator> SecurityTokenValidators { get; } = new List<ISecurityTokenValidator> { new JwtSecurityTokenHandler() };

看一看代碼,代碼比較簡單,就是解碼token,然后將claims資訊回傳,之前生成jwt也是使用的這個類,
如果需要額外的校驗邏輯,可以自己實作ISecurityTokenValidator,用這個類解碼token得到claims之后實作自己的業務邏輯,

public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
    {
        if (string.IsNullOrWhiteSpace(token))
            throw LogHelper.LogArgumentNullException(nameof(token));

        if (validationParameters == null)
            throw LogHelper.LogArgumentNullException(nameof(validationParameters));

        if (token.Length > MaximumTokenSizeInBytes)
            throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(TokenLogMessages.IDX10209, token.Length, MaximumTokenSizeInBytes)));

        var tokenParts = token.Split(new char[] { '.' }, JwtConstants.MaxJwtSegmentCount + 1);
        if (tokenParts.Length != JwtConstants.JwsSegmentCount && tokenParts.Length != JwtConstants.JweSegmentCount)
            throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX12741, token)));

        if (tokenParts.Length == JwtConstants.JweSegmentCount)
        {
            var jwtToken = ReadJwtToken(token);
            var decryptedJwt = DecryptToken(jwtToken, validationParameters);
            var innerToken = ValidateSignature(decryptedJwt, validationParameters);
            jwtToken.InnerToken = innerToken;
            validatedToken = jwtToken;
            return ValidateTokenPayload(innerToken, validationParameters);
        }
        else
        {
            validatedToken = ValidateSignature(token, validationParameters);
            return ValidateTokenPayload(validatedToken as JwtSecurityToken, validationParameters);
        }
    }

Chanllenge -- 質詢

質詢邏輯簡單說下,執行認證方法,成功則回傳結果,失敗回傳401,生成的報文大致這樣

https://tools.ietf.org/html/rfc6750#section-3.1
WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
    var authResult = await HandleAuthenticateOnceSafeAsync();
    var eventContext = new JwtBearerChallengeContext(Context, Scheme, Options, properties)
    {
        AuthenticateFailure = authResult?.Failure
    };

    // Avoid returning error=invalid_token if the error is not caused by an authentication failure (e.g missing token).
    if (Options.IncludeErrorDetails && eventContext.AuthenticateFailure != null)
    {
        eventContext.Error = "invalid_token";
        eventContext.ErrorDescription = CreateErrorDescription(eventContext.AuthenticateFailure);
    }

    await Events.Challenge(eventContext);
    if (eventContext.Handled)
    {
        return;
    }

    Response.StatusCode = 401;

    if (string.IsNullOrEmpty(eventContext.Error) &&
        string.IsNullOrEmpty(eventContext.ErrorDescription) &&
        string.IsNullOrEmpty(eventContext.ErrorUri))
    {
        Response.Headers.Append(HeaderNames.WWWAuthenticate, Options.Challenge);
    }
    else
    {
        // https://tools.ietf.org/html/rfc6750#section-3.1
        // WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
        var builder = new StringBuilder(Options.Challenge);
        if (Options.Challenge.IndexOf(' ') > 0)
        {
            // Only add a comma after the first param, if any
            builder.Append(',');
        }
        if (!string.IsNullOrEmpty(eventContext.Error))
        {
            builder.Append(" error=\"");
            builder.Append(eventContext.Error);
            builder.Append("\"");
        }
        if (!string.IsNullOrEmpty(eventContext.ErrorDescription))
        {
            if (!string.IsNullOrEmpty(eventContext.Error))
            {
                builder.Append(",");
            }

            builder.Append(" error_description=\"");
            builder.Append(eventContext.ErrorDescription);
            builder.Append('\"');
        }
        if (!string.IsNullOrEmpty(eventContext.ErrorUri))
        {
            if (!string.IsNullOrEmpty(eventContext.Error) ||
                !string.IsNullOrEmpty(eventContext.ErrorDescription))
            {
                builder.Append(",");
            }

            builder.Append(" error_uri=\"");
            builder.Append(eventContext.ErrorUri);
            builder.Append('\"');
        }

        Response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString());
    }
}

Forbid - 拒絕

回傳403

protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
{
    var forbiddenContext = new ForbiddenContext(Context, Scheme, Options);
    Response.StatusCode = 403;
    return Events.Forbidden(forbiddenContext);
}

參考資料:

Cookie的SameSite屬性

http://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html

CORS

https://holdengong.com/aspnetcore3.1_middleware原始碼決議_1_cors/

ASPNET Core 認證與授權[4]:JwtBearer認證

https://www.cnblogs.com/RainingNight/p/jwtbearer-authentication-in-asp-net-core.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/52841.html

標籤:.NET Core

上一篇:web系統安全運營之基礎- 基于DFA演算法的高性能的敏感詞,臟詞的檢測過濾演算法類(c#).

下一篇:Asp.Net Core 中IdentityServer4 實戰之 Claim詳解

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more