1. 程式人生 > >Asp.Net Core WebApi 身份驗證、註冊、使用者管理

Asp.Net Core WebApi 身份驗證、註冊、使用者管理


用了兩天的時間研究了在Asp.Net WebApi 專案中如何實現註冊、登入、身份驗證功能。此專案使用JWT(Json Web Token)來進行身份驗證。

使用者服務

ASP.NET Core使用者服務負責與使用者身份驗證,註冊和管理相關的所有資料庫互動和核心業務邏輯。

該檔案的頂部包含一個定義使用者服務的介面,下面是實現該介面的具體使用者服務類。該類的底部包含一些私有方法,用於建立和驗證儲存在資料庫中的雜湊密碼。

  1. 通過Emain和Password來登入服務,Authenticate方法呼叫VerifyPasswordHash來驗證Email和密碼是否正確來進行登入授權。
public User Authenticate(string email, string password)
        {
            if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password))
                return null;

            var user = _iUserRepository.GetUserByEmail(email);

            // check if username exists
            if (user == null)
                return null;

            // check if password is correct
            if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
                return null;

            // authentication successful
            return user;
        }


private static bool VerifyPasswordHash(string password, byte[] storedHash, byte[] storedSalt)
        {
            if (password == null) throw new ArgumentNullException(nameof(password));
            if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
            if (storedHash.Length != 64) throw new ArgumentException("Invalid length of password hash (64 bytes expected).", "passwordHash");
            if (storedSalt.Length != 128) throw new ArgumentException("Invalid length of password salt (128 bytes expected).", "passwordHash");

            using (var hmac = new System.Security.Cryptography.HMACSHA512(storedSalt))
            {
                var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
                for (int i = 0; i < computedHash.Length; i++)
                {
                    if (computedHash[i] != storedHash[i]) return false;
                }
            }

            return true;
        }

  1. 使用者註冊時需要建立新使用者並將使用者資料存放到資料庫中
public User Create(User user, string password)
       {
           // validation
           if (string.IsNullOrWhiteSpace(password))
               throw new AppException("Password is required");

           if (_iUserRepository.Any(x => x.Email == user.Email))
               throw new AppException("Email \"" + user.Email + "\" is already taken");

           if (_iUserRepository.Any(x => x.UserName == user.UserName))
               throw new AppException("Username \"" + user.UserName + "\" is already taken");

           CreatePasswordHash(password, out var passwordHash, out var passwordSalt);

           user.PasswordHash = passwordHash;
           user.PasswordSalt = passwordSalt;

           _iUserRepository.Add(user);

           return user;
       }

對密碼進行加密儲存

private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
       {
           if (password == null) throw new ArgumentNullException(nameof(password));
           if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");

           using (var hmac = new System.Security.Cryptography.HMACSHA512())
           {
               passwordSalt = hmac.Key;
               passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
           }
       }
  1. 使用者資訊更新及刪除程式碼
public void Update(User userParam, string password = null)
       {
           var user = _iUserRepository.GetById(userParam.Id);

           if (user == null)
               throw new AppException("User not found");

           if (userParam.UserName != user.UserName)
           {
               // username has changed so check if the new username is already taken
               if (_iUserRepository.Any(x => x.UserName == userParam.UserName))
                   throw new AppException("Username " + userParam.UserName + " is already taken");
           }

           // update user properties
           user.FirstName = userParam.FirstName;
           user.LastName = userParam.LastName;
           user.UserName = userParam.UserName;

           // update password if it was entered
           if (!string.IsNullOrWhiteSpace(password))
           {
               CreatePasswordHash(password, out var passwordHash, out var passwordSalt);

               user.PasswordHash = passwordHash;
               user.PasswordSalt = passwordSalt;
           }

           _iUserRepository.Update(user);
       }

       public void Delete(string id)
       {
           var user = _iUserRepository.GetById(id);
           if (user != null)
           {
               _iUserRepository.Remove(user);
           }
       }

使用者實體、使用者模型及資料上下文

public class User
    {
        public string Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UserName { get; set; }
        public string Email { get; set; }
        public byte[] PasswordHash { get; set; }
        public byte[] PasswordSalt { get; set; }
    }
public class UserModel
    {
        public string Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UserName { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
    }
public class VueContext : DbContext
    {
        public VueContext(DbContextOptions<VueContext> options)
            : base(options)
        {
        }

        public DbSet<User> User { get; set; }
    }

應用程式設定檔案

/appsettings.json

{
  "AppSettings": {
    "Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "VueDatabase": "serverName;Database=VueDb;Trusted_Connection=True;"
  }
}

在Startup.cs中配置身份驗證

新增以下程式碼到ConfigureServices方法中以新增身份驗證服務

// configure strongly typed settings objects
            var appSettingsSection = Configuration.GetSection("AppSettings");
            services.Configure<AppSettings>(appSettingsSection);

            // configure jwt authentication
            var appSettings = appSettingsSection.Get<AppSettings>();
            var key = Encoding.ASCII.GetBytes(appSettings.Secret);
            services.AddAuthentication(x =>
                {
                    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(x =>
                {
                    x.Events = new JwtBearerEvents
                    {
                        OnTokenValidated = context =>
                        {
                            var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
                            var userId = context.Principal.Identity.Name;
                            var user = userService.GetById(userId);
                            if (user == null)
                            {
                                // return unauthorized if user no longer exists
                                context.Fail("Unauthorized");
                            }
                            return Task.CompletedTask;
                        }
                    };
                    x.RequireHttpsMetadata = false;
                    x.SaveToken = true;
                    x.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(key),
                        ValidateIssuer = false,
                        ValidateAudience = false
                    };
                });

對應用配置身份驗證,新增以下程式碼到Configure中

app.UseAuthentication();

核心程式碼大概就這些。最近看了微軟官方文件,但是並沒有發現詳細教程,最終發現一篇部落格,提取核心內容搬運記錄下來。

原始碼獲取

參考文獻