簡體   English   中英

OWIN ASP.NET - 避免在Web Api中沒有Identity的同一帳戶進行多次登錄

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

我想知道如何阻止用戶同時使用多個刷新令牌。 讓我解釋:

  • 有些用戶使用憑據要求新的訪問令牌。
  • 憑據是密鑰,因此身份驗證服務器返回訪問令牌和刷新令牌。
  • 當訪問令牌到期時,用戶必須使用新的刷新令牌而不是憑據來獲取新的訪問令牌。

問題是,如果另一個用戶使用相同的憑據登錄,則將為同一身份生成另一個刷新令牌。 所以,我想要做的是:如果某人使用具有活動刷新令牌的某些憑據再次登錄,而不是生成新的憑證,請替換現有的,或刪除它並插入新的。 因此,當訪問令牌到期時,先前的用戶將被斷開,因為刷新令牌不再存在。

另外,我如何實現一些服務來銷毀認證服務器中的刷新令牌? 因此,用戶可以調用它來斷開他的帳戶,而不僅僅是刪除cookie並等到它過期。

這是我的代碼:

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:

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:

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);
    }
}

還有一個問題:如何在catch中拋出內部服務器錯誤? 因為它實際上返回了invalid_grant ,但catch意味着數據庫錯誤,而不是無效的憑據或令牌。

謝謝你的幫助,對不好的英語感到抱歉。 我希望你明白!

考慮以下:

  1. 將訪問令牌到期設置為小值(分鍾/小時)
  2. 僅在用戶使用憑據登錄時發出刷新令牌(grant_type = password)。 這是一個新的刷新令牌。
  3. 存儲在數據庫中加密的“新”刷新令牌,替換“當前”刷新令牌。

您不必阻止用戶。 訪問令牌很快就會過期,只需確保在刷新令牌時不發出新的訪問令牌。 您可以通過檢查刷新令牌來執行此操作。 如果加密的刷新令牌與數據庫'invalid_grant'中的加密刷新令牌不匹配,則將返回。 用戶只有一個選項:再次登錄。

如果用戶使用憑據登錄,則更新刷新令牌(也在數據庫中)。 這將自動使“舊”刷新令牌無效。

您可以在RefreshTokenProvider.CreateAsync中實現第2點和第3點。 一些偽代碼:

// 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);
}

關於錯誤,只需返回invalid_grant 您經常期望數據庫失敗? 客戶端將要求登錄或收到'invalid_grant'。 它知道如何處理(重定向到登錄頁面)。 客戶端不必知道存在數據庫錯誤。 如果您需要其他信息,可以在后端登錄。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM