Oauth 2.0 用户密码更改时OAuth访问和刷新令牌控制/管理

Oauth 2.0 用户密码更改时OAuth访问和刷新令牌控制/管理,oauth-2.0,authorization,asp.net-web-api2,access-token,asp.net-identity-2,Oauth 2.0,Authorization,Asp.net Web Api2,Access Token,Asp.net Identity 2,我们正在开发内部移动应用程序和web api。 我们将asp.net web api 2与asp.net Identity 2 OAuth一起使用 我已经启动并运行了api,并给了我一个承载令牌。但是,我想稍微修改流程,使其类似于以下内容: 应用程序用户使用用户名和密码登录到api 应用程序收到有效期为30天的刷新令牌 然后,应用程序请求一个访问令牌,为api提供刷新令牌。(在这里,如果用户更改了密码或帐户被锁定,我希望能够使请求无效) 应用程序获取30分钟内有效的访问令牌,或者如果密码检查失败

我们正在开发内部移动应用程序和web api。 我们将asp.net web api 2与asp.net Identity 2 OAuth一起使用

我已经启动并运行了api,并给了我一个承载令牌。但是,我想稍微修改流程,使其类似于以下内容:

  • 应用程序用户使用用户名和密码登录到api
  • 应用程序收到有效期为30天的刷新令牌
  • 然后,应用程序请求一个访问令牌,为api提供刷新令牌。(在这里,如果用户更改了密码或帐户被锁定,我希望能够使请求无效)
  • 应用程序获取30分钟内有效的访问令牌,或者如果密码检查失败,则获取401
  • 应用程序可以在接下来的29分钟内使用给定的访问令牌访问api。之后,应用程序将必须使用刷新令牌获取新的访问令牌
  • 我这样做的原因是为了阻止用户在更改密码后访问api。如果他们的手机被盗,他们需要能够登录到网站并更改密码,以便新的手机所有者无法访问我们公司的服务

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

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

    总结我的问题如下:

  • 我的计划方法合理吗
  • 我如何安全地检查密码是否已更改,或者帐户是否在刷新令牌过程中被锁定
  • 请在下面找到我当前的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);
    }
    
    ApplicationAuthProvider.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);
                }
            }   
    
    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);
        }
    }
    
    公共类ApplicationAuthProvider:OAuthAuthorizationServerProvider
    {
    私有只读字符串_publicClientId;
    公共ApplicationAuthProvider(字符串publicClientId)
    {
    if(publicClientId==null)
    {
    抛出新ArgumentNullException(“publicClientId”);
    }
    _publicClientId=publicClientId;
    }
    公共重写异步任务GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentials上下文)
    {
    var userManager=context.OwinContext.GetUserManager();
    FskUser user=await userManager.FindAsync(context.UserName,context.Password);
    if(user==null)
    {
    SetError(“无效的授权”,“用户名或密码不正确”);
    返回;
    }
    ClaimsIdentity oAuthIdentity=等待用户.GenerateUserIdentityAsync(userManager,
    OAuthDefaults.AuthenticationType);
    ClaimSideEntity cookiesIdentity=等待用户.GenerateUserIdentity异步(userManager,
    CookieAuthenticationDefaults.AuthenticationType);
    AuthenticationProperties=CreateProperties(user.UserName);
    AuthenticationTicket=新的AuthenticationTicket(OAuthidentitity,属性);
    上下文。已验证(票证);
    context.Request.context.Authentication.sign(cookiesIdentity);
    }
    公共重写任务令牌端点(OAuthTokenEndpointContext)
    {
    foreach(context.Properties.Dictionary中的KeyValuePair属性)
    {
    AdditionalResponseParameters.Add(property.Key,property.Value);
    }
    返回Task.FromResult(空);
    }
    公共覆盖任务ValidateClientAuthentication(OAuthValidateClientAuthenticationContext)
    {
    //资源所有者密码凭据不提供客户端ID。
    if(context.ClientId==null)
    {
    context.Validated();
    }
    返回Task.FromResult(空);
    }
    公共覆盖任务ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext上下文)
    {
    if(context.ClientId==\u publicClientId)
    {
    Uri expectedRootUri=新Uri(context.Request.Uri,“/”;
    if(expectedRootUri.AbsoluteUri==context.RedirectUri)
    {
    context.Validated();
    }
    }
    返回Task.FromResult(空);
    }
    公共静态身份验证属性CreateProperties(字符串用户名)
    {
    IDictionary data=新字典
    {
    {“用户名”,用户名}
    };
    返回新的AuthenticationProperties(数据);
    }
    }
    
    如果我正确理解了您的任务,下面是一个想法

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

    在创建访问令牌时不应该经常这样做,所以db访问应该不会出现问题

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


    请告诉我您对此的看法。

    据我所知,没有办法恢复代币。据我所知,发行的代币有效期与到期日相同。我正在寻找一个关于如何实际实施它的更技术性的答案。我在哪里覆盖tke令牌生成逻辑等。您找到这个问题的答案了吗?解决方案是什么?我最终提出了一个解决方案,我最终不得不自定义它并将令牌存储在数据库中?