Web API请求的滑动会话

时间:2018-01-10 14:42:56

标签: c# .net asp.net-mvc session asp.net-web-api2

更新:它似乎正在尝试在ApplyResponseGrantAsync中编写新的Cookie标头但不能,因为它的 会抛出异常标题已经发送。

更新:更清晰。如何在Web API请求期间将Set-Cookie标头添加到XHR响应中?

TL; DR; 问题是应用程序是使用MVC进行身份验证的,但是大量使用Web API。 Web API请求即使使用Authentication属性也不会滑动会话 - 几乎可以肯定,因为它是一个cookie响应。

我有一个组合的MVC和Web API应用程序。在大多数情况下,MVC视图只加载产生大量Web API请求的SPA。 这很好,但会话滑动不适用于Web API请求。

我可以看到CookieAuthenticationHandlerApplyResponseGrantAsync中滑动会话的位置,但我需要在每个Web API请求中强制执行此操作。

model.Properties.IssuedUtc = new DateTimeOffset?(this._renewIssuedUtc);
model.Properties.ExpiresUtc = new DateTimeOffset?(this._renewExpiresUtc);
if (this.Options.SessionStore != null && this._sessionKey != null)
{
  await this.Options.SessionStore.RenewAsync(this._sessionKey, model);
  ClaimsIdentity identity = new ClaimsIdentity((IEnumerable<Claim>) new Claim[1]
  {
    new Claim("Microsoft.Owin.Security.Cookies-SessionId", this._sessionKey)
  }, this.Options.AuthenticationType);
    model = new AuthenticationTicket(identity, (AuthenticationProperties) null);
}
string cookieValue = this.Options.TicketDataFormat.Protect(model);
if (model.Properties.IsPersistent)
  cookieOptions.Expires = new DateTime?(this._renewExpiresUtc.ToUniversalTime().DateTime);
this.Options.CookieManager.AppendResponseCookie(this.Context, this.Options.CookieName, cookieValue, cookieOptions);

有人知道如何强迫这个吗?

身份验证设置

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
            validateInterval: TimeSpan.FromMinutes(15),
            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
    },
    SlidingExpiration = true,
    ExpireTimeSpan = TimeSpan.FromMinutes(1)
});

app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

5 个答案:

答案 0 :(得分:1)

根据CookieAuthenticationMiddleware来源,如果您将SlidingExpiration的{​​{1}}设置为true,那么您的会话将在每次请求时续订。您可以查看此code以了解自己。

我认为我会根据this line跟踪您的问题,以便更新Cookie,SignIn方法CookieAuthenticationOptions的{​​{1}}属性应为true。我猜这是你的问题。

<德尔> 如果我没有错,你使用`ApplicationSignInManager.SignInAsync()`来登录你的用户。不幸的是基于[implementation] [2],没有办法传递参数来设置`AllowRefresh`,所以我建议覆盖SignInAsync方法并将`AllowRefresh`设置为true,看看它是否有效。

<强>更新

这是另一种猜测,它可能会解决您的问题。基于this line,当只有50%的生命周期过去时,cookie会话将会更新。当我查看代码时,我认为没有办法覆盖它。我建议通过在每个请求上添加一个AllowRefresh AuthenticationProperties用户来更新cookie,从而在每个请求上创建一个新的cookie,如果在WebAPI或MVC中请求是否无关紧要

Middleware

之后添加中间件
SingIn

基本上您可以将app.UseCookieAuthentication设置为false,因为这与此变通方法无关,并且app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>( validateInterval: TimeSpan.FromMinutes(15), regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) }, SlidingExpiration = true, ExpireTimeSpan = TimeSpan.FromMinutes(1) }); app.Use(async (context, next) => { var result = await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie); if (result != null) { result.Properties.ExpiresUtc = null; result.Properties.IssuedUtc = null; context.Authentication.SignIn(result.Properties, result.Identity); } await next(); }); SlidingExpiration上的OnValidateIdentity无法每30分钟重新加载一次用户声明,因为每个请求都会刷新IssuedUTC,如果需要,您必须修改此中间件并编写自定义CookieAuthentication

