OAuthAuthorizationServerProvider在哪里检查Refresh Token Expiry?

时间:2016-03-20 05:02:57

标签: asp.net-mvc asp.net-web-api owin

我已经在文章

之后实现了授权服务器

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

我几乎完全像文章一样实现它,但我没看到auth服务器如何知道刷新令牌已过期。事实上,我已经测试过,服务器在提供过期的刷新令牌时不会授予访问权限,但我在我的auth服务器中没有看到这个逻辑。此外,当我使用过期的刷新令牌请求访问令牌时,根本不会调用我的OAuthAuthorizationServerProvider子类,实际上,当我使用过期请求新的访问令牌时,我的OAuthAuthorizationServerProvider派生类或我的IAuthenticationTokenProvider实现中的所有方法都不会被调用刷新令牌。任何帮助表示赞赏。这就是我所拥有的

public class SmartCardOAuthAuthenticationTokenProvider : IAuthenticationTokenProvider
{
    private IDataAccessFactoryFactory _producesFactoryThatProducesIAuthenticateDataAccess;
    public SmartCardOAuthAuthenticationTokenProvider(IDataAccessFactoryFactory producesFactoryThatProducesIAuthenticateDataAccess)
    {
        _producesFactoryThatProducesIAuthenticateDataAccess = producesFactoryThatProducesIAuthenticateDataAccess;

    }
    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(IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory())//using (IAuthorizationDataAccess _repo = new AuthRepository())
        {
            IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>();

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

            var token = new RefreshToken()
            {
                RefreshTokenId = 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.AddRefreshTokenAsync(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 (IAuthorizationDataAccess _repo = new AuthRepository())
        //{
        using (IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory()) //using (AuthRepository _repo = new AuthRepository())
        {
            IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>();

            var refreshToken = await _repo.FindRefreshTokenAsync(hashedTokenId);

            if (refreshToken != null)
            {
                //Get protectedTicket from refreshToken class
                context.DeserializeTicket(refreshToken.ProtectedTicket);
                var result = await _repo.RemoveRefreshTokenAsync(hashedTokenId);
            }
        }
    }

    public void Create(AuthenticationTokenCreateContext context)
    {
        throw new NotImplementedException();
    }

    public void Receive(AuthenticationTokenReceiveContext context)
    {
        throw new NotImplementedException();
    }
}

public class SmartCardOAuthAuthorizationProvider : OAuthAuthorizationServerProvider
{
    private IDataAccessFactoryFactory _producesFactoryThatProducesIAuthenticateDataAccess;
    public SmartCardOAuthAuthorizationProvider(IDataAccessFactoryFactory producesFactoryThatProducesIAuthenticateDataAccess)
    {
        _producesFactoryThatProducesIAuthenticateDataAccess = producesFactoryThatProducesIAuthenticateDataAccess;

    }
    public override System.Threading.Tasks.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 });

        if (context.UserName != "onlyOneHardCodedUserForSakeOfExploration" && context.Password!="thePassword")
        {
            context.SetError("invalid_grant", "the user name or password is incorrect");
            return Task.FromResult<object>(null); ;
        }
        ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        identity.AddClaim(new Claim("sub", context.UserName));
        identity.AddClaim(new Claim(ClaimTypes.Role, "PostVSDebugBreakModeEnterEventArgs"));
        identity.AddClaim(new Claim(DatawareClaimTypes.SmartCardUserId.ToString(), 1.ToString()));

        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);
        //ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddDays(2));
        context.Validated(ticket);

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

    public override System.Threading.Tasks.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);
        }

        string[] clientIdClientUnique = context.ClientId.Split(':');

        if (clientIdClientUnique == null || clientIdClientUnique.Length <= 1)
        {
            context.SetError("invalid_client_unique");
            return Task.FromResult<object>(null);
        }

        clientId = clientIdClientUnique[0];
        string clientUnique = clientIdClientUnique[1];

        using (IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory()) //using (AuthRepository _repo = new AuthRepository())
        {
            IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>();


            client = _repo.FindClient(clientId);//new Client { Active = true, AllowedOrigin = "*", ApplicationType = ApplicationTypes.DesktopClient, ClientId = context.ClientId, Name = "Visual Studio Event Source", RefreshTokenLifeTimeInMinutes = 14400, Secret = Helper.GetHash(clientSecret) };//_repo.FindClient(context.ClientId);
        }

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

        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.RefreshTokenLifeTimeInMinutes.ToString());

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

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

}

