zoukankan      html  css  js  c++  java
  • HTTP认证之摘要认证——Digest(二)

    导航

    HTTP认证之摘要认证——Digest(一)中介绍了Digest认证的工作原理和流程,接下来就赶紧通过代码来实践一下,以下教程使用默认的MD5摘要算法、auth策略,基于ASP.NET Core WebApi框架。如有兴趣,可查看源码

    一、准备工作

    在开始之前,先把最基本的业务逻辑准备好,只有一个根据用户名获取密码的方法:

    public class UserService
    {
        public static string GetPassword(string userName) => userName;
    }
    

    还有MD5加密的一些扩展方法

    public static class MD5HashExtensions
    {
        public static string ToMD5Hash(this string input) => MD5Helper.Encrypt(input);
    }
    
    public class MD5Helper
    {
        public static string Encrypt(string plainText) => Encrypt(plainText, Encoding.UTF8);
    
        public static string Encrypt(string plainText, Encoding encoding)
        {
            var bytes = encoding.GetBytes(plainText);
            return Encrypt(bytes);
        }
    
        public static string Encrypt(byte[] bytes)
        {
            using (var md5 = MD5.Create())
            {
                var hash = md5.ComputeHash(bytes);
                return FromHash(hash);
            }
        }
    
        private static string FromHash(byte[] hash)
        {
            var sb = new StringBuilder();
            foreach (var t in hash)
            {
                sb.Append(t.ToString("x2"));
            }
    
            return sb.ToString();
        }
    }
    

    二、编码

    以下代码书写在自定义授权过滤器中,继承自Attribute, IAuthorizationFilter

    1.首先,先确定使用的认证方案为Digest,并指定Realm,设置Qop的策略为auth,这里我们采用的预处理方式为在一定时间段内可以重用nonce,指定过期时间为10s

    public const string AuthenticationScheme = "Digest";
    public const string AuthenticationRealm = "http://localhost:32435";
    public const string Qop = "auth";
    //设置 nonce 过期时间为10s
    public const int MaxNonceAgeSeconds = 10;
    

    2.接着,我们再把常用的常量封装一下

    public static class AuthenticateHeaderNames
    {
        public const string UserName = "username";
        public const string Realm = "realm";
        public const string Nonce = "nonce";
        public const string ClientNonce = "cnonce";
        public const string NonceCounter = "nc";
        public const string Qop = "qop";
        public const string Response = "response";
        public const string Uri = "uri";
        public const string RspAuth = "rspauth";
        public const string Stale = "stale";
    }
    
    public static class QopValues
    {
        public const string Auth = "auth";
        public const string AuthInt = "auth-int";
    }
    

    3.在没有进行认证或认证失败时,服务端需要返回401 Unauthorized,并对客户端发出质询,一下是质询需要包含的内容(“stale”参数指示nonce是否过期)

    private void AddChallenge(HttpResponse response, bool stale)
    {
        var partList = new List<ValueTuple<string, string, bool>>()
        {
            (AuthenticateHeaderNames.Realm, AuthenticationRealm, true),
            (AuthenticateHeaderNames.Qop, Qop, true),
            (AuthenticateHeaderNames.Nonce, GetNonce(), true),
        };
    
        var value = $"{AuthenticationScheme} {string.Join(", ", partList.Select(part => FormatHeaderPart(part)))}";
        if (stale)
        {
            value += $", {FormatHeaderPart((AuthenticateHeaderNames.Stale, "true", false))}";
        }
        response.Headers.Append(HeaderNames.WWWAuthenticate, value);
    }
    
    private string GetNonce(DateTimeOffset? timestamp = null)
    {
        var privateKey = "test private key";
        var timestampStr = timestamp?.ToString() ?? DateTimeOffset.UtcNow.ToString();
        return Convert.ToBase64String(_encoding.GetBytes($"{ timestampStr } {$"{timestampStr} : {privateKey}".ToMD5Hash()}"));
    }
    
    private string FormatHeaderPart((string Name, string Value, bool ShouldQuote) part)
        => part.ShouldQuote ? $"{part.Name}="{part.Value}"" : $"{part.Name}={part.Value}";
    

    4.客户端请求认证后,服务端需要使用HTTP Request中Authorization标头的参数进行摘要计算,所以我们需要将这些参数解析出来并封装成一个类对象AuthorizationHeader

    private AuthorizationHeader GetAuthenticationHeader(HttpRequest request)
    {
        try
        {
            var credentials = GetCredentials(request);
            if (credentials != null)
            {
                var authorizationHeader = new AuthorizationHeader()
                {
                    RequestMethod = request.Method,
                };
                var nameValueStrs = credentials.Replace(""", string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim());
                foreach (var nameValueStr in nameValueStrs)
                {
                    var index = nameValueStr.IndexOf('=');
                    var name = nameValueStr.Substring(0, index);
                    var value = nameValueStr.Substring(index + 1);
    
                    switch (name)
                    {
                        case AuthenticateHeaderNames.UserName:
                            authorizationHeader.UserName = value;
                            break;
                        case AuthenticateHeaderNames.Realm:
                            authorizationHeader.Realm = value;
                            break;
                        case AuthenticateHeaderNames.Nonce:
                            authorizationHeader.Nonce = value;
                            break;
                        case AuthenticateHeaderNames.ClientNonce:
                            authorizationHeader.ClientNonce = value;
                            break;
                        case AuthenticateHeaderNames.NonceCounter:
                            authorizationHeader.NonceCounter = value;
                            break;
                        case AuthenticateHeaderNames.Qop:
                            authorizationHeader.Qop = value;
                            break;
                        case AuthenticateHeaderNames.Response:
                            authorizationHeader.Response = value;
                            break;
                        case AuthenticateHeaderNames.Uri:
                            authorizationHeader.Uri = value;
                            break;
                    }
                }
    
                return authorizationHeader;
            }
        }
        catch { }
    
        return null;
    }
    
    private string GetCredentials(HttpRequest request)
    {
        string credentials = null;
    
        string authorization = request.Headers[HeaderNames.Authorization];
        //请求中存在 Authorization 标头且认证方式为 Digest
        if (authorization?.StartsWith(AuthenticationScheme, StringComparison.OrdinalIgnoreCase) == true)
        {
            credentials = authorization.Substring(AuthenticationScheme.Length).Trim();
        }
    
        return credentials;
    }
    
    public class AuthorizationHeader
    {
        public string UserName { get; set; }
        public string Realm { get; set; }
        public string Nonce { get; set; }
        public string ClientNonce { get; set; }
        public string NonceCounter { get; set; }
        public string Qop { get; set; }
        public string Response { get; set; }
        public string RequestMethod { get; set; }
        public string Uri { get; set; }
    }
    

    5.进行摘要计算的参数信息已经齐备了,不过别着急,先来校验一下nonce的有效性。

    /// <summary>
    /// 验证Nonce是否有效
    /// </summary>
    /// <param name="nonce"></param>
    /// <returns>true:验证通过;false:验证失败;null:随机数过期</returns>
    private bool? ValidateNonce(string nonce)
    {
        try
        {
            var plainNonce = _encoding.GetString(Convert.FromBase64String(nonce));
            var timestamp = DateTimeOffset.Parse(plainNonce.Substring(0, plainNonce.LastIndexOf(' ')));
            //验证Nonce是否被篡改
            var isValid = nonce == GetNonce(timestamp);
    
            //验证是否过期
            if (Math.Abs((timestamp - DateTimeOffset.UtcNow).TotalSeconds) > MaxNonceAgeSeconds)
            {
                return isValid ? (bool?)null : false;
            }
    
            return isValid;
        }
        catch
        {
            return false;
        }
    }
    

    6.好,接下来就来进行摘要计算吧,其实就是套用公式,如果不记得了,可以重温一下第一节。

    private static string GetComputedResponse(AuthorizationHeader authorizationHeader, string password)
    {
        var a1Hash = $"{authorizationHeader.UserName}:{authorizationHeader.Realm}:{password}".ToMD5Hash();
        var a2Hash = $"{authorizationHeader.RequestMethod}:{authorizationHeader.Uri}".ToMD5Hash();
        return $"{a1Hash}:{authorizationHeader.Nonce}:{authorizationHeader.NonceCounter}:{authorizationHeader.ClientNonce}:{authorizationHeader.Qop}:{a2Hash}".ToMD5Hash();
    }
    

    7.如果认证通过,我们通过Authorization-Info返回一些授权会话的信息。

    private void AddAuthorizationInfo(HttpResponse response, AuthorizationHeader authorizationHeader, string password)
    {
        var partList = new List<ValueTuple<string, string, bool>>()
        {
            (AuthenticateHeaderNames.Qop, authorizationHeader.Qop, true),
            (AuthenticateHeaderNames.RspAuth, GetRspAuth(authorizationHeader, password), true),
            (AuthenticateHeaderNames.ClientNonce, authorizationHeader.ClientNonce, true),
            (AuthenticateHeaderNames.NonceCounter, authorizationHeader.NonceCounter, false)
        };
        response.Headers.Append("Authorization-Info", string.Join(", ", partList.Select(part => FormatHeaderPart(part))));
    }
    
    private string GetRspAuth(AuthorizationHeader authorizationHeader, string password)
    {
        var a1Hash = $"{authorizationHeader.UserName}:{authorizationHeader.Realm}:{password}".ToMD5Hash();
        var a2Hash = $":{authorizationHeader.Uri}".ToMD5Hash();
        return $"{a1Hash}:{authorizationHeader.Nonce}:{authorizationHeader.NonceCounter}:{authorizationHeader.ClientNonce}:{authorizationHeader.Qop}:{a2Hash}".ToMD5Hash();
    }
    

    8.我们把整个认证流程整理一下

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        //请求允许匿名访问
        if (context.Filters.Any(item => item is IAllowAnonymousFilter)) return;
    
        var authorizationHeader = GetAuthenticationHeader(context.HttpContext.Request);
        var stale = false;
        if(authorizationHeader != null)
        {
            var isValid = ValidateNonce(authorizationHeader.Nonce);
            //随机数过期
            if(isValid == null)
            {
                stale = true;
            }
            else if(isValid == true)
            {
                var password = UserService.GetPassword(authorizationHeader.UserName);
                string computedResponse = null;
                switch (authorizationHeader.Qop)
                {
                    case QopValues.Auth:
                        computedResponse = GetComputedResponse(authorizationHeader, password);
                        break;
                    default:
                        context.Result = new BadRequestObjectResult($"qop指定策略必须为"{QopValues.Auth}"");
                        break;
                }
    
                if (computedResponse == authorizationHeader.Response)
                {
                    AddAuthorizationInfo(context.HttpContext.Response, authorizationHeader, password);
                    return;
                }
            }
        }
    
        context.Result = new UnauthorizedResult();
        AddChallenge(context.HttpContext.Response, stale);
    }
    

    9.最后,在需要认证的Action上加上自定义过滤器特性,大功告成!自己测试一下吧!

    三、封装为中间件

    照例,接下来我们将摘要认证封装为ASP.NET Core中间件,便于使用和扩展。以下封装采用Jwt Bearer封装规范。以下代码较长,推荐直接去看源码。

    1. 首先封装常量(之前提到过的就不说了)
    public static class DigestDefaults
    {
        public const string AuthenticationScheme = "Digest";
    }
    

    2.然后封装Basic认证的Options,包括Realm、Qop、Private key和事件,继承自Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions。在事件内部,我们定义了获取密码行为和质询行为,分别用来根据用户名获取密码和在HTTP Response中添加质询信息。要注意的是,获取密码行为要求必须由用户实现,毕竟我们内部是不知道密码的。

    public class DigestOptions : AuthenticationSchemeOptions
    {
        public const string DefaultQop = QopValues.Auth;
        public const int DefaultMaxNonceAgeSeconds = 10;
    
        public string Realm { get; set; }
        public string Qop { get; set; } = DefaultQop;
        public int MaxNonceAgeSeconds { get; set; } = DefaultMaxNonceAgeSeconds;
        public string PrivateKey { get; set; }
    
        public new DigestEvents Events
        {
            get => (DigestEvents)base.Events;
            set => base.Events = value;
        }
    }
    
    public class DigestEvents
    {
        public DigestEvents(Func<GetPasswordContext, Task<string>> onGetPassword)
        {
            OnGetPassword = onGetPassword;
        }
    
        public Func<GetPasswordContext, Task<string>> OnGetPassword { get; set; } = context => throw new NotImplementedException($"{nameof(OnGetPassword)} must be implemented!");
    
        public Func<DigestChallengeContext, Task> OnChallenge { get; set; } = context => Task.CompletedTask;
    
        public virtual Task<string> GetPassword(GetPasswordContext context) => OnGetPassword(context);
    
        public virtual Task Challenge(DigestChallengeContext context) => OnChallenge(context);
    }
    
    public class GetPasswordContext : ResultContext<DigestOptions>
    {
        public GetPasswordContext(
            HttpContext context, 
            AuthenticationScheme scheme, 
            DigestOptions options) 
            : base(context, scheme, options)
        {
        }
    
        public string UserName { get; set; }
    }
    
    public class DigestChallengeContext : PropertiesContext<DigestOptions>
    {
        public DigestChallengeContext(
            HttpContext context, 
            AuthenticationScheme scheme, 
            DigestOptions options, 
            AuthenticationProperties properties) 
            : base(context, scheme, options, properties)
        {
        }
    
        /// <summary>
        /// 在认证期间出现的异常
        /// </summary>
        public Exception AuthenticateFailure { get; set; }
    
        public bool Stale { get; set; }
    
        /// <summary>
        /// 指定是否已被处理,如果已处理,则跳过默认认证逻辑
        /// </summary>
        public bool Handled { get; private set; }
    
        /// <summary>
        /// 跳过默认认证逻辑
        /// </summary>
        public void HandleResponse() => Handled = true;
    }
    

    3.接下来,就是对认证过程处理的封装了,需要继承自Microsoft.AspNetCore.Authentication.AuthenticationHandler

    public class DigestHandler : AuthenticationHandler<DigestOptions>
    {
    private static readonly Encoding _encoding = Encoding.UTF8;
    
    public DigestHandler(
        IOptionsMonitor<DigestOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }
    
    protected new DigestEvents Events
    {
        get => (DigestEvents)base.Events;
        set => base.Events = value;
    }
    
    /// <summary>
    /// 确保创建的 Event 类型是 DigestEvents
    /// </summary>
    /// <returns></returns>
    protected override Task<object> CreateEventsAsync() => throw new NotImplementedException($"{nameof(Events)} must be created");
    
    protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var authorizationHeader = GetAuthenticationHeader(Context.Request);
        if (authorizationHeader == null)
        {
            return AuthenticateResult.NoResult();
        }
    
        try
        {
            var isValid = ValidateNonce(authorizationHeader.Nonce);
            //随机数过期
            if (isValid == null)
            {
                var properties = new AuthenticationProperties();
                properties.SetParameter(AuthenticationHeaderNames.Stale, true);
                return AuthenticateResult.Fail(string.Empty, properties);
            }
            else if (isValid == true)
            {
                var getPasswordContext = new GetPasswordContext(Context, Scheme, Options)
                {
                    UserName = authorizationHeader.UserName
                };
                var password = await Events.GetPassword(getPasswordContext);
                string computedResponse = null;
                switch (authorizationHeader.Qop)
                {
                    case QopValues.Auth:
                        computedResponse = GetComputedResponse(authorizationHeader, password);
                        break;
                    default:
                        return AuthenticateResult.Fail($"qop指定策略必须为"{QopValues.Auth}"");
                }
    
                if (computedResponse == authorizationHeader.Response)
                {
                    var claim = new Claim(ClaimTypes.Name, getPasswordContext.UserName);
                    var identity = new ClaimsIdentity(DigestDefaults.AuthenticationScheme);
                    identity.AddClaim(claim);
    
                    var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), Scheme.Name);
                    AddAuthorizationInfo(Context.Response, authorizationHeader, password);
                    return AuthenticateResult.Success(ticket);
                }
            }
    
            return AuthenticateResult.NoResult();
        }
        catch (Exception ex)
        {
            return AuthenticateResult.Fail(ex.Message);
        }
    
    }
    
    protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        var authResult = await HandleAuthenticateOnceSafeAsync();
        var challengeContext = new DigestChallengeContext(Context, Scheme, Options, properties)
        {
            AuthenticateFailure = authResult.Failure,
            Stale = authResult.Properties?.GetParameter<bool>(AuthenticationHeaderNames.Stale) ?? false
        };
        await Events.Challenge(challengeContext);
        //质询已处理
        if (challengeContext.Handled) return;
    
        var challengeValue = GetChallengeValue(challengeContext.Stale);
        var error = challengeContext.AuthenticateFailure?.Message;
        if (!string.IsNullOrWhiteSpace(error))
        {
            //将错误信息封装到内部
            challengeValue += $", error="{ error }"";
        }
    
        Response.StatusCode = (int)HttpStatusCode.Unauthorized;
        Response.Headers.Append(HeaderNames.WWWAuthenticate, challengeValue);
    }
    
    private AuthorizationHeader GetAuthenticationHeader(HttpRequest request)
    {
        try
        {
            var credentials = GetCredentials(request);
            if (credentials != null)
            {
                var authorizationHeader = new AuthorizationHeader()
                {
                    RequestMethod = request.Method,
                };
                var nameValueStrs = credentials.Replace(""", string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim());
                foreach (var nameValueStr in nameValueStrs)
                {
                    var index = nameValueStr.IndexOf('=');
                    var name = nameValueStr.Substring(0, index);
                    var value = nameValueStr.Substring(index + 1);
    
                    switch (name)
                    {
                        case AuthenticationHeaderNames.UserName:
                            authorizationHeader.UserName = value;
                            break;
                        case AuthenticationHeaderNames.Realm:
                            authorizationHeader.Realm = value;
                            break;
                        case AuthenticationHeaderNames.Nonce:
                            authorizationHeader.Nonce = value;
                            break;
                        case AuthenticationHeaderNames.ClientNonce:
                            authorizationHeader.ClientNonce = value;
                            break;
                        case AuthenticationHeaderNames.NonceCounter:
                            authorizationHeader.NonceCounter = value;
                            break;
                        case AuthenticationHeaderNames.Qop:
                            authorizationHeader.Qop = value;
                            break;
                        case AuthenticationHeaderNames.Response:
                            authorizationHeader.Response = value;
                            break;
                        case AuthenticationHeaderNames.Uri:
                            authorizationHeader.Uri = value;
                            break;
                    }
                }
    
                return authorizationHeader;
            }
        }
        catch { }
    
        return null;
    }
    
    private string GetCredentials(HttpRequest request)
    {
        string credentials = null;
    
        string authorization = request.Headers[HeaderNames.Authorization];
        //请求中存在 Authorization 标头且认证方式为 Digest
        if (authorization?.StartsWith(DigestDefaults.AuthenticationScheme, StringComparison.OrdinalIgnoreCase) == true)
        {
            credentials = authorization.Substring(DigestDefaults.AuthenticationScheme.Length).Trim();
        }
    
        return credentials;
    }
    
    /// <summary>
    /// 验证Nonce是否有效
    /// </summary>
    /// <param name="nonce"></param>
    /// <returns>true:验证通过;false:验证失败;null:随机数过期</returns>
    private bool? ValidateNonce(string nonce)
    {
        try
        {
            var plainNonce = _encoding.GetString(Convert.FromBase64String(nonce));
            var timestamp = DateTimeOffset.Parse(plainNonce.Substring(0, plainNonce.LastIndexOf(' ')));
            //验证Nonce是否被篡改
            var isValid = nonce == GetNonce(timestamp);
    
            //验证是否过期
            if (Math.Abs((timestamp - DateTimeOffset.UtcNow).TotalSeconds) > Options.MaxNonceAgeSeconds)
            {
                return isValid ? (bool?)null : false;
            }
    
            return isValid;
        }
        catch
        {
            return false;
        }
    }
    
    private static string GetComputedResponse(AuthorizationHeader authorizationHeader, string password)
    {
        var a1Hash = $"{authorizationHeader.UserName}:{authorizationHeader.Realm}:{password}".ToMD5Hash();
        var a2Hash = $"{authorizationHeader.RequestMethod}:{authorizationHeader.Uri}".ToMD5Hash();
        return $"{a1Hash}:{authorizationHeader.Nonce}:{authorizationHeader.NonceCounter}:{authorizationHeader.ClientNonce}:{authorizationHeader.Qop}:{a2Hash}".ToMD5Hash();
    }
    
    
    private void AddAuthorizationInfo(HttpResponse response, AuthorizationHeader authorizationHeader, string password)
    {
        var partList = new List<ValueTuple<string, string, bool>>()
        {
            (AuthenticationHeaderNames.Qop, authorizationHeader.Qop, true),
            (AuthenticationHeaderNames.RspAuth, GetRspAuth(authorizationHeader, password), true),
            (AuthenticationHeaderNames.ClientNonce, authorizationHeader.ClientNonce, true),
            (AuthenticationHeaderNames.NonceCounter, authorizationHeader.NonceCounter, false)
        };
        response.Headers.Append("Authorization-Info", string.Join(", ", partList.Select(part => FormatHeaderPart(part))));
    }
    
    private string GetChallengeValue(bool stale)
    {
        var partList = new List<ValueTuple<string, string, bool>>()
        {
            (AuthenticationHeaderNames.Realm, Options.Realm, true),
            (AuthenticationHeaderNames.Qop, Options.Qop, true),
            (AuthenticationHeaderNames.Nonce, GetNonce(), true),
        };
    
        var value = $"{DigestDefaults.AuthenticationScheme} {string.Join(", ", partList.Select(part => FormatHeaderPart(part)))}";
        if (stale)
        {
            value += $", {FormatHeaderPart((AuthenticationHeaderNames.Stale, "true", false))}";
        }
        return value;
    }
    
    private string GetRspAuth(AuthorizationHeader authorizationHeader, string password)
    {
        var a1Hash = $"{authorizationHeader.UserName}:{authorizationHeader.Realm}:{password}".ToMD5Hash();
        var a2Hash = $":{authorizationHeader.Uri}".ToMD5Hash();
        return $"{a1Hash}:{authorizationHeader.Nonce}:{authorizationHeader.NonceCounter}:{authorizationHeader.ClientNonce}:{authorizationHeader.Qop}:{a2Hash}".ToMD5Hash();
    }
    
    private string GetNonce(DateTimeOffset? timestamp = null)
    {
        var privateKey = Options.PrivateKey;
        var timestampStr = timestamp?.ToString() ?? DateTimeOffset.UtcNow.ToString();
        return Convert.ToBase64String(_encoding.GetBytes($"{ timestampStr } {$"{timestampStr} : {privateKey}".ToMD5Hash()}"));
    }
    
    private string FormatHeaderPart((string Name, string Value, bool ShouldQuote) part)
        => part.ShouldQuote ? $"{part.Name}="{part.Value}"" : $"{part.Name}={part.Value}";
    

    4.最后,就是要把封装的接口暴露给用户了。

    public static class DigestExtensions
    {
        public static AuthenticationBuilder AddDigest(this AuthenticationBuilder builder)
           => builder.AddDigest(DigestDefaults.AuthenticationScheme, _ => { });
    
        public static AuthenticationBuilder AddDigest(this AuthenticationBuilder builder, Action<DigestOptions> configureOptions)
            => builder.AddDigest(DigestDefaults.AuthenticationScheme, configureOptions);
    
        public static AuthenticationBuilder AddDigest(this AuthenticationBuilder builder, string authenticationScheme, Action<DigestOptions> configureOptions)
            => builder.AddDigest(authenticationScheme, displayName: null, configureOptions: configureOptions);
    
        public static AuthenticationBuilder AddDigest(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<DigestOptions> configureOptions)
            => builder.AddScheme<DigestOptions, DigestHandler>(authenticationScheme, displayName, configureOptions);
    }
    

    5.Digest认证库已经封装好了,我们创建一个ASP.NET Core WebApi程序来测试一下吧。

    //在 ConfigureServices 中配置认证中间件
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(DigestDefaults.AuthenticationScheme)
            .AddDigest(options =>
            {
                options.Realm = "http://localhost:44550";
                options.PrivateKey = "test private key";
                options.Events = new DigestEvents(context => Task.FromResult(context.UserName));
            });
    }
    
    //在 Configure 中启用认证中间件
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseAuthentication();
    }
    

    最后,一定要记得为需要认证的Action添加[Authorize]特性,否则前面做的一切都是徒劳+_+

    查看源码

  • 相关阅读:
    CSS Class 选择器
    CSS ID 选择器
    一个可以兼容各种数据库事务的使用范例
    参数化构造的通用查询方法
    QUI操作超时弹出登录窗口登录的处理方式
    一款代码生成器的妙用
    mongoDB操作命令及mongoDB的helper
    记录asp.net网站停止运行原因的代码
    一个JS版本的MD5
    获取地理位置的html5代码
  • 原文地址:https://www.cnblogs.com/xiaoxiaotank/p/11079024.html
Copyright © 2011-2022 走看看