Get Owin IIdentity from IHttpHandler



接受的答案注释:

尽管我很感激创建自己的OwinMiddleware来代替IHttpModule在进行一些检查后发送图像的帮助,但这并不能完全解决问题。

问题是,我在ajax请求中添加了一个Authorization头,在这个头中,我发送我的Bearer's Token,这样我就可以从Owin获得记录的用户信息。因此,我也必须将这个头添加到图像请求中,以便能够从图像处理程序中间件中获取记录的用户信息。


原始问题:

我关注这篇博客文章,为我的网络项目创建基于令牌的身份验证。因为我的Web API的一些资源将被本地移动客户端使用。我听说基于令牌的身份验证是实现这一点的方法。在我自己的项目中,我有一个自定义的图像请求处理程序。并且需要在此处理程序中记录的用户信息。但当我试图从票证中提取用户信息时,我得到了null。我不确定,但是,我想我这里有两个不同的IIdentity对象,我需要一个存储在Owin Context中的对象。

让我给你看一些代码;

我的GrantResourceOwnerCredentials,它将索赔存储到ClaimsIdentity,中

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
....
// checking user credentials and get user information into 'usr' variable
....
            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));
            identity.AddClaim(new Claim(ClaimTypes.Sid, usr.UserId.ToString()));
            var props = new AuthenticationProperties(new Dictionary<string, string>
                {
                    {
                        "as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
                    },
                    {
                        "userId", usr.UserId.ToString()
                    }
                });
            var ticket = new AuthenticationTicket(identity, props);
            context.Validated(ticket);
}

从给定IIdentity对象中提取用户id的Helper函数

public class utils {
    public Guid? GetUserIdFromTicket(IIdentity identity)
    {
        var cId = (ClaimsIdentity)identity;
        var uid = cId.FindFirst(ClaimTypes.Sid);
        if (uid != null && Comb.IsComb(uid.Value))
            return new Guid(uid.Value);
        else
            return null;
    }
....
}

现在我可以从我的控制器中获得loggedUserId,比如

    var loggedUserId = utils.GetUserIdFromTicket(User.Identity);

但如果我从我的IHttpHandler调用它,我会得到null,

    public class ImageHandler : IHttpHandler
    {
        public ImageHandler()
        {
        }
        public ImageHandler(RequestContext requestContext)
        {
            RequestContext = requestContext;
        }
        protected RequestContext RequestContext { get; set; }
        public utils utils = new utils(); // changed name for simplicity.
        public void ProcessRequest(HttpContext context)
        {
            var strUserId = RequestContext.RouteData.Values["userid"].ToString();
            var strContentId = RequestContext.RouteData.Values["contentid"].ToString();
            var fileName = RequestContext.RouteData.Values["filename"].ToString();
            var size = RequestContext.RouteData.Values["size"].ToString();
            var loggedUserId = utils.GetUserIdFromTicket(context.User.Identity);
....
image processing
....
            context.Response.End();
        }
}

希望我没有永远把这件事搞砸。。。

解决方案:

我已经实现了我自己的中间件,在进行一些检查后为我的用户提供图像。这是我的Invoke任务实现。其他一切都和公认答案中建议的一样。但如上所述,为了实现这一点,我必须发送带有Authorization标头的图像,否则loggedUserId将再次为null。

public async override Task Invoke(IOwinContext context)
{
    // need to interrupt image requests having src format : http://www.mywebsite.com/myapp-img/{userid}/{contentId}/{fileName}/{size}/
    if (context.Request.Path.HasValue && context.Request.Path.Value.IndexOf("myapp-img") > -1)
    {
        // get values from url.
        var pathValues = context.Request.Path.Value.Split('/');
        var strUserId = pathValues[2].ToString();
        var strContentId = pathValues[3].ToString();
        var fileName = pathValues[4].ToString();
        var size = pathValues[5].ToString();
        // check if code returned a notfound or unauthorized image as response.
        var hasError = false;
        // get userId from static utils class providing current owin identity object
        var loggedUserId = ChildOnBlogUtils.GetUserIdFromTicket(context.Request.User.Identity);
        // save root path of application to provide error images.
        var rootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
        // assign content type of response to requested file type
        context.Response.ContentType = ChildOnBlogUtils.GetContentType(context.Request.Path.Value.ToString());
        // if user requested thumbnail send it without doing checks
        if (size == "thumb")
        {
            imgPath = "images/" + strUserId.ToLower() + "/thumbnail/" + fileName;
        }
        else
        {
            var canSee = false;
            // check if user can see the content and put the result into canSee variable
            // I am using loggedUserId inside these checks
            ...
            ...
            // end checks
            if (canSee)
            {
                // removed some more checks here for simplicity
                imgPath = "images/" + strUserId.ToLower() + "/" + fileName;
            }
            else
            {
                context.Response.ContentType = "Image/png";
                var imgData = File.ReadAllBytes(rootPath + "/images/unauthorized.png");
                await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
                hasError = true;
            }
        }
        if (!hasError) // if no errors have been risen until this point. try to provide the requested image to user.
        {
            try
            {
                var imgData = UserMediaContainer.GetFileContent(imgPath); // get file from storage account (azure)
                if (imgData.Length == 0)
                {
                    context.Response.ContentType = "Image/png";
                    imgData = File.ReadAllBytes(rootPath + "/images/notfound.png");
                    await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
                }
                else
                {
                    await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
                }
            }
            catch (Exception ex)
            {
                context.Response.ContentType = "Image/png";
                var imgData = File.ReadAllBytes(rootPath + "/images/notfound.png");
                await context.Response.Body.WriteAsync(imgData, 0, imgData.Length);
            }
        }
    }
    else if (context.Request.Path.HasValue && context.Request.Path.Value.IndexOf("profile-img") > -1)
    {
        // profile image provider. Same code as providing thumbnails.
    }
    else
    {
        // if it is not an image request to be handled. move to the next middleware.
        await Next.Invoke(context);
    }
}

我猜您的ImageHandler是在owin管道中的所有其他内容之前处理的,这意味着它是在授权到位之前处理的。

由于您正在使用owin,我建议您放弃IHttpHandler,使用一些自定义的owin中间件。遵循此路径将允许您在管道中的正确位置注入模块。

创建中间件非常容易:

public class ImageProcessingMiddleware : OwinMiddleware
{
    public ImageProcessingMiddleware(OwinMiddleware next): base(next)
    {
    }
    public async override Task Invoke(IOwinContext context)
    {
        string username = context.Request.User.Identity.Name;
        Console.WriteLine("Begin Request");
        await Next.Invoke(context);
        Console.WriteLine("End Request");
    }
}

一旦定义了中间件,就可以为实例化创建一个扩展方法:

public static class ImageProcessingExtensions
{
    public static IAppBuilder UseImageProcessing(this IAppBuilder app)
    {
        return app.Use<ImageProcessingMiddleware>();
    }
}

现在您可以在管道中插入中间件:

app.UseImageProcessing();

如果你已经遵循了Taiser示例,你会在配置了授权模块后这样做:

// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

回到中间件,您可能已经注意到有一种叫做Invoke:的方法

public async override Task Invoke(IOwinContext context)
{
    string username = context.Request.User.Identity.Name;
    Console.WriteLine("Begin Request");
    await Next.Invoke(context);
    Console.WriteLine("End Request");
}

这是每个中间件的入口点。正如您所看到的,我正在读取授权令牌经过验证和授权后立即授权的用户名。

有一篇关于owin中间件的有趣文章值得一读。

相关内容

  • 没有找到相关文章

最新更新