UserService
asp.net 코어에서 IdentityServer4에 등록하는 방법을 모두 검색 했지만 올바른 방법을 찾지 못하는 것 같습니다.
이것은 여기 에있는 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 사용자)에 어떻게 바인드 합니까? 그리고 UserService
(저는 리포지토리를 사용하여 db에 연결) 에서 기존 DB 사용자에 액세스하고 인증하기 위해 데이터베이스를 호출하는 방법은 무엇입니까?
고려하면이 이 작업을 asp.net 코어 .
감사!
답변
업데이트 -IdentityServer 4가 IUserService 를 IResourceOwnerPasswordValidator 및 IProfileService 로 변경하고 대체 했습니다.
내 UserRepository 를 사용 하여 데이터베이스에서 모든 사용자 데이터를 가져 왔습니다. 이것은 생성자에 주입 (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를 호출 할 수 있습니다.
이 작업을 수행하는 데 몇 시간이 걸리므로 다른 사람들에게 도움이되기를 바랍니다.
답변
IdentityServer4에서. IUserService
더 이상 사용할 수 없습니다. 이제 IResourceOwnerPasswordValidator
인증을 수행 IProfileService
하고 클레임을 가져 오는 데을 사용해야 합니다.
내 시나리오에서는 리소스 소유자 권한 부여 유형을 사용하며 사용자 이름과 암호에 따라 내 웹 API에 대한 역할 기반 권한 부여를 수행하도록 사용자의 클레임을 얻는 것뿐입니다. 그리고 주제는 모든 사용자에게 고유하다고 가정했습니다.
아래에 내 코드를 게시했으며 제대로 작동 할 수 있습니다. 누구든지 내 코드에 문제가 있다고 말할 수 있습니까?
이 두 서비스를 startup.cs에 등록하십시오.
public void ConfigureServices(IServiceCollection services)
{
var builder = services.AddIdentityServer();
builder.AddInMemoryClients(Clients.Get());
builder.AddInMemoryScopes(Scopes.Get());
builder.Services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
builder.Services.AddTransient<IProfileService, ProfileService>();
}
IResourceOwnerPasswordValidator
인터페이스를 구현하십시오 .
public class ResourceOwnerPasswordValidator: IResourceOwnerPasswordValidator
{
public Task<customgrantvalidationresult> ValidateAsync(string userName, string password, ValidatedTokenRequest request)
{
// Check The UserName And Password In Database, Return The Subject If Correct, Return Null Otherwise
// subject = ......
if (subject == null)
{
var result = new CustomGrantValidationResult("Username Or Password Incorrect");
return Task.FromResult(result);
}
else {
var result = new CustomGrantValidationResult(subject, "password");
return Task.FromResult(result);
}
}
}
ProfileService
인터페이스를 구현하십시오 .
public class ProfileService : IProfileService
{
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
string subject = context.Subject.Claims.ToList().Find(s => s.Type == "sub").Value;
try
{
// Get Claims From Database, And Use Subject To Find The Related Claims, As A Subject Is An Unique Identity Of User
//List<string> claimStringList = ......
if (claimStringList == null)
{
return Task.FromResult(0);
}
else {
List<Claim> claimList = new List<Claim>();
for (int i = 0; i < claimStringList.Count; i++)
{
claimList.Add(new Claim("role", claimStringList[i]));
}
context.IssuedClaims = claimList.Where(x => context.RequestedClaimTypes.Contains(x.Type));
return Task.FromResult(0);
}
}
catch
{
return Task.FromResult(0);
}
}
public Task IsActiveAsync(IsActiveContext context)
{
return Task.FromResult(0);
}
}
답변
IdentityServer4 1.0.0-rc5에서는 IUserService 나 CustomGrantValidationResult를 사용할 수 없습니다.
이제 CustomGrantValidationResult를 반환하는 대신 context.Result를 설정해야합니다.
public class ResourceOwnerPasswordValidator: IResourceOwnerPasswordValidator
{
private MyUserManager _myUserManager { get; set; }
public ResourceOwnerPasswordValidator()
{
_myUserManager = new MyUserManager();
}
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var user = await _myUserManager.FindByNameAsync(context.UserName);
if (user != null && await _myUserManager.CheckPasswordAsync(user,context.Password))
{
context.Result = new GrantValidationResult(
subject: "2",
authenticationMethod: "custom",
claims: someClaimsList);
}
else
{
context.Result = new GrantValidationResult(
TokenRequestErrors.InvalidGrant,
"invalid custom credential");
}
return;
}