Аутентификация - укажите адрес электронной почты для входа в систему для идентификации личности пользователя.
Я использую конфигурацию Thinktecture AuthenticationConfiguration, чтобы предоставить конечную точку для подписи токенов в моем API:
var authConfig = new AuthenticationConfiguration
{
EnableSessionToken = true,
SendWwwAuthenticateResponseHeaders = true,
RequireSsl = false,
ClaimsAuthenticationManager = new ClaimsTransformation(),
SessionToken = new SessionTokenConfiguration
{
EndpointAddress = "/api/token",
SigningKey = signingKey,
DefaultTokenLifetime = new TimeSpan(1, 0, 0)
}
};
var userCredentialsService = new CredentialsService(credentialStore);
authConfig.AddBasicAuthentication(userCredentialsService.Validate);
И аутентификация пользователей с помощью CredentialsService:
public class CredentialsService
{
public bool Validate(string username, string password)
{
return username == password;
}
}
Вышеуказанное работает, и, конечно, оно не используется в производстве, но при возврате true я получу токен, в котором содержится утверждение с именем пользователя.
В моем сценарии у меня есть идентификатор пользователя (целое число), который никогда не может измениться, и я хотел бы, чтобы это было в моей заявке. Таким образом, пользователь будет передавать адрес электронной почты конечной точке службы в заголовке в качестве базовой аутентификации, а затем, если он действителен, будет идти вперед и подпишется с идентификатором в качестве утверждения (но не с адресом электронной почты в качестве утверждения):
public class CredentialsService
{
public bool Validate(string emailAddress, string password)
{
// map from the provided name, to the user id
var details = MySqlDb.ReadBy(emailAddress);
var id = details.Id; // this is the actual identity of the user
var email = details.EmailAddress;
var hash = details.Hash;
return PasswordHash.ValidatePassword(password,hash);
}
}
Я ценю, что для этого потребуется второй поиск в базе данных sql-сервера, чтобы преобразовать emailAddress в userId. Можно ли мне вставить это в конвейерный поток перед вызовом CredentialsService?
Или я поступаю неправильно, и просто придерживаюсь имени пользователя, который был зарегистрирован как as, затем использую преобразование утверждений, основанное на имени пользователя, для обогащения целочисленной идентификацией - но что тогда, если они изменили имя пользователя?
1 ответ
Хорошо, мне удалось решить эту проблему, взглянув на удивительный источник Thinktecture и переопределив BasicAuthenticationSecurityTokenHandler
дать производный класс со вторым делегатом, возвращающим Claim[], готовый для подписания:
public class BasicAuthSecurityTokenHandlerWithClaimsOutput : BasicAuthenticationSecurityTokenHandler
{
public BasicAuthSecurityTokenHandlerWithClaimsOutput(ValidateUserNameCredentialDelegate validateUserNameCredential, GetClaimsForAuthenticatedUser getClaimsForAuthenticatedUser)
: base()
{
if (validateUserNameCredential == null)
{
throw new ArgumentNullException("ValidateUserNameCredential");
}
if (getClaimsForAuthenticatedUser== null)
{
throw new ArgumentNullException("GetClaimsForAuthenticatedUser");
}
base.ValidateUserNameCredential = validateUserNameCredential;
_getClaimsForAuthenticatedUser = getClaimsForAuthenticatedUser;
}
public delegate Claim[] GetClaimsForAuthenticatedUser(string username);
private readonly GetClaimsForAuthenticatedUser _getClaimsForAuthenticatedUser;
public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
{
if (token == null)
{
throw new ArgumentNullException("token");
}
if (base.Configuration == null)
{
throw new InvalidOperationException("No Configuration set");
}
UserNameSecurityToken unToken = token as UserNameSecurityToken;
if (unToken == null)
{
throw new ArgumentException("SecurityToken is not a UserNameSecurityToken");
}
if (!ValidateUserNameCredentialCore(unToken.UserName, unToken.Password))
{
throw new SecurityTokenValidationException(unToken.UserName);
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, unToken.UserName),
new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password),
AuthenticationInstantClaim.Now
};
var lookedUpClaims = _getClaimsForAuthenticatedUser(unToken.UserName);
claims.AddRange(lookedUpClaims);
if (RetainPassword)
{
claims.Add(new Claim("password", unToken.Password));
}
var identity = new ClaimsIdentity(claims, "Basic");
if (Configuration.SaveBootstrapContext)
{
if (this.RetainPassword)
{
identity.BootstrapContext = new BootstrapContext(unToken, this);
}
else
{
var bootstrapToken = new UserNameSecurityToken(unToken.UserName, null);
identity.BootstrapContext = new BootstrapContext(bootstrapToken, this);
}
}
return new List<ClaimsIdentity> {identity}.AsReadOnly();
}
}
Затем я добавил второй вспомогательный метод, чтобы упростить подключение:
public static class BasicAuthHandlerExtensionWithClaimsOutput
{
public static void AddBasicAuthenticationWithClaimsOutput(
this AuthenticationConfiguration configuration,
BasicAuthenticationSecurityTokenHandler.ValidateUserNameCredentialDelegate validationDelegate,
BasicAuthSecurityTokenHandlerWithClaimsOutput.GetClaimsForAuthenticatedUser getClaimsForAuthenticatedUserDelegate,
string realm = "localhost", bool retainPassword = false)
{
var handler = new BasicAuthSecurityTokenHandlerWithClaimsOutput(validationDelegate, getClaimsForAuthenticatedUserDelegate);
handler.RetainPassword = retainPassword;
configuration.AddMapping(new AuthenticationOptionMapping
{
TokenHandler = new SecurityTokenHandlerCollection { handler },
Options = AuthenticationOptions.ForAuthorizationHeader(scheme: "Basic"),
Scheme = AuthenticationScheme.SchemeAndRealm("Basic", realm)
});
}
}
Надеюсь, что это поможет другим, пожалуйста, дайте мне знать, если я сделал что-то ужасное!