答案 1 :(得分:1)

  

通常,如果您在web.config中启用了slidingExpiration,它就会起作用。您无需手动重新生成新cookie。对于您的方案,我建议您使用跟踪工具,例如提琴手。刷新页面时,您可以从Fiddler查看是否重置了Cookie过期时间。

答案 2 :(得分:1)

由于AuthenticationHandler使用{{1连接回调,因此在将标头写入客户端时,ApplyResponseAsync()以及随后的ApplyResponseGrantAsync()ApplyResponseChallengeAsync()也会被调用一次在初始化阶段。这可以确保所有处理程序都有机会在标题发送给用户之前创建挑战或处理响应授权,这是我们可以的最后一个时间点修改回复

所以为什么你需要在你的情况下建立一个cookie响应?!为什么你没有使用合适的超时间隔(Response.OnSendingHeaders() )?或者至少使用持久性cookie

修改

通过在 web.config 中启用SlidingExpiration解决了一些与您相比有问题的人解决后添加slidingExpiration,如下所示。请让我知道结果:

cookieless="UseCookies"

答案 3 :(得分:1)

<强> TL; DR

试试这个:只需将此代码添加到您的控制器,它会覆盖OnActionExecuted,它会在每个操作后运行并手动添加标题:

       protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (filterContext.ActionDescriptor.ActionName=="YourWebAPIName")
            {
                filterContext.HttpContext.Response.AddHeader("Set-Cookie","CookieName=CookieValue");           
            }
        //You can replace the former if with a more general one like checking:
        //filterContext.Result.GetType() and see if it is of the type JsonResult

            base.OnActionExecuted(filterContext);
        }

------------------以下是我的老答案--------------------- --------

首先考虑@Amirhossein Mehrvarzi的问题,如果出于任何原因需要创建自己的会话滑动,请考虑以下内容:

这只是一个建议,不确定它是否可行

您可以尝试覆盖:

OnActionExecuting(ActionExecutingContext filterContext)(在行动前运行)

Controller.OnActionExecuted(ActionExecutedContext filterContext)(行动后运行)

控件的操作并在那里滑动会话。

它将在WebApi控制器和页面控制器上执行,您可以知道哪个控制器使用filterContext.ActionDescriptor.ActionName中的this链接

这两种方法都会收到有用的输入,可以操作动作执行,如下面的链接所示:

ActionExecutedContext

ActionExecutingContext

答案 4 :(得分:0)

它最终成为我添加到管道中的过滤器,用于记录响应的内容。删除该过滤器后,即使是Web API请求,也会写入cookie。

public class WebApiResponseFilter : ActionFilterAttribute
{
    private ILogUtils logUtils;

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        base.OnActionExecuted(actionExecutedContext);

        if (logUtils == null)
        {
            logUtils = StructureMapConfig.Container.GetInstance<ILogUtils>();
        }

        var httpContext = HttpContext.Current;

        var actionDescriptor = actionExecutedContext.ActionContext.ActionDescriptor;

        var requestId = httpContext.Request.Headers.GetValues("X-RequestId").First();
        var userId = httpContext.User.Identity.GetUserId();
        var userName = httpContext.User.Identity.GetUserName();

        var responseContent = actionExecutedContext.Response.Content;
        if (responseContent == null)
        {
            logUtils.LogUsage($"RESPONSE LOG ipAddress:{httpContext.Request.UserHostAddress} requestId:{requestId} userId:{userId} userName:{userName} action:{actionDescriptor.ControllerDescriptor.ControllerName}.{actionDescriptor.ActionName} response:n/a");
        }
        else
        {
            var response = Task.Run(async () => await responseContent.ReadAsStringAsync()).Result;
            response = AesManager.EncryptData(response);
            logUtils.LogUsage($"RESPONSE LOG ipAddress:{httpContext.Request.UserHostAddress} requestId:{requestId} userId:{userId} userName:{userName} action:{actionDescriptor.ControllerDescriptor.ControllerName}.{actionDescriptor.ActionName} response:{response}");
        }
    }
}

感谢所有的帮助,我很抱歉这件小工件造成了这个问题!