[解決済み] IdentityServer4 UserServiceを登録し、データベースからユーザーを取得する(Asp.net core)
質問
を登録する方法について、いろいろと調べてみました。
UserService
を登録する方法を調べましたが、正しい方法を見つけることができません。
これは、見つかったInMemoryUsersを登録するためのコードです。 ここで しかし、私はサンプルで定義された静的なユーザーではなく、私のMSSQL DBからユーザーにアクセスしたいと思います。
var builder = services.AddIdentityServer(options =>
{
options.SigningCertificate = cert;
});
builder.AddInMemoryClients(Clients.Get());
builder.AddInMemoryScopes(Scopes.Get());
builder.AddInMemoryUsers(Users.Get());
そこで、次に見たのが この のためのもので、これは IdentityServer3 .
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());
var userService = new UserService();
factory.UserService = new Registration<IUserService>(resolver => userService);
ネットで調べたところ、UserServiceを登録するにはDIシステムを使用する必要があるようですが、IdentityServerなどにどのようにバインドするのかがよくわかりません。
services.AddScoped<IUserService, UserService>();
そこで質問なのですが
どのように私の
UserService
をビルダー (IdentityServer4 Users) にバインドするにはどうすればよいですか。また、データベースを呼び出して
UserService
(私は db に接続するためにリポジトリを使用します)?
これを考慮すると があります。 と連携することで asp.net コア .
ありがとうございます。
どのように解決するのですか?
更新 - IdentityServer 4 は変更され、置き換えられました。 IUserService を IResourceOwnerPasswordValidatorを使用しています。 と IProfileService
私は自分の
ユーザーリポジトリ
を使用して、データベースからすべてのユーザーデータを取得しています。これはコンストラクタにインジェクション(DI)されます。
Startup.cs
. また、IDサーバーのために以下のクラスを作成しました(これもインジェクトされます)。
最初に定義した
ResourceOwnerPasswordValidator.cs
:
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
//repository to get user from db
private readonly IUserRepository _userRepository;
public ResourceOwnerPasswordValidator(IUserRepository userRepository)
{
_userRepository = userRepository; //DI
}
//this is used to validate your user account with provided grant at /connect/token
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
try
{
//get your user model from db (by username - in my case its email)
var user = await _userRepository.FindAsync(context.UserName);
if (user != null)
{
//check if password match - remember to hash password if stored as hash in db
if (user.Password == context.Password) {
//set the result
context.Result = new GrantValidationResult(
subject: user.UserId.ToString(),
authenticationMethod: "custom",
claims: GetUserClaims(user));
return;
}
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Incorrect password");
return;
}
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "User does not exist.");
return;
}
catch (Exception ex)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid username or password");
}
}
//build claims array from user data
public static Claim[] GetUserClaims(User user)
{
return new Claim[]
{
new Claim("user_id", user.UserId.ToString() ?? ""),
new Claim(JwtClaimTypes.Name, (!string.IsNullOrEmpty(user.Firstname) && !string.IsNullOrEmpty(user.Lastname)) ? (user.Firstname + " " + user.Lastname) : ""),
new Claim(JwtClaimTypes.GivenName, user.Firstname ?? ""),
new Claim(JwtClaimTypes.FamilyName, user.Lastname ?? ""),
new Claim(JwtClaimTypes.Email, user.Email ?? ""),
new Claim("some_claim_you_want_to_see", user.Some_Data_From_User ?? ""),
//roles
new Claim(JwtClaimTypes.Role, user.Role)
};
}
そして
ProfileService.cs
:
public class ProfileService : IProfileService
{
//services
private readonly IUserRepository _userRepository;
public ProfileService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
//Get user profile date in terms of claims when calling /connect/userinfo
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{
//depending on the scope accessing the user data.
if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
{
//get user from db (in my case this is by email)
var user = await _userRepository.FindAsync(context.Subject.Identity.Name);
if (user != null)
{
var claims = GetUserClaims(user);
//set issued claims to return
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
else
{
//get subject from context (this was set ResourceOwnerPasswordValidator.ValidateAsync),
//where and subject was set to my user id.
var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");
if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
{
//get user from db (find user by user id)
var user = await _userRepository.FindAsync(long.Parse(userId.Value));
// issue the claims for the user
if (user != null)
{
var claims = ResourceOwnerPasswordValidator.GetUserClaims(user);
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
}
}
catch (Exception ex)
{
//log your error
}
}
//check if user account is active.
public async Task IsActiveAsync(IsActiveContext context)
{
try
{
//get subject from context (set in ResourceOwnerPasswordValidator.ValidateAsync),
var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "user_id");
if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
{
var user = await _userRepository.FindAsync(long.Parse(userId.Value));
if (user != null)
{
if (user.IsActive)
{
context.IsActive = user.IsActive;
}
}
}
}
catch (Exception ex)
{
//handle error logging
}
}
}
次に
Startup.cs
以下のようにしました。
public void ConfigureServices(IServiceCollection services)
{
//...
//identity server 4 cert
var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath, "idsrv4test.pfx"), "your_cert_password");
//DI DBContext inject connection string
services.AddScoped(_ => new YourDbContext(Configuration.GetConnectionString("DefaultConnection")));
//my user repository
services.AddScoped<IUserRepository, UserRepository>();
//add identity server 4
services.AddIdentityServer()
.AddSigningCredential(cert)
.AddInMemoryIdentityResources(Config.GetIdentityResources()) //check below
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddProfileService<ProfileService>();
//Inject the classes we just created
services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
services.AddTransient<IProfileService, ProfileService>();
//...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//...
app.UseIdentityServer();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
IdentityServerAuthenticationOptions identityServerValidationOptions = new IdentityServerAuthenticationOptions
{
//move host url into appsettings.json
Authority = "http://localhost:50000/",
ApiSecret = "secret",
ApiName = "my.api.resource",
AutomaticAuthenticate = true,
SupportedTokens = SupportedTokens.Both,
// required if you want to return a 403 and not a 401 for forbidden responses
AutomaticChallenge = true,
//change this to true for SLL
RequireHttpsMetadata = false
};
app.UseIdentityServerAuthentication(identityServerValidationOptions);
//...
}
また
Config.cs
が必要です。これはクライアント、API、リソースを定義します。ここに例があります。
https://github.com/IdentityServer/IdentityServer4.Demo/blob/master/src/IdentityServer4Demo/Config.cs
これで、IdentityServer /connect/token を呼び出すことができるようになったはずです。
詳細については、ドキュメントをご確認ください。 https://media.readthedocs.org/pdf/identityserver4/release/identityserver4.pdf
古い回答 (これは ではない は新しい IdentityServer4 ではもう動作しません)。
流れがわかれば、とても簡単です。
IdentityServiceを以下のように設定します (Startup.cs内)
ConfigureServices()
):
var builder = services.AddIdentityServer(options =>
{
options.SigningCertificate = cert;
});
builder.AddInMemoryClients(Clients.Get());
builder.AddInMemoryScopes(Scopes.Get());
//** this piece of code DI's the UserService into IdentityServer **
builder.Services.AddTransient<IUserService, UserService>();
//for clarity of the next piece of code
services.AddTransient<IUserRepository, UserRepository>();
次に、UserServiceを設定します。
public class UserService : IUserService
{
//DI the repository from Startup.cs - see previous code block
private IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
var user = _userRepository.Find(context.UserName);
//check if passwords match against user column
//My password was hashed,
//so I had to hash it with the saved salt first and then compare.
if (user.Password == context.Password)
{
context.AuthenticateResult = new AuthenticateResult(
user.UserId.ToString(),
user.Email,
//I set up some claims
new Claim[]
{
//Firstname and Surname are DB columns mapped to User object (from table [User])
new Claim(Constants.ClaimTypes.Name, user.Firstname + " " + user.Surname),
new Claim(Constants.ClaimTypes.Email, user.Email),
new Claim(Constants.ClaimTypes.Role, user.Role.ToString()),
//custom claim
new Claim("company", user.Company)
}
);
}
return Task.FromResult(0);
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//find method in my repository to check my user email
var user = _userRepository.Find(context.Subject.Identity.Name);
if (user != null)
{
var claims = new Claim[]
{
new Claim(Constants.ClaimTypes.Name, user.Firstname + " " + user.Surname),
new Claim(Constants.ClaimTypes.Email, user.Email),
new Claim(Constants.ClaimTypes.Role, user.Role.ToString(), ClaimValueTypes.Integer),
new Claim("company", user.Company)
};
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type));
}
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
var user = _userRepository.Find(context.Subject.Identity.Name);
return Task.FromResult(user != null);
}
}
基本的にインジェクションで
UserService
を
builder
(タイプの
IdentityServerBuilder
)
Services
とすることで、認証時にUserServiceを呼び出すことができます。
これを動かすのに数時間かかったので、これが他の人の役に立てばいいのですが。
関連
-
[解決済み】Excel "外部テーブルが期待された形式ではありません。"
-
[解決済み] エンティティタイプ <type> は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み] 'SubSonic.Schema .DatabaseColumn' 型のオブジェクトをシリアライズする際に、循環参照が検出されました。
-
[解決済み】クロススレッド操作が有効でない。作成されたスレッド以外のスレッドからアクセスされたコントロール
-
[解決済み】取り消せないメンバはメソッドのように使えない?
-
[解決済み】EF 5 Enable-Migrations : アセンブリにコンテキストタイプが見つかりませんでした
-
[解決済み】Swashbuckle/Swagger + ASP.Net Core: "Failed to load API definition" (API定義の読み込みに失敗しました
-
[解決済み] EntityTypeにキーが定義されていないエラー
-
[解決済み】WSACancelBlockingCallの例外について
-
[解決済み] ConfigureServices内からASP.NET Core DIでインスタンスを解決する
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】コンパイルエラー「未割り当てのローカル変数を使用しています」が発生したのはなぜですか?
-
[解決済み】統合マネージドパイプラインモードで適用されないASP.NETの設定が検出された
-
[解決済み】トランスポート接続からデータを読み取れない:既存の接続は、リモートホストによって強制的に閉じられました。
-
[解決済み】バックスラッシュを含むパス文字列のエスケープシーケンスが認識されない件
-
[解決済み】Visual studio 2019がデバッグ時にフリーズする件
-
[解決済み】Unity 「関連するスクリプトを読み込むことができません」「Win32Exception: システムは指定されたファイルを見つけることができません"
-
[解決済み】aspNetCore 2.2.0 - AspNetCoreModuleV2 エラー
-
[解決済み】 C# 条件演算子エラー 代入、call、increment、decrement、await、new object 式のみ文として使用可能です。
-
[解決済み】パラメータ付きRedirectToAction
-
[解決済み】ユーザー設定値を別のユーザー設定値で設定する