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

OWIN ASP.NET - Cant generate Access Token using Refresh Token if Access Token is expired

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

GrantRefreshToken中的Validated()方法不是使用先前访问令牌中存储在字典中的身份生成访问令牌吗?

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

这是我的代码:

Startup.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
    }
}

OAuthProvider.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);
    }
}

RefreshTokenProvider.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
    }
}

对不起,英语不好,希望您能理解!

编辑:

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

结果是:

OAuthProvider.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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);
    }
}

感谢支持!


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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"错误。