再次,当我提出请求时

request with expired refresh 并且刷新令牌已过期,上述类\方法中的NONE将被命中。此外,传递给API的刷新令牌只不过是我在SmartCardOAuthAuthenticationTokenProvider.CreateAsync中生成的GUID,它不包含有关到期的信息。如果在通过刷新请求访问时没有遇到上述任何方法,并且在通过刷新请求新访问令牌时请求没有任何通知(看起来什么也没有)那么服务器如何知道刷新令牌已过期?

对我来说看起来很神奇。

更新1 - 添加启动代码

public static class OwinStartUpConfig
{
    public static void Configure(HttpConfiguration configFromOwinStartup)
    {
        configFromOwinStartup.MapHttpAttributeRoutes();
        configFromOwinStartup.Routes.MapHttpRoute("Default", "{controller}/{id}", new { id = RouteParameter.Optional });

        var jsonFormatter = configFromOwinStartup.Formatters.OfType<JsonMediaTypeFormatter>().First();
        jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

        RegiserDependencies(configFromOwinStartup);

    }

    public static void RegiserDependencies(HttpConfiguration configFromOwinStartup)
    {
        string connectionStringForSmartCardDbCntx = System.Configuration.ConfigurationManager.ConnectionStrings["SmartCardDataContext"].ConnectionString;
        string projectNameWhenNewProjectCreatedDueToNoMatch = System.Configuration.ConfigurationManager.AppSettings["ProjectNameWhenNewProjectCreatedDueToNoMatch"];

        Autofac.ContainerBuilder builderUsedToRegisterDependencies = new Autofac.ContainerBuilder();

        builderUsedToRegisterDependencies.RegisterType<DataAccessFactoryFactoryEf>()
            .As<IDataAccessFactoryFactory>()
            .WithParameter(new TypedParameter(typeof(string), connectionStringForSmartCardDbCntx));

        builderUsedToRegisterDependencies.Register(
            c =>
                    new List<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>
            {
                new MatchVSProjectWithMostRecentActivity<VSDebugBreakModeEnterActivity>(),
                new MatchVSSolutionWithMostRecentActivityActivityMatch<VSDebugBreakModeEnterActivity>(),
                new MatchMostRecentActivityMatch<VSDebugBreakModeEnterActivity>(),
                new MatchToNewProjectActivityMatch<VSDebugBreakModeEnterActivity>(projectNameWhenNewProjectCreatedDueToNoMatch)
            }
        ).As<IEnumerable<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>>();

        builderUsedToRegisterDependencies
            .RegisterType<MatchDontGiveUpActivityMatch<VSDebugBreakModeEnterActivity>>()
            //.WithParameter(Autofac.Core.ResolvedParameter.ForNamed<IEnumerable<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>>("VSDebugBreakModeEnterActivityMatchers"))
            .As<IProjectActivityMatch<VSDebugBreakModeEnterActivity>>();

        builderUsedToRegisterDependencies
            .RegisterType<VSDebugBreakModeEnterEventArgsEventSaver>()
            .Named<ISaveVisualStudioEvents>("VSDebugBreakModeEnterSaver");

        builderUsedToRegisterDependencies
            .RegisterType<VSDebugBreakModeEnterEventArgsController>()
            .WithParameter(Autofac.Core.ResolvedParameter.ForNamed<ISaveVisualStudioEvents>("VSDebugBreakModeEnterSaver"));

        var container = builderUsedToRegisterDependencies.Build();

        configFromOwinStartup.DependencyResolver = new AutofacWebApiDependencyResolver(container);

    }
}
    public static class OAuthStartupConfig
{
    internal static void Configure(IAppBuilder app)
    {
        OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions
        {
            AllowInsecureHttp = false,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), //TimeSpan.FromDays(1),
            Provider = new SmartCardOAuthAuthorizationProvider(new AuthorizationDataAccessFactoryFactory()),
            RefreshTokenProvider = new SmartCardOAuthAuthenticationTokenProvider(new AuthorizationDataAccessFactoryFactory())
        };
        app.UseOAuthAuthorizationServer(oAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
    }
}

