【问题标题】:OAuth access and refresh token control / management on user password changeOAuth 访问和刷新令牌控制/管理用户密码更改
【发布时间】:2015-06-14 13:26:14
【问题描述】:

我们正在开发内部移动应用程序和网络 API。 我们正在使用带有 asp.net Identy 2 OAuth 的 asp.net web api 2。

我已经启动并运行了 api,并给了我一个不记名令牌。但是,我想将流程稍微修改为类似以下内容:

  1. App用户使用用户名和密码登录api。
  2. 应用收到有效期为 30 天的 Refresh-token。
  3. 然后应用程序请求一个访问令牌,为 api 提供刷新令牌。 (在这里,如果用户更改了密码或他们的帐户已被锁定,我希望能够使请求无效)。
  4. 应用获取一个有效期为 30 分钟的访问令牌,如果密码检查失败,它会获取 401。
  5. 应用程序可以在接下来的 29 分钟内使用给定的访问令牌访问 api。之后,应用必须使用刷新令牌获取新的访问令牌。

我想这样做的原因是为了阻止用户设备在更改密码后访问 api。如果他们的手机被盗,他们需要能够登录网站并更改密码,这样手机的新主人就无法访问我们公司的服务。

我提出的解决方案可行吗?如果可行,它是一个明智的解决方案吗?我没有忘记任何关键要素?

我愿意在每次刷新令牌时进行数据库访问,但不是在每次 API 调用时进行。

总结一下我的问题是:

  1. 我计划的方法合理吗?
  2. 如何安全地检查密码是否已更改或帐户是否在刷新令牌过程中被锁定。

请在下面找到我当前的 OAuth 设置和类:(我已尝试添加刷新令牌功能,但尚未尝试添加任何密码验证)

Startup.Auth.cs

public partial class Startup
{
    public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

    public static string PublicClientId { get; private set; }

    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context and user manager to use a single instance per request
        app.CreatePerOwinContext(IdentityDbContext.Create);
        app.CreatePerOwinContext<FskUserManager>(FskUserManager.Create);

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Configure the application for OAuth based flow
        PublicClientId = "self";
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider(PublicClientId),
            AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            RefreshTokenProvider = new ApplicationRefreshTokenProvider(),
            AllowInsecureHttp = true
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions);
}

ApplicationRefreshTokenProvider.cs

public class ApplicationRefreshTokenProvider : AuthenticationTokenProvider
        {
            public override void Create(AuthenticationTokenCreateContext context)
            {
                // Expiration time in minutes
                int refreshTokenExpiration = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["ApiRefreshTokenExpiry"]);
                context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddMinutes(refreshTokenExpiration));
                context.SetToken(context.SerializeTicket());
            }

            public override void Receive(AuthenticationTokenReceiveContext context)
            {
                context.DeserializeTicket(context.Token);
            }
        }   

ApplicationOAuthProvider.cs

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
    private readonly string _publicClientId;

    public ApplicationOAuthProvider(string publicClientId)
    {
        if (publicClientId == null)
        {
            throw new ArgumentNullException("publicClientId");
        }

        _publicClientId = publicClientId;
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var userManager = context.OwinContext.GetUserManager<FskUserManager>();

        FskUser user = await userManager.FindAsync(context.UserName, context.Password);

        if (user == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }

        ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
           OAuthDefaults.AuthenticationType);
        ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
            CookieAuthenticationDefaults.AuthenticationType);

        AuthenticationProperties properties = CreateProperties(user.UserName);
        AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
        context.Validated(ticket);
        context.Request.Context.Authentication.SignIn(cookiesIdentity);
    }

    public override Task TokenEndpoint(OAuthTokenEndpointContext context)
    {
        foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
        {
            context.AdditionalResponseParameters.Add(property.Key, property.Value);
        }

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

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        // Resource owner password credentials does not provide a client ID.
        if (context.ClientId == null)
        {
            context.Validated();
        }

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

    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
    {
        if (context.ClientId == _publicClientId)
        {
            Uri expectedRootUri = new Uri(context.Request.Uri, "/");

            if (expectedRootUri.AbsoluteUri == context.RedirectUri)
            {
                context.Validated();
            }
        }

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

    public static AuthenticationProperties CreateProperties(string userName)
    {
        IDictionary<string, string> data = new Dictionary<string, string>
        {
            { "userName", userName }
        };
        return new AuthenticationProperties(data);
    }
}

【问题讨论】:

  • 您找到这个问题的答案了吗?解决方案是什么?
  • 我最终想出了一个解决方案,我最终不得不对其进行自定义并将令牌存储在数据库中?

标签: oauth-2.0 authorization asp.net-web-api2 access-token asp.net-identity-2


【解决方案1】:

如果我理解你的任务,这里有一个想法。

在创建访问令牌事件中,您可以检查密码是否已从网站更改,如果是,则撤销刷新令牌。 (您可以创建一些密码已更改的标志或其他东西)

创建访问令牌时不应该经常这样做,因此数据库访问应该没有问题。

现在的问题是如何撤销刷新令牌。除非有一种构建方式,否则您将不得不实现自定义构建方式。这里的一个想法是检查刷新令牌创建日期和更改密码操作的日期。如果更改密码操作是在创建刷新令牌后完成的,则您无需对用户进行身份验证。

让我知道你对此的看法。

【讨论】:

  • 据我所知,没有办法恢复令牌。从我读到的内容来看,发行的令牌在其到期日期间一直有效。我正在寻找有关如何实际实施它的更多技术答案。我在哪里覆盖 tke 令牌生成逻辑等。
猜你喜欢
  • 2019-03-16
  • 2015-04-01
  • 2016-08-13
  • 2021-09-13
  • 1970-01-01
  • 2013-08-18
  • 2018-06-17
  • 2012-07-11
  • 2012-04-28
相关资源
最近更新 更多