简体   繁体   English

OWIN ASP.NET - 避免在Web Api中没有Identity的同一帐户进行多次登录

[英]OWIN ASP.NET - Avoid multiple logins for the same account without Identity in Web Api

I want to know how can I block users to have multiple Refresh Tokens working at the same time. 我想知道如何阻止用户同时使用多个刷新令牌。 Let me explain: 让我解释:

  • Some user ask for a new Access Token using Credentials. 有些用户使用凭据要求新的访问令牌。
  • Credentials are okey, so the Authentication servers returns an Access Token and a Refresh Token. 凭据是密钥,因此身份验证服务器返回访问令牌和刷新令牌。
  • When Access Token expires, user have to use the new Refresh Token instead of Credentials to get a new Access Token. 当访问令牌到期时,用户必须使用新的刷新令牌而不是凭据来获取新的访问令牌。

The problem is that if another user log in using the same credentials, another Refresh Token will be generated for the same identity. 问题是,如果另一个用户使用相同的凭据登录,则将为同一身份生成另一个刷新令牌。 So, what i want to do is: if somebody log in again using some credentials that have an active refresh token, instead of generating a new one, replace the existing one, or delete it and insert the new one. 所以,我想要做的是:如果某人使用具有活动刷新令牌的某些凭据再次登录,而不是生成新的凭证,请替换现有的,或删除它并插入新的。 So the previous user will be disconnected when Access Token expires since Refresh Token wont exist anymore. 因此,当访问令牌到期时,先前的用户将被断开,因为刷新令牌不再存在。

Also, how can i implement some service to destroy a Refresh Token in the Authentication Server? 另外,我如何实现一些服务来销毁认证服务器中的刷新令牌? So the user can call it to disconnect his account, not just deleting the cookie and wait till it expires. 因此,用户可以调用它来断开他的帐户,而不仅仅是删除cookie并等到它过期。

Here is my code: 这是我的代码:

Startup.cs: Startup.cs:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();

        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}"
        );

        app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,

            TokenEndpointPath = new PathString("/auth"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(1),

            Provider = new OAuthProvider(),
            RefreshTokenProvider = new RefreshTokenProvider()
        });
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        app.UseWebApi(config);
    }
}

OAuthProvider.cs: OAuthProvider.cs:

public class OAuthProvider : OAuthAuthorizationServerProvider
{
    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
        return Task.FromResult<object>(null);
    }

    public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        try
        {
            var account = AccountRepository.Instance.GetByUsername(context.UserName);
            if (account != null && Global.VerifyHash(context.Password, account.Password))
            {
                var claimsIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
                claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, account.Username));
                claimsIdentity.AddClaim(new Claim("DriverId", account.DriverId.ToString()));

                var newTicket = new AuthenticationTicket(claimsIdentity, null);
                context.Validated(newTicket);
            }
        }
        catch { }

        return Task.FromResult<object>(null);
    }

    public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
    {
        context.Validated();
        return Task.FromResult<object>(null);
    }
}

RefreshTokenProvider.cs: RefreshTokenProvider.cs:

public class RefreshTokenProvider : AuthenticationTokenProvider
{
    public override Task CreateAsync(AuthenticationTokenCreateContext context)
    {
        var refreshToken = new TokenModel()
        {
            Subject = context.Ticket.Identity.Name,
            Token = GenerateToken(),
            IssuedUtc = DateTime.UtcNow,
            ExpiresUtc = DateTime.UtcNow.AddMinutes(5)
        };

        context.Ticket.Properties.IssuedUtc = refreshToken.IssuedUtc;
        context.Ticket.Properties.ExpiresUtc = refreshToken.ExpiresUtc;

        refreshToken.Ticket = context.SerializeTicket();

        try
        {
            TokenRepository.Instance.Insert(refreshToken);
            context.SetToken(refreshToken.Token);
        }
        catch { }

        return Task.FromResult<object>(null);
    }