更新 - 回应评论

request for access token by pass returns access and refresh token

上述响应中的访问令牌包含声明信息和访问令牌的到期信息。所有这些信息都由服务器序列化到上面的访问令牌中。所以,我可以看到如何检查访问令牌的到期时间。但是,刷新令牌怎么样?使用刷新令牌进行访问令牌请求时:

enter image description here

在上面的请求中,刷新令牌是好的,因此刷新令牌对访问令牌的请求被授予,但如果刷新令牌已过期,OAuthAuthorizationServerProvider将如何检查刷新令牌的到期时间?

同样,我已经检查过上面提供的代码确实检查了过期的刷新令牌,并且如果刷新令牌已过期则不会授予访问令牌,但它是如何知道的?我没有写任何东西来检查我的OAuthAuthorizationServerProvider派生类中的刷新令牌到期。那么如何????

更新

神奇发生在IAuthenticationTokenProvider实现的ReceiveAsync方法中。

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 (IAuthorizationDataAccess _repo = new AuthRepository())
        //{
        using (IDataAccessFactory producesIAuthenticateDataAccess = _producesFactoryThatProducesIAuthenticateDataAccess.GetDataAccessFactory()) //using (AuthRepository _repo = new AuthRepository())
        {
            IAuthorizationDataAccess _repo = producesIAuthenticateDataAccess.GetDataAccess<IAuthorizationDataAccess>();

            var refreshToken = await _repo.FindRefreshTokenAsync(hashedTokenId);

            if (refreshToken != null)
            {
                //Get protectedTicket from refreshToken class
                context.DeserializeTicket(refreshToken.ProtectedTicket);
                var result = await _repo.RemoveRefreshTokenAsync(hashedTokenId);
            }
        }
    }

特别是行context.DeserializeTicket(refreshToken.ProtectedTicket);是神奇的。这将设置context.Ticket属性。在ReceiveAsync方法完成后完成。无需手动检查OWIN,在幕后的某个地方,知道票证已过期。

2 个答案:

答案 0 :(得分:1)

  

我几乎完全像文章一样实现了它,但是我没看到auth服务器知道刷新令牌是否过期。

您必须在某处保存该信息,例如在sql表中。当您收到使用刷新令牌发出新令牌的请求时,您必须根据您对本地事实来源的信息检查提交的刷新令牌是否仍然有效。

例如,这是我实现它:

    public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
    {
        var allowedOrigin = context.OwinContext.Get<string>(Constants.PublicAuth.CLIENT_ALLOWED_ORIGIN);
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] {allowedOrigin});

        RefreshTokenModel refreshToken = await _mediator.SendAsync(new VerifyRefreshToken(context.Token));

        if (refreshToken != null)
        {
            context.DeserializeTicket(refreshToken.ProtectedTicket);
        }
    }

这是查询处理程序:

public async Task<RefreshTokenModel> Handle(VerifyRefreshToken query)
        {
            RefreshToken refreshToken = await Context.RefreshTokens
                .Where(rt => rt.Token == query.Token)
                .FirstOrDefaultAsync();

            if (refreshToken == null || refreshToken.ExpiresUtc < DateTimeOffset.UtcNow) return null;

            User user = await Context.Users.Where(u => u.Email == refreshToken.Subject).SingleOrDefaultAsync();
            if (user == null || (user.LockoutEnabled && user.LockoutEndUtc > DateTimeOffset.UtcNow)) return null;

            return new RefreshTokenModel
            {
                ProtectedTicket = new UTF8Encoding(true).GetString(refreshToken.ProtectedTicket)
            };
        }

如您所见,如果不存在,或者令牌到期点已经过去,我返回null; null表示没有令牌,因此ReceiveAsync(AuthenticationTokenReceiveContext context)将不返回任何内容,并且会发出401 Unauthorized

答案 1 :(得分:0)

神奇发生在IAuthenticationTokenProvider实现的ReceiveAsync方法中。

特别是行context.DeserializeTicket(refreshToken.ProtectedTicket);是我失踪的魔力。这将设置context.Ticket属性。在ReceiveAsync方法完成后完成。无需手动检查OWIN,在幕后的某个地方,知道票证已过期。