Skip to main content
 首页 » 编程设计

c# WebAPI Owin jwt RefreshToken invalid_grant 收藏

2024年02月27日43lexus

我被这种行为难住了。基本上,当请求刷新 token 时,WebAPI 会执行从数据中删除旧 refresh_token 的逻辑,我在调试器中没有看到任何错误。但是,API 返回一个 HTTP 400,错误为“invalid_grant”。

启动.cs

// OAuth Server configuration 
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions() 
{ 
    AllowInsecureHttp = false, 
    TokenEndpointPath = new PathString("/oauth2/token"), 
    AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(1), 
    AccessTokenFormat = new AccessTokenJwtFormat(issuer), 
    RefreshTokenProvider = new SimpleRefreshTokenProvider(), 
    RefreshTokenFormat = new RefreshTokenJwtFormat(issuer), 
    Provider = new CustomOAuthProvider() 
}; 
 
// OAuth 2.0 Bearer Access Token Generation 
app.UseOAuthAuthorizationServer(OAuthServerOptions); 

SimpleRefreshTokenProvider.cs

public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider 
{ 
 
    public async Task CreateAsync(AuthenticationTokenCreateContext context) 
    { 
        var clientid = context.Ticket.Properties.Dictionary["as:client_id"]; 
 
        if (string.IsNullOrEmpty(clientid)) 
        { 
            return; 
        } 
 
        var refreshTokenId = Guid.NewGuid().ToString("n"); 
        var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime"); 
 
        var _repo = ClientDbProvider.GetInstance(); 
        var token = new RefreshToken() 
        { 
            Id = ClientHelper.GetHash(refreshTokenId), 
            ClientId = clientid, 
            Subject = context.Ticket.Identity.Name, 
            IssuedUtc = DateTime.UtcNow, 
            ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime)) 
        }; 
 
        context.Ticket.Properties.IssuedUtc = new DateTimeOffset(token.IssuedUtc); 
        context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(token.ExpiresUtc); 
        context.Ticket.Properties.Dictionary.Add("refreshTokenId", refreshTokenId); 
        context.Ticket.Identity.AddClaim(new Claim(ClaimTypes.Role, "refreshToken")); 
        token.ProtectedTicket = context.SerializeTicket(); 
        var result = await _repo.AddRefreshToken(token); 
        if (result) 
        { 
            context.SetToken(token.ProtectedTicket); 
        } 
 
        return; 
    } 
 
    public async Task ReceiveAsync(AuthenticationTokenReceiveContext context) 
    { 
 
        var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin"); 
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin }); 
 
        var _repo = ClientDbProvider.GetInstance(); 
        string hashedTokenId = ClientHelper.GetHash(context.Token); 
        var refreshToken = await _repo.FindRefreshToken(hashedTokenId); 
 
        if (refreshToken != null) 
        { 
            //Get protectedTicket from refreshToken class 
            context.DeserializeTicket(refreshToken.ProtectedTicket); 
            var result = await _repo.RemoveRefreshToken(hashedTokenId); 
        } 
    } 
 
    public void Create(AuthenticationTokenCreateContext context) 
    { 
        throw new NotImplementedException(); 
    } 
 
    public void Receive(AuthenticationTokenReceiveContext context) 
    { 
        throw new NotImplementedException(); 
    } 
} 

RefreshTokenJwtFormat.cs

public class RefreshTokenJwtFormat : ISecureDataFormat<AuthenticationTicket> 
    { 
        private const string AudiencePropertyKey = "audience"; 
 
        private readonly string _issuer = string.Empty; 
 
        public RefreshTokenJwtFormat(string issuer) 
        { 
            _issuer = issuer; 
            audStore = new AudiencesStore(); 
        } 
 
        public string Protect(AuthenticationTicket data) 
        { 
            if (data == null) 
            { 
                throw new ArgumentNullException("data"); 
            } 
 
            string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ? data.Properties.Dictionary[AudiencePropertyKey] : null; 
 
            if (string.IsNullOrWhiteSpace(audienceId)) throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience"); 
 
            var audience = GetAudience(audienceId); 
            string symmetricKeyAsBase64 = audience.Base64Secret; 
 
            var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64); 
            var signingCredentials = new SigningCredentials( 
                    new InMemorySymmetricSecurityKey(keyByteArray), 
                    SecurityAlgorithms.HmacSha256Signature, 
                    SecurityAlgorithms.Sha256Digest); 
 
            var issued = data.Properties.IssuedUtc; 
            var expires = data.Properties.ExpiresUtc; 
            var payload = new JwtPayload(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime); 
            if (data.Properties.Dictionary.ContainsKey("refreshTokenId")) 
            { 
                payload.Add("refreshTokenId", data.Properties.Dictionary["refreshTokenId"]); 
            } 
 
            var header = new JwtHeader(signingCredentials); 
            var token = new JwtSecurityToken(header, payload); 
 
            var handler = new JwtSecurityTokenHandler(); 
 
            var jwt = handler.WriteToken(token); 
 
            return jwt; 
        } 
 
        public AuthenticationTicket Unprotect(string protectedText) 
        { 
            var handler = new JwtSecurityTokenHandler(); 
            SecurityToken securityToken = handler.ReadToken(protectedText); 
            var audienceId = ((JwtSecurityToken)securityToken).Claims.First(x => x.Type == "aud").Value; 
            var audience = GetAudience(audienceId); 
 
            string symmetricKeyAsBase64 = audience.Base64Secret; 
            var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64); 
            var securityKey = new InMemorySymmetricSecurityKey(keyByteArray); 
 
            var validationParameters = new TokenValidationParameters() 
            { 
                ValidateIssuerSigningKey = true, 
                IssuerSigningKey = securityKey, 
                ValidateLifetime = true, 
                ClockSkew = TimeSpan.Zero, 
                ValidateAudience = true, 
                ValidAudience = audienceId, 
                ValidateIssuer = true, 
                ValidIssuer = _issuer 
            }; 
 
            SecurityToken validatedToken; 
            ClaimsPrincipal principal = null; 
            try 
            { 
                principal = handler.ValidateToken(protectedText, validationParameters, out validatedToken); 
            } 
            catch(Exception ex) 
            { 
                return null; 
            } 
 
            return new AuthenticationTicket(principal.Identities.First(), new AuthenticationProperties()); 
        } 
 
        private Models.Audience GetAudience(string audienceId) 
        { 
            var findAudTask = Task.Run(() => audStore.FindAudienceAsync(audienceId)); 
            findAudTask.Wait(); 
            var audience = findAudTask.Result; 
            return audience; 
        } 
 
        private AudiencesStore audStore; 
    } 

请您参考如下方法:

首先你需要 在您的 CustomOAuthProvider 中实现 GrantRefreshToken(OAuthGrantRefreshTokenContext context) 像

public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context) 
        { 
 
            // Change auth ticket for refresh token requests 
            var newIdentity = new ClaimsIdentity(context.Ticket.Identity); 
            //newIdentity.AddClaim(new Claim("newClaim", "newValue")); 
 
            var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties); 
            context.Validated(newTicket); 
 
            return Task.FromResult<object>(null); 
        } 

其次,您需要在 RefreshTokenJwtFormat 中更改方法 Unprotect。

替换

return new AuthenticationTicket(principal.Identities.First(), new AuthenticationProperties()); 

与

return new AuthenticationTicket(principal.Identities.First(), new AuthenticationProperties 
                { 
                    IssuedUtc = validatedToken.ValidFrom, 
                    ExpiresUtc = validatedToken.ValidTo 
                });