0

我正在使用 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 时,我会得到一个令牌,其中包含一个带有用户名的声明。

在我的场景中,我有一个永远不会改变的用户 ID(一个整数),我希望这在我的声明中。因此,用户将在标头中将电子邮件地址传递给服务端点作为基本身份验证,然后如果有效,则继续使用 id 作为声明(但不是作为声明的电子邮件地址)进行签名:

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 server 数据库进行第二次查找以将 emailAddress 转换为 userId,有没有办法在调用 CredentialsService 之前将其插入到管道流中?

或者我是不是走错了路,只是坚持使用登录的用户名,然后使用基于用户名的声明转换来丰富整数身份 - 但是如果他们更改了用户名怎么办?

4

1 回答 1

0

好的,我通过查看令人敬畏的 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)
        });
    }
}

希望这对其他人有帮助,如果我做了可怕的事情,请告诉我!

于 2015-01-04T22:20:49.700 回答