    public override Task ReceiveAsync(AuthenticationTokenReceiveContext context)
    {
        try
        {
            var refreshToken = TokenRepository.Instance.Get(context.Token);
            if (refreshToken != null)
            {
                if (TokenRepository.Instance.Delete(refreshToken))
                {
                    context.DeserializeTicket(refreshToken.Ticket);
                }
            }
        }
        catch { }

        return Task.FromResult<object>(null);
    }

    private string GenerateToken()
    {
        HashAlgorithm hashAlgorithm = new SHA256CryptoServiceProvider();

        byte[] byteValue = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N"));
        byte[] byteHash = hashAlgorithm.ComputeHash(byteValue);

        return Convert.ToBase64String(byteHash);
    }
}

One more question: how can i throw an Internal Server Error in the catch? 还有一个问题:如何在catch中抛出内部服务器错误? Because it's actually returning invalid_grant , but the catch means a Database error, not invalid Credentials or Token. 因为它实际上返回了invalid_grant ,但catch意味着数据库错误,而不是无效的凭据或令牌。

Thanks for the help, and sorry for the bad english. 谢谢你的帮助,对不好的英语感到抱歉。 I hope you understand! 我希望你明白!

Consider the following: 考虑以下:

  1. set access token expiration to small value (minutes / hours) 将访问令牌到期设置为小值(分钟/小时)
  2. only issue a refresh token when user logs in using credentials (grant_type=password). 仅在用户使用凭据登录时发出刷新令牌(grant_type = password)。 This is a new refresh token. 这是一个新的刷新令牌。
  3. store the 'new' refresh token encrypted in the database, replacing the 'current' refresh token. 存储在数据库中加密的“新”刷新令牌,替换“当前”刷新令牌。

You do not have to block users. 您不必阻止用户。 The access token expires soon enough, just make sure you do not issue a new access token when refreshing the token. 访问令牌很快就会过期,只需确保在刷新令牌时不发出新的访问令牌。 You can do this by checking the refresh token. 您可以通过检查刷新令牌来执行此操作。 If the encrypted refresh token doesn't match the encrypted refresh token in the database 'invalid_grant' will be returned. 如果加密的刷新令牌与数据库'invalid_grant'中的加密刷新令牌不匹配,则将返回。 The user has only one option: log in again. 用户只有一个选项:再次登录。

If the user logs in using credentials the refresh token is updated (also in the database). 如果用户使用凭据登录,则更新刷新令牌(也在数据库中)。 This will automatically invalidate the 'old' refresh tokens. 这将自动使“旧”刷新令牌无效。

You can implement points 2 and 3 in RefreshTokenProvider.CreateAsync. 您可以在RefreshTokenProvider.CreateAsync中实现第2点和第3点。 Some pseudo code: 一些伪代码:

// using Microsoft.AspNet.Identity;

public override Task CreateAsync(AuthenticationTokenCreateContext context)
{
    var form = context.Request.ReadFormAsync().Result;
    var grantType = form.GetValues("grant_type");

    if (grantType[0] != "refresh_token")
    {
        // your code
        ...

        // One day
        int expire = 24 * 60 * 60;
        context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire));

        // Store the encrypted token in the database
        var currentUser = context.Ticket.Identity.GetUserId();
        TokenRepository.Instance.EncryptAndSaveTokenInDatabase(context.Token, currentUser);
    }
    base.Create(context);
}

About the error, just return invalid_grant . 关于错误,只需返回invalid_grant how often do you expect the database to fail? 您经常期望数据库失败? The client will expect to login or either receive an 'invalid_grant'. 客户端将要求登录或收到'invalid_grant'。 It knows how to handle that (redirect to login page). 它知道如何处理(重定向到登录页面)。 The client doesn't have to know there was a database error. 客户端不必知道存在数据库错误。 If you want additional information, you can log it in the backend. 如果您需要其他信息,可以在后端登录。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM