从 Web Api 2 IAuthenticationFilter AuthenticateAsync 方法设置 cookie

Posted

技术标签:

【中文标题】从 Web Api 2 IAuthenticationFilter AuthenticateAsync 方法设置 cookie【英文标题】:Set cookie from Web Api 2 IAuthenticationFilter AuthenticateAsync method 【发布时间】:2015-10-30 15:53:00 【问题描述】:

使用 Web Api 2.2,我有一个自定义 IAuthenticationFilter,用于通过自定义方案对客户端请求进行身份验证。

基本上,当客户端未通过身份验证并想要访问受保护的资源时,他会在请求旁边发送一个Authorization 标头:Authorization: MyCustomScheme XXXXXXX。然后过滤器验证凭据,对用户进行身份验证并生成无状态身份验证令牌以供进一步访问(类似于JWT)。

我想将生成的身份验证令牌存储在 cookie 中。当出现在传入请求中时,cookie 会在单独的过滤器中进行本地验证(此处未提供)。

我的问题是,如果我尝试这样设置 cookie:

Task IAuthenticationFilter.AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)

    if (context.Request.Headers.Authorization != null &&
        string.Equals(context.Request.Headers.Authorization.Scheme, "MyCustomScheme", StringComparison.OrdinalIgnoreCase))
    
        // This works
        CustomPrincipal principal = this.ValidateCredentials(context.Request.Headers.Authorization.Parameter);
        context.Principal = principal;

        // This doesn't work: context.ActionContext.Response is null
        var cookie = new CookieHeaderValue("MySessionCookie", principal.AuthenticationToken)  Path = "/", HttpOnly = true ;
        context.ActionContext.Response.Headers.AddCookies(new CookieHeaderValue[]  cookie );
    
    return Task.FromResult(0);

然后它失败了,因为context.ActionContext.Response 为空。如何将 cookie 添加到来自 AuthenticateAsync 的响应中?

查看相关:Setting Cookie values in HttpAuthenticationContext for IAuthenticationFilter (您可以在 cmets 中看到人们遇到同样的问题)。

【问题讨论】:

【参考方案1】:

您可能需要实现 IRequiresSessionState 才能使 cookie 持久保存?

见:http://www.strathweb.com/2012/11/adding-session-support-to-asp-net-web-api/

【讨论】:

不确定你的意思。我不需要服务器会话状态,我的 cookie 是一个简单的无状态令牌(如 JWT),但我没有从 Authorization 标头获取它,而是将其存储在 HttpOnly cookie 中。 好的,我明白了,我刚刚找到了这个:***.com/questions/22587992/… 链接中的实现基于不同的模式:cient 在“登录”URL 上检索令牌,然后在每个后续请求的 Authorization 标头中手动添加它。我想处理每个资源上的令牌生成(并使用cookie而不是Authorization标头),因此使用IAuthenticationFilter(我认为DelegatingHandler并不适合设置用户身份)。 【参考方案2】:

除了IAuthenticationFilter 之外,我还通过实现IActionFilter 使过滤器工作。这种方法是有效的,因为您可以在同一个地方访问请求、响应和用户身份。这是我的实现:

async Task<HttpResponseMessage> IActionFilter.ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)

    // Process the request pipeline and get the response (this causes the action to be executed)
    HttpResponseMessage response = await continuation();

    // If the user is authenticated and the token is not present in the request cookies, then it needs to be set
    CustomPrincipal principal = actionContext.ControllerContext.RequestContext.Principal as CustomPrincipal;
    if (principal != null && !actionContext.Request.Headers.GetCookies("MySessionCookie").Any())
    
        // Set the cookie in the response
        var cookie = new CookieHeaderValue("MySessionCookie", principal.AuthenticationToken)  Path = "/", HttpOnly = true ;
        response.Headers.AddCookies(new CookieHeaderValue[]  cookie );
    

    return response;

我发现这种方法非常不实用(混合接口),您绝对应该可以访问IAuthenticationFilter.AuthenticateAsync 中的响应(例如通过异步延续回调,或者能够访问操作结果(IHttpActionResult)上下文,就像在同一接口的ChallengeAsync 方法中一样)。

【讨论】:

【参考方案3】:

我的要求是添加标题,但应该很容易适应添加 cookie。

我对此采取了不同的方法。我将要添加的标头放入context.Request.Properties。然后在ChallengeAsync(不管每个请求都会调用它)通过IHttpActionResult 检查属性是否存在,如果存在则将其添加到标题中。像这样的:

protected class AddRenewOnAauthorizedResult : IHttpActionResult 

    public const string RenewalPropertyKey = "ETicket.RenewalKey";

    public AddRenewOnAauthorizedResult(HttpRequestMessage request, IHttpActionResult innerResult) 
        this.Request = request;
        this.InnerResult = innerResult;
    

    public HttpRequestMessage Request  get; set; 
    public IHttpActionResult InnerResult  get; set; 

    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) 

        HttpResponseMessage response = await this.InnerResult.ExecuteAsync(cancellationToken);

        if (Request.Properties.ContainsKey(RenewalPropertyKey)) Request.response.Headers.Add("X-ETicket-Renew", Request.Properties(RenewalPropertyKey));

        Return response;


然后在ChallengeAsync:

public Threading.Tasks.Task ChallengeAsync(HttpAuthenticationChallengeContext context, Threading.CancellationToken cancellationToken)


    context.Result = new AddRenewOnAauthorizedResult(context.Request, context.Result);
    return Task.FromResult(0);


【讨论】:

以上是关于从 Web Api 2 IAuthenticationFilter AuthenticateAsync 方法设置 cookie的主要内容,如果未能解决你的问题,请参考以下文章

如何使用适合导入的 Web Api 帮助页面从 Web Api 2 项目生成 JSON Postman 集合

使用 OWIN 身份从多个 API 客户端注册 Web API 2 外部登录

从Ember-Data格式化Web API 2的Json

从javascript传递到web api 2时如何隐藏或保护令牌

无法从 NET Core 2.2 Web API 连接到 docker sql server

框架从 4.5.2 升级到 4.7.2 后 C# Web API 引发 CORS 错误