zoukankan      html  css  js  c++  java
  • Asp.net core 学习笔记 ( Identity 之 Authentication )

    和从前的 identity 区别不是很大.

    从 2.1 开始 vs 模板的 identity 都被封装了起来, 你几乎看不到任何一行代码, 需要向下面这样打开它, 才能做修改. 

    说一下比较常用的配置

    services.Configure<DataProtectionTokenProviderOptions>(
            x => x.TokenLifespan = TimeSpan.FromHours(1));
    
    services.AddDefaultIdentity<ApplicationUser>(options =>
    {
        options.SignIn.RequireConfirmedEmail = false;
        options.SignIn.RequireConfirmedPhoneNumber = false;
        //options.Tokens.EmailConfirmationTokenProvider = "MyEmailTokenProvider";
    
        options.Lockout.AllowedForNewUsers = true;
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1);
        options.Lockout.MaxFailedAccessAttempts = 5;
    
        options.Password.RequireDigit = false;
        options.Password.RequiredLength = 6;
        options.Password.RequiredUniqueChars = 0;
        options.Password.RequireLowercase = false;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequireUppercase = false;
    
        options.User.RequireUniqueEmail = true;
    })
    .AddSecurityPhoneTokenProvider()
    .AddRoles<ApplicationRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();
    TokenLifespan 是说在 reset password 之类的情况, token 的时长.

    options.Lockout 就是说如果用户多次尝试 password login 账号会被锁起来, 防止暴力破解密码.
    options.Password 就是 password 强度
    AddSecurityPhoneTokenProvider 是我自己写的一个东西. 随便说一下吧. 

    在做 reset password 时, identity 默认会生成一个很长的 token, 这个叫 DefaultTokenProvider, 时长就是 TokenLefespan,
    不过这种长 token 只适合使用 email + link 的方式来 reset password
    如果用户没有 email 只有手机号, 那就不管用了.
    好在 identity 也有比较短的 token 适合手机的, 叫 totp (time based on time password)
    原理是使用当前时间+密钥+算法来生成 token, 不过这个其实是用在 2fa 的, 并不适合用于 reset password.
    因为这种 token 默认可以保持 3 分钟, 可以无限尝试. 很容易暴力破解.
    identity 默认的 phoneConfirm 就是用这个方式的.
    但是对我来说还不够安全, 所以我自己加了一个扩展, 通过数据库记入尝试次数, 防止暴力破解.
    public static class IdentityBuilderExtensions
    {
        public static IdentityBuilder AddSecurityPhoneTokenProvider(this IdentityBuilder builder)
        {
            var userType = builder.UserType;
            var provider = typeof(SecurityPhoneNumberTokenProvider<>).MakeGenericType(userType);
            return builder.AddTokenProvider(TokenOptions.SecurityPhoneProvider, provider);
        }
    }
    
    //public static class ClaimsPrincipalExtensions
    //{
    //    public static int GetUserId(this ClaimsPrincipal principal)
    //    {
    //        if (principal == null) throw new ArgumentNullException(nameof(principal));
    //        var stringId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    //        if (stringId == null) throw new Exception("User Id not found in claim");
    //        return Convert.ToInt32(stringId);
    //    }
    //}
    
    
    public class TokenOptions
    {
        public static readonly string SecurityPhoneProvider = "SecurityPhone";
    }
    
    public class SecurityPhoneNumberTokenProvider<TUser> : PhoneNumberTokenProvider<TUser> where TUser : ApplicationUser
    {
        private const int MaxFailedCount = 3;
        private const int TokenExpiryInMinutes = 3;
        private ApplicationDbContext Db { get; set; }
    
        public SecurityPhoneNumberTokenProvider(ApplicationDbContext db)
        {
            Db = db;
        }
    
        public override async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user)
        {
            var token = await base.GenerateAsync(purpose, manager, user);
            Db.UserSecurityTokens.Add(new UserSecurityToken
            {
                userId = user.Id,
                token = token
            });
            await Db.SaveChangesAsync();
            return token;
        }
    
        public override async Task<bool> ValidateAsync(string purpose, string token, UserManager<TUser> manager, TUser user)
        {
            var ok = await base.ValidateAsync(purpose, token, manager, user);
            if (ok)
            {
                var userToken = await Db.UserSecurityTokens.FirstOrDefaultAsync(u => u.userId == user.Id && u.token == token);
                return userToken.failedCount <= MaxFailedCount;
            }
            else
            {
                var userTokens = await Db.UserSecurityTokens.Where(u => u.userId == user.Id).ToListAsync();
                foreach (var userToken in userTokens)
                {
                    if (userToken.rowCreated.AddMinutes(TokenExpiryInMinutes) <= DateTime.Now)
                    {
                        Db.UserSecurityTokens.Remove(userToken);
                    }
                    else
                    {
                        userToken.failedCount++;
                    }
                }
                await Db.SaveChangesAsync();
                return false;
            }
        }
    }

    支持第三方登入 

    services.AddAuthentication()
        .AddGoogle(options =>
        {
            options.ClientId = "xx";
            options.ClientSecret = "yy";
            options.Events.OnCreatingTicket = (context) =>
            {
                context.Identity.AddClaim(new Claim("image", context.User.GetValue("image").SelectToken("url").ToString()));
                context.Identity.AddClaim(new Claim("gender", context.User.GetValue("gender").ToString()));
                context.Identity.AddClaim(new Claim("language", context.User.GetValue("language").ToString()));
                var g = context.User.GetValue("whatever"); // null
                return Task.CompletedTask;
            };
        })

    通过 onCreatingTicket 可以把资料放入特定的 claim 中, 比如 accessToken 等.

    facebook 的比较麻烦

     .AddFacebook(options =>
                 {        
                     // app setup https://developers.facebook.com/apps/482615952162581/settings/basic/  
                     // user remove fb login https://facebook.com/settings?tab=applications
                     // scope https://developers.facebook.com/docs/facebook-login/permissions/#reference-default_fields
                     // field https://developers.facebook.com/docs/graph-api/reference/v2.2/user
                     options.ClientId = "xx";
                     options.ClientSecret = "yy";
                     options.Scope.Add("email");
                     options.Scope.Add("user_gender"); 
                     options.Scope.Add("user_age_range");
                     options.Scope.Add("user_birthday");
                     options.Scope.Add("user_location");
                     options.Fields.Add("picture");
                     options.Fields.Add("email");
                     options.Fields.Add("gender");
                     options.Fields.Add("age_range");
                     options.Fields.Add("birthday");
                     options.Fields.Add("location");
    
                     options.Events.OnRedirectToAuthorizationEndpoint = (context) =>
                     {
                         // auth_type=rerequest 可以再次要求 scope (它很聪明的, 如果 scope 都 allow 了是不会 rerequest 的)
                         var rerequestQueryParams = new Dictionary<string, string> { { "auth_type", "rerequest" } };
                         var newUri = QueryHelpers.AddQueryString(context.RedirectUri, rerequestQueryParams);
                         context.HttpContext.Response.Redirect(newUri);
                         return Task.CompletedTask;
                     };
                     options.Events.OnCreatingTicket = (context) =>
                     {
                         var x = context.User;
                         //context.Identity.AddClaim(new Claim("image", context.User.GetValue("image").SelectToken("url").ToString()));
                         //context.Identity.AddClaim(new Claim("gender", context.User.GetValue("gender").ToString()));
                         //context.Identity.AddClaim(new Claim("language", context.User.GetValue("language").ToString()));
                         //var g = context.User.GetValue("whatever"); // null
                         return Task.CompletedTask;
                     };
                 });

    通过 scope 和 fields 添加更多的资料. rerequest 用于再次要求额外 scope . 

    更灵活的 facebook 图片获取 https://stackoverflow.com/questions/11442442/get-user-profile-picture-by-id

    最后通过 controller 通过 signInMangager 获取 claim 就可以了. 

    var info = await _signInManager.GetExternalLoginInfoAsync();             
    string email = info.Principal.FindFirstValue(ClaimTypes.Email);





     
  • 相关阅读:
    ES6初识-(冲突)数据结构
    ES6初识-Proxy和Reflect
    ES6初识- Class
    实时显示从file输入框中打开的图片C:fakepath路径问题
    php跨域访问
    移动端web开发技巧
    phpqrcode生成带logo的二维码图片
    iwebshop 自动给css js链接加版本信息
    微信统一支付接口返回“签名错误”的可能原因
    php smtp发送邮件功能
  • 原文地址:https://www.cnblogs.com/keatkeat/p/9308709.html
Copyright © 2011-2022 走看看