Аутентификация - укажите адрес электронной почты для входа в систему для идентификации личности пользователя.

Я использую конфигурацию 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)
        });
    }
}

Надеюсь, что это поможет другим, пожалуйста, дайте мне знать, если я сделал что-то ужасное!

Другие вопросы по тегам