Asp.Net Core WebApi 身份驗證、註冊、使用者管理
阿新 • • 發佈:2019-02-06
用了兩天的時間研究了在Asp.Net WebApi 專案中如何實現註冊、登入、身份驗證功能。此專案使用JWT(Json Web Token)來進行身份驗證。
使用者服務
ASP.NET Core使用者服務負責與使用者身份驗證,註冊和管理相關的所有資料庫互動和核心業務邏輯。
該檔案的頂部包含一個定義使用者服務的介面,下面是實現該介面的具體使用者服務類。該類的底部包含一些私有方法,用於建立和驗證儲存在資料庫中的雜湊密碼。
- 通過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; }
- 使用者註冊時需要建立新使用者並將使用者資料存放到資料庫中
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));
}
}
- 使用者資訊更新及刪除程式碼
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();
核心程式碼大概就這些。最近看了微軟官方文件,但是並沒有發現詳細教程,最終發現一篇部落格,提取核心內容搬運記錄下來。