从 IHttpHandler 获取 Owin IIdentity
Get Owin IIdentity from IHttpHandler
已接受的答案说明:
虽然我很感激创建我自己的 OwinMiddleware 来在做一些检查而不是 IHttpModule 后发送图像,但这并不能完全解决问题。
问题是我在 ajax 请求中添加了一个授权 header,并且在那个 header 中我正在发送我的 Bearer's Token 以便我可以从中获取记录的用户信息欧文。所以我必须将此 header 添加到图像请求中,以便能够从图像处理程序中间件获取记录的用户信息。
原题:
我正在关注 this blog post 为我的 Web 项目创建基于令牌的身份验证。因为我的 Web API 的某些资源将被本机移动客户端使用。我听说基于令牌的身份验证是实现这一目标的方法。在我自己的项目中,我有一个自定义图像请求处理程序。并且需要在此处理程序中记录用户信息。但是当我尝试从票证中提取用户信息时,我得到了空值。我对此不确定,但是,我想我这里有 2 个不同的 IIdentity object,我需要存储在 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 的辅助函数 object
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 任务实现。其他一切都与接受的答案中推荐的一样。但如上所述,要使其正常工作,我必须使用授权 header 发送图像,否则 loggedUserId 将再次为空。
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();
如果你遵循了Taiseer sample,你会在配置授权模块后这样做:
// 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 中间件有一个有趣的 article 值得一读。
已接受的答案说明:
虽然我很感激创建我自己的 OwinMiddleware 来在做一些检查而不是 IHttpModule 后发送图像,但这并不能完全解决问题。
问题是我在 ajax 请求中添加了一个授权 header,并且在那个 header 中我正在发送我的 Bearer's Token 以便我可以从中获取记录的用户信息欧文。所以我必须将此 header 添加到图像请求中,以便能够从图像处理程序中间件获取记录的用户信息。
原题:
我正在关注 this blog post 为我的 Web 项目创建基于令牌的身份验证。因为我的 Web API 的某些资源将被本机移动客户端使用。我听说基于令牌的身份验证是实现这一目标的方法。在我自己的项目中,我有一个自定义图像请求处理程序。并且需要在此处理程序中记录用户信息。但是当我尝试从票证中提取用户信息时,我得到了空值。我对此不确定,但是,我想我这里有 2 个不同的 IIdentity object,我需要存储在 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 的辅助函数 object
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 任务实现。其他一切都与接受的答案中推荐的一样。但如上所述,要使其正常工作,我必须使用授权 header 发送图像,否则 loggedUserId 将再次为空。
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();
如果你遵循了Taiseer sample,你会在配置授权模块后这样做:
// 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 中间件有一个有趣的 article 值得一读。