OWIN ASP.NET - 如果访问令牌过期,则无法使用刷新令牌生成访问令牌

时间:2017-05-28 04:47:01

标签: c# asp.net asp.net-web-api oauth owin

当我尝试在访问令牌到期之前使用刷新令牌生成访问令牌时,系统会生成一个新的,并且一切都很好。但是,如果访问令牌已过期,请求将返回invalid_grant

来自Validated()的{​​{1}}方法是否使用我之前的访问令牌中存储在字典中的身份生成访问令牌?

如果最后一个没有过期,我如何阻止客户使用相同的刷新令牌来请求新的访问令牌?

这是我的代码:

Startup.cs:

GrantRefreshToken

OAuthProvider.cs:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,

            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),

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

RefreshTokenProvider.cs:

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

    public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        if (context.UserName == "admin" && context.Password == "123456")
        {
            var claimsIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));

            var ticket = new AuthenticationTicket(claimsIdentity, null);
            context.Validated(ticket);
        }
        return Task.FromResult<object>(null);
    }

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

抱歉英语不好,希望你明白!

修改

好吧,我修改了代码以实现数据库支持并将刷新令牌到期时间设置为5分钟。出于测试目的,到期时间很短。

结果如下:

OAuthProvider.cs:

public class RefreshTokenProvider : AuthenticationTokenProvider
{
    private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();

    public override Task CreateAsync(AuthenticationTokenCreateContext context)
    {
        var guid = Guid.NewGuid().ToString();
        _refreshTokens.TryAdd(guid, context.Ticket);

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

    public override Task ReceiveAsync(AuthenticationTokenReceiveContext context)
    {
        if (_refreshTokens.TryRemove(context.Token, out AuthenticationTicket ticket))
        {
            context.SetTicket(ticket);
        }
        return Task.FromResult<object>(null);
    }
}

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

感谢您的支持!

1 个答案:

答案 0 :(得分:0)

似乎刷新令牌与访问令牌具有相同的到期时间。因此,您需要延长刷新令牌的到期时间:

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

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

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

- 更新 -

我已更新代码以回答评论中的问题。这比你要求的要多。但我认为这有助于解释。

这完全取决于要求和选择的策略。解释代码:

每次发出访问令牌时,您还会点击这段代码,这将添加刷新令牌。在此策略中,仅当grant_type不是'refresh_token'时,我才会发出新的刷新令牌。这意味着刷新令牌在某些时候到期,用户必须再次登录。

在示例中,用户必须每天再次登录。但是,如果用户在refresh_token到期之前登录,则将发出新的刷新令牌。这样刷新令牌具有绝对过期时间,迫使用户每天至少登录一次。

如果您想要“滑动过期”,则可以在每次发出访问令牌时添加刷新令牌。请注意,除非刷新令牌在刷新之前到期,否则用户可能永远不必再次登录。

在任何情况下,当grant_type不是refresh_token时,我不会阻止用户创建访问令牌。因为那是用户互动。访问令牌的窗口非常有限(想想小时或分钟,而不是几天),因此它很快就会过期。每次在标头中收到访问令牌时,您都不希望验证访问令牌,因为这意味着您需要检查每次调用的数据库。

而是考虑使“旧”刷新令牌无效的策略。您可以将最后一个刷新令牌的散列版本保存在数据库中。如果不匹配,则回复“invalid_grant”错误。