カスタムユーザとの5つの関連付けられた
65631 ワード
イントロ
今回はカスタムユーザーと署名してみます.
デフォルトのASPです.ネットコアアイデンティティのユーザーはとても多くのプロパティを持っているし、彼らはあまりにも私のためです.
これを行うには、何かしなければならない.
環境
サンプル
カスタムユーザーの作成
カスタムユーザーを作成するには、“IdentityUser”を継承します.
そして私は“組織”と“lastupdatedate”を追加しました.
アプリケーションユーザー.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Identity;
namespace ApprovementWorkflowSample.Applications
{
// To use int as ID type, I inherited "IdentityUser<int>".
public class ApplicationUser: IdentityUser<int>
{
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public override int Id { get; set; }
[Required]
[Column("user_name")]
public override string UserName { get; set; } = "";
[Column("organization")]
public string? Organization { get; set; }
[Required]
[Column("mail")]
public override string Email { get; set; } = "";
[Required]
[Column("password")]
public override string PasswordHash { get; set; } = "";
[Required]
[Column("last_update_date", TypeName = "timestamp with time zone")]
public DateTime LastUpdateDate { get; set; }
[NotMapped]
public override bool EmailConfirmed { get; set; }
[NotMapped]
public override string NormalizedUserName {
get
{
return UserName.ToUpper();
}
set { /* DO nothing*/ }
}
[NotMapped]
public override string NormalizedEmail {
get
{
return Email.ToUpper();
}
set { /* DO nothing*/ }
}
[NotMapped]
public override bool LockoutEnabled { get; set; }
[NotMapped]
public override int AccessFailedCount { get; set; }
[NotMapped]
public override string? PhoneNumber { get; set; }
[NotMapped]
public override string? ConcurrencyStamp { get; set; }
[NotMapped]
public override string? SecurityStamp { get; set; }
[NotMapped]
public override DateTimeOffset? LockoutEnd { get; set; }
[NotMapped]
public override bool TwoFactorEnabled { get; set; }
[NotMapped]
public override bool PhoneNumberConfirmed { get; set; }
public void Update(ApplicationUser user)
{
UserName = user.UserName;
Organization = user.Organization;
Email = user.Email;
PasswordHash = user.PasswordHash;
}
public void Update(string userName, string? organization,
string email, string password)
{
UserName = userName;
Organization = organization;
Email = email;
// set hashed password text to PasswordHash.
PasswordHash = new PasswordHasher<ApplicationUser>()
.HashPassword(this, password);
}
public string Validate()
{
if(string.IsNullOrEmpty(UserName))
{
return "UserName is required";
}
if(string.IsNullOrEmpty(Email))
{
return "E-Mail address is required";
}
if(string.IsNullOrEmpty(PasswordHash))
{
return "Password is required";
}
return "";
}
}
}
私はデータベースを作成し、このような“ApplicationUser”テーブルを追加します.ユニークな制約
デフォルトでは、usernameは一意であるように制限されます.
このサンプルでは、メールを制限する必要もあります.
ワークフローコンテキスト.cs
using ApprovementWorkflowSample.Applications;
using Microsoft.EntityFrameworkCore;
namespace ApprovementWorkflowSample.Models
{
public class ApprovementWorkflowContext: DbContext
{
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ApplicationUser>()
.Property(w => w.LastUpdateDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
modelBuilder.Entity<ApplicationUser>()
.HasIndex(u => u.Email)
.IsUnique();
}
public DbSet<ApplicationUser> ApplicationUsers => Set<ApplicationUser>();
}
}
役割
今回は、私は特別な役割を追加しません.
だからデフォルトの“IdentityPost”を使います.
起動.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
...
namespace ApprovementWorkflowSample
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddDbContext<ApprovementWorkflowContext>(options =>
options.UseNpgsql(configuration["DbConnection"]));
// THIS PROJECT CAUSES AN ERROR
services.AddIdentity<ApplicationUser, IdentityRole<int>>()
.AddEntityFrameworkStores<ApprovementWorkflowContext>()
.AddDefaultTokenProviders();
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
アイデンティティー
"IdentityUser < int >"を使うときは"IdentityPost < int >"を使わなければなりません.
または、実行時に例外を取得します.
Stopped program because of exception System.ArgumentException: GenericArguments[1], 'Microsoft.AspNetCore.Identity.IdentityRole', on 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`4[TUser,TRole,TContext,TKey]' violates the constraint of type 'TRole'.
---> System.TypeLoadException: GenericArguments[1], 'Microsoft.AspNetCore.Identity.IdentityRole', on 'Microsoft.AspNetCore.Identity.UserStoreBase`8[TUser,TRole,TKey,TUserClaim,TUserRole,TUserLogin,TUserToken,TRoleClaim]' violates the constraint of type parameter 'TRole'.
...
カスタムユーザー
このプロジェクトには問題がある.
「ApplicationUser」のいくつかのプロパティはNULLです.
NullReferenceExceptionを回避するには、カスタムユーザーストアを追加する必要があります.
パスワードでサインするので、「iuserpasswordstore」を実装します.
ApplicationUserStorecs
using System;
using System.Threading;
using System.Threading.Tasks;
using ApprovementWorkflowSample.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
namespace ApprovementWorkflowSample.Applications
{
public class ApplicationUserStore: IUserPasswordStore<ApplicationUser>
{
private readonly ILogger<ApplicationUserStore> logger;
private readonly ApprovementWorkflowContext context;
public ApplicationUserStore(ILogger<ApplicationUserStore> logger,
ApprovementWorkflowContext context)
{
this.logger = logger;
this.context = context;
}
public async Task<IdentityResult> CreateAsync(ApplicationUser user, CancellationToken cancellationToken)
{
// validation
string validationError = user.Validate();
if(string.IsNullOrEmpty(validationError) == false)
{
return IdentityResult.Failed(new IdentityError { Description = validationError });
}
using(IDbContextTransaction transaction = context.Database.BeginTransaction())
{
if(await context.ApplicationUsers
.AnyAsync(u => u.Email == user.Email,
cancellationToken))
{
return IdentityResult.Failed(new IdentityError { Description = "Your e-mail address is already used" });
}
var newUser = new ApplicationUser();
newUser.Update(user);
await context.ApplicationUsers.AddAsync(newUser, cancellationToken);
await context.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync();
return IdentityResult.Success;
}
}
public async Task<IdentityResult> DeleteAsync(ApplicationUser user, CancellationToken cancellationToken)
{
ApplicationUser? target = await context.ApplicationUsers
.FirstOrDefaultAsync(u => u.Id == user.Id,
cancellationToken);
context.ApplicationUsers.Remove(target);
await context.SaveChangesAsync(cancellationToken);
return IdentityResult.Success;
}
public void Dispose() { /* do nothing */ }
public async Task<ApplicationUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
if(int.TryParse(userId, out var id) == false)
{
return new ApplicationUser();
}
return await context.ApplicationUsers
.FirstOrDefaultAsync(u => u.Id == id,
cancellationToken);
}
public async Task<ApplicationUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
return await context.ApplicationUsers
.FirstOrDefaultAsync(u => u.UserName.ToUpper() == normalizedUserName,
cancellationToken);
}
public async Task<string> GetNormalizedUserNameAsync(ApplicationUser user, CancellationToken cancellationToken)
{
return await Task.FromResult(user.NormalizedUserName);
}
public async Task<string> GetPasswordHashAsync(ApplicationUser user, CancellationToken cancellationToken)
{
return await Task.FromResult(user.PasswordHash);
}
public async Task<string> GetUserIdAsync(ApplicationUser user, CancellationToken cancellationToken)
{
return await Task.FromResult(user.Id.ToString());
}
public async Task<string> GetUserNameAsync(ApplicationUser user, CancellationToken cancellationToken)
{
return await Task.FromResult(user.UserName);
}
public async Task<bool> HasPasswordAsync(ApplicationUser user, CancellationToken cancellationToken)
{
return await Task.FromResult(true);
}
public async Task SetNormalizedUserNameAsync(ApplicationUser user, string normalizedName, CancellationToken cancellationToken)
{
// do nothing
await Task.Run(() => {});
}
public async Task SetPasswordHashAsync(ApplicationUser user, string passwordHash, CancellationToken cancellationToken)
{
using(IDbContextTransaction transaction = context.Database.BeginTransaction())
{
ApplicationUser? target = await context.ApplicationUsers
.FirstOrDefaultAsync(u => u.Id == user.Id,
cancellationToken);
target.PasswordHash = passwordHash;
// validation
await context.SaveChangesAsync();
await transaction.CommitAsync();
}
}
public async Task SetUserNameAsync(ApplicationUser user, string userName, CancellationToken cancellationToken)
{
using(IDbContextTransaction transaction = context.Database.BeginTransaction())
{
ApplicationUser? target = await context.ApplicationUsers
.FirstOrDefaultAsync(u => u.Id == user.Id,
cancellationToken);
target.UserName = userName;
// validation
await context.SaveChangesAsync();
await transaction.CommitAsync();
}
}
public async Task<IdentityResult> UpdateAsync(ApplicationUser user, CancellationToken cancellationToken)
{
string validationError = user.Validate();
if(string.IsNullOrEmpty(validationError) == false)
{
return IdentityResult.Failed(new IdentityError { Description = validationError });
}
using(IDbContextTransaction transaction = context.Database.BeginTransaction())
{
ApplicationUser? target = await context.ApplicationUsers
.FirstOrDefaultAsync(u => u.Id == user.Id,
cancellationToken);
// validation
target.Update(user);
await context.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync();
return IdentityResult.Success;
}
}
}
}
起動.cs
...
namespace ApprovementWorkflowSample
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
// OK
services.AddIdentity<ApplicationUser, IdentityRole<int>>()
.AddUserStore<ApplicationUserStore>()
.AddEntityFrameworkStores<ApprovementWorkflowContext>()
.AddDefaultTokenProviders();
...
資源
サインマネージャー
サインとサインアウト
ログインしてサインをすることができます.
ApplicationUserService.cs
using System.Security.Claims;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
namespace ApprovementWorkflowSample.Applications
{
public class ApplicationUserService: IApplicationUserService
{
private readonly ILogger<ApplicationUsers> logger;
private readonly IApplicationUsers applicationUsers;
private readonly SignInManager<ApplicationUser> signInManager;
public ApplicationUserService(ILogger<ApplicationUsers> logger,
IApplicationUsers applicationUsers,
SignInManager<ApplicationUser> signInManager)
{
this.logger = logger;
this.applicationUsers = applicationUsers;
this.signInManager = signInManager;
}
public async Task<bool> SignInAsync(string email, string password)
{
var target = await applicationUsers.GetByEmailAsync(email);
if (target == null)
{
return false;
}
var result = await signInManager.PasswordSignInAsync(target, password, false, false);
return result.Succeeded;
}
public async Task SignOutAsync()
{
await signInManager.SignOutAsync();
}
}
}
"passwordsigninasync "は"username "を使ってサインするだけです.それで、電子メールでサインしたいなら、上記のように最初にメールでユーザーを見つけなければなりません.
ユーザーの作成
"signinmanager "もユーザを作成することができます.
ApplicationUserService.cs
...
namespace ApprovementWorkflowSample.Applications
{
public class ApplicationUserService: IApplicationUserService
{
...
public async Task<IdentityResult> CreateAsync(string userName, string? organization, string email, string password)
{
var newUser = new ApplicationUser();
newUser.Update(userName, organization, email, password);
return await signInManager.UserManager.CreateAsync(newUser);
}
...
ユーザ情報の署名を取得する
サインをした後に、httpcontextからユーザー情報を得ることができます.
ApplicationUserService.cs
using System.Security.Claims;
using System;
using System.Threading.Tasks;
using ApprovementWorkflowSample.Applications.Dto;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
namespace ApprovementWorkflowSample.Applications
{
public class ApplicationUserService: IApplicationUserService
{
...
private readonly SignInManager<ApplicationUser> signInManager;
private readonly IHttpContextAccessor httpContextAccessor;
public ApplicationUserService(ILogger<ApplicationUsers> logger,
IApplicationUsers applicationUsers,
SignInManager<ApplicationUser> signInManager,
IHttpContextAccessor httpContextAccessor)
{
...
this.signInManager = signInManager;
this.httpContextAccessor = httpContextAccessor;
}
...
public async ValueTask<User?> GetSignInUserAsync()
{
ClaimsPrincipal? user = httpContextAccessor.HttpContext?.User;
if (user == null)
{
return null;
}
if(signInManager.IsSignedIn(user) == false)
{
return null;
}
string? userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
if(string.IsNullOrEmpty(userId) ||
int.TryParse(userId, out var id) == false)
{
return null;
}
ApplicationUser? appUser = await applicationUsers.GetByIdAsync(id);
if (appUser == null)
{
return null;
}
return new User(appUser.Id, appUser.UserName, appUser.Organization, appUser.Email);
}
...
Reference
この問題について(カスタムユーザとの5つの関連付けられた), 我々は、より多くの情報をここで見つけました https://dev.to/masanori_msl/net-5-asp-net-core-identity-signin-with-custom-user-56feテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol