web api - asp.net 身份令牌即使对于后续请求也会过期

Posted

技术标签:

【中文标题】web api - asp.net 身份令牌即使对于后续请求也会过期【英文标题】:web api - asp.net identity token expires even for the subsequent request 【发布时间】:2015-12-02 18:55:10 【问题描述】:

我在 web api 中使用 asp.net 身份进行基于令牌的身份验证。

对于刷新令牌,我已经基于以下链接实现了

http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/

我添加了以下两个类,并在启动配置中提到。

从我通过 api 单独使用用户名和密码调用的用户界面

http://domain/token

当我调用上面的api时,请求直接转到方法ValidateClientAuthentication

但在这个方法中,逻辑是,我们需要发送客户端 ID 和客户端密码。

在用户登录特定用户之前,我们如何知道这两个?

我认为工作流程应该是这样的,我们需要根据数据库检查用户名和密码,并且应该生成访问令牌和刷新令牌。

但是我在哪里做这个逻辑。

示例中提到的这个系统的工作流程是什么?

在这个系统之前,我会在我的应用程序中调用Common/login api,验证成功后,

我会调用代码让用户登录

 var userIdentity=await user.GenerateUserIdentityAsync(UserManager);

AuthenticationManager.SignIn(new AuthenticationProperties()  IsPersistent = isPersistent , userIdentity);

在上面的代码之后,我将根据用户身份生成访问令牌。

我已经多次尝试以下实现并且厌倦了流程。

请帮助我了解这里提到的逻辑和流程。

SimpleAuthorizationServerProvider

namespace AngularJSAuthentication.API.Providers
    
        public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
        
            public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
            

                string clientId = string.Empty;
            string clientSecret = string.Empty;
            Client client = null;

            if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
            
                context.TryGetFormCredentials(out clientId, out clientSecret);
            

            if (context.ClientId == null)
            
                //Remove the comments from the below line context.SetError, and invalidate context 
                //if you want to force sending clientId/secrects once obtain access tokens. 
                context.Validated();
                //context.SetError("invalid_clientId", "ClientId should be sent.");
                return Task.FromResult<object>(null);
            

            using (AuthRepository _repo = new AuthRepository())
            
                client = _repo.FindClient(context.ClientId);
            

            if (client == null)
            
                context.SetError("invalid_clientId", string.Format("Client '0' is not registered in the system.", context.ClientId));
                return Task.FromResult<object>(null);
            

            if (client.ApplicationType == Models.ApplicationTypes.NativeConfidential)
            
                if (string.IsNullOrWhiteSpace(clientSecret))
                
                    context.SetError("invalid_clientId", "Client secret should be sent.");
                    return Task.FromResult<object>(null);
                
                else
                
                    if (client.Secret != Helper.GetHash(clientSecret))
                    
                        context.SetError("invalid_clientId", "Client secret is invalid.");
                        return Task.FromResult<object>(null);
                    
                
            

            if (!client.Active)
            
                context.SetError("invalid_clientId", "Client is inactive.");
                return Task.FromResult<object>(null);
            

            context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
            context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());

            context.Validated();
            return Task.FromResult<object>(null);
        

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        

            var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");

            if (allowedOrigin == null) allowedOrigin = "*";

            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[]  allowedOrigin );

            using (AuthRepository _repo = new AuthRepository())
            
                IdentityUser user = await _repo.FindUser(context.UserName, context.Password);

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

            var identity = new ClaimsIdentity(context.Options.AuthenticationType);
            identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
            identity.AddClaim(new Claim("sub", context.UserName));

            var props = new AuthenticationProperties(new Dictionary<string, string>
                
                     
                        "as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
                    ,
                     
                        "userName", context.UserName
                    
                );

            var ticket = new AuthenticationTicket(identity, props);
            context.Validated(ticket);

        

        public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
        
            var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
            var currentClient = context.ClientId;

            if (originalClient != currentClient)
            
                context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
                return Task.FromResult<object>(null);
            

            // Change auth ticket for refresh token requests
            var newIdentity = new ClaimsIdentity(context.Ticket.Identity);

            var newClaim = newIdentity.Claims.Where(c => c.Type == "newClaim").FirstOrDefault();
            if (newClaim != null)
            
                newIdentity.RemoveClaim(newClaim);
            
            newIdentity.AddClaim(new Claim("newClaim", "newValue"));

            var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
            context.Validated(newTicket);

            return Task.FromResult<object>(null);
        

        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);
        

    

SimpleRefreshTokenProvider

namespace AngularJSAuthentication.API.Providers

    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");

            using (AuthRepository _repo = new AuthRepository())
            
                var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime"); 

                var token = new RefreshToken() 
                 
                    Id = Helper.GetHash(refreshTokenId),
                    ClientId = clientid, 
                    Subject = context.Ticket.Identity.Name,
                    IssuedUtc = DateTime.UtcNow,
                    ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime)) 
                ;

                context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
                context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;

                token.ProtectedTicket = context.SerializeTicket();

                var result = await _repo.AddRefreshToken(token);

                if (result)
                
                    context.SetToken(refreshTokenId);
                

            
        

        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 );

            string hashedTokenId = Helper.GetHash(context.Token);

            using (AuthRepository _repo = new AuthRepository())
            
                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();
        
    

【问题讨论】:

“如果我将令牌设置为 10 分钟过期,...,令牌在 10 分钟后过期。”嗯,好像有道理! ;-) 对于后续请求,令牌应该过期应该延长。令牌应在 10 分钟的空闲时间内过期.. 是的,我明白了。只是这句话对我来说是古玩。顺便说一句,检查我的答案和教程的链接,了解如何使用刷新令牌扩展会话。 您是否尝试过 Freerider 提到的刷新令牌? 而不是刷新令牌,有一个选项,例如为每个请求重置时间。你能给这个吗? 【参考方案1】:

如何使用刷新令牌并将它们存储在数据库中,例如以下两个示例:

http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/ http://leastprivilege.com/2013/11/15/adding-refresh-tokens-to-a-web-api-v2-authorization-server/

正如第一个链接中广泛描述的那样,您可以创建自己的令牌提供程序实现来处理令牌刷新:

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");

        using (AuthRepository _repo = new AuthRepository())
        
            var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime"); 

            var token = new RefreshToken() 
             
                Id = Helper.GetHash(refreshTokenId),
                ClientId = clientid, 
                Subject = context.Ticket.Identity.Name,
                IssuedUtc = DateTime.UtcNow,
                ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime)) 
            ;

            context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
            context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;

            token.ProtectedTicket = context.SerializeTicket();

            var result = await _repo.AddRefreshToken(token);

            if (result)
            
                context.SetToken(refreshTokenId);
            

        
    
 

【讨论】:

我已经添加了这些类并实现了。我不知道这些方法是否会被自动调用或者我需要调用这些方法。如果我需要手动调用,那么我不知道何时/何处调用。 在这个方法之前,我几乎不记得我们可以为后续请求重置时间。我们可以吗?这是正确的做法吗? @JeevaJsb 你能发布一个关于你如何实现你的类以及你如何使用它们的代码 sn-p 吗?从我的观点来看,上述方法(来自两个链接)似乎是有效的。 每次用户发出请求时都会自动执行吗?

以上是关于web api - asp.net 身份令牌即使对于后续请求也会过期的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET MVC Web API 身份验证令牌安全问题

使用 asp.net web api 令牌在 mvc 网站上进行身份验证

ASP.net core web api:使用 Facebook/Google OAuth 访问令牌进行身份验证

Azure AD B2C 与 ASP.NET Web API 发出令牌,用于 Web API 中的身份验证和访问 MS Graph API

Asp.Net Web API JWT 身份验证

如何自定义 ASP .NET Web API JWT 令牌响应?