现在我这样做是为了获取我需要的信息:
在我的基本控制器中:
public int roleId { get; private set; }
public int userId { get; private set; }
public void setUserAndRole()
{
ClaimsIdentity claimsIdentity;
var httpContext = HttpContext.Current;
claimsIdentity = httpContext.User.Identity as ClaimsIdentity;
roleId = Int32.Parse(claimsIdentity.FindFirst("RoleId").Value);
userId = Int32.Parse(User.Identity.GetUserId());
}
在我的控制器方法中:
public async Task<IHttpActionResult> getTest(int examId, int userTestId, int retrieve)
{
setUserAndRole();
我希望roleId和userId在我的类的构造函数中可用并填充,但是从我理解的构造函数在授权信息可用之前触发。
有人可以告诉我如何使用动作过滤器执行此操作吗?理想情况下,我希望Action Filter位于控制器级别,但如果没有,则可以在方法级别完成。
我希望得到一些好的建议和意见。谢谢
更新以显示System.Web.Http
#region Assembly System.Web.Http, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// C:\H\server\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll
#endregion
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
namespace System.Web.Http.Filters
{
//
// Summary:
// Represents the base class for all action-filter attributes.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IFilter
{
//
// Summary:
// Initializes a new instance of the System.Web.Http.Filters.ActionFilterAttribute
// class.
protected ActionFilterAttribute();
//
// Summary:
// Occurs after the action method is invoked.
//
// Parameters:
// actionExecutedContext:
// The action executed context.
public virtual void OnActionExecuted(HttpActionExecutedContext actionExecutedContext);
public virtual Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken);
//
// Summary:
// Occurs before the action method is invoked.
//
// Parameters:
// actionContext:
// The action context.
public virtual void OnActionExecuting(HttpActionContext actionContext);
public virtual Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken);
}
}
答案 0 :(得分:36)
根据您的方法签名(以及后面的注释),代码假定您使用的是Web API而不是MVC,尽管这也可以很容易地为MVC更改。
我确实想指出,如果你纯粹看待它的要求,我该如何创建一个可重用的可维护代码片段。在这种情况下,代码获取基于声明的信息并将其注入您的控制器。您要求过滤器的事实是技术要求,但我也将提出一个不使用过滤器而是使用IoC的解决方案,这增加了一些灵活性(恕我直言)。
一些提示
System.Web.HttpContext.Current
。很难对使用它的单元测试代码进行单元测试。 Mvc和Web API有一个名为HttpContextBase
的通用抽象,尽可能使用它。如果没有其他方法(我还没有看到),那么使用new HttpContextWrapper(System.Web.HttpContext.Current)
并将此实例传递给您想要使用的任何方法/类(HttpContextWrapper
派生自HttpContextBase
) 这些没有特别的顺序。请参阅end以获取每个解决方案的基本专业列表。
<强> UserInfo.cs 强>
这是我将在下面演示的两种解决方案中使用的常用代码。这是您希望访问的基于属性/声明的信息的常见抽象。这样,如果要添加对另一个属性的访问权限,只需扩展接口/类,就不必扩展方法。
using System;
using System.Security.Claims;
using System.Web;
using Microsoft.AspNet.Identity;
namespace MyNamespace
{
public interface IUserInfo
{
int RoleId { get; }
int UserId { get; }
bool IsAuthenticated { get; }
}
public class WebUserInfo : IUserInfo
{
public int RoleId { get; set; }
public int UserId { get; set; }
public bool IsAuthenticated { get; set; }
public WebUserInfo(HttpContextBase httpContext)
{
try
{
var claimsIdentity = httpContext.User.Identity as ClaimsIdentity;
IsAuthenticated = httpContext.User.Identity.IsAuthenticated;
if (claimsIdentity != null)
{
RoleId = Int32.Parse(claimsIdentity.FindFirst("RoleId").Value);
UserId = Int32.Parse(claimsIdentity.GetUserId());
}
}
catch (Exception ex)
{
IsAuthenticated = false;
UserId = -1;
RoleId = -1;
// log exception
}
}
}
}
此解决方案演示了您所要求的内容,这是一个可重复使用的Web API过滤器,用于填充基于声明的信息。
<强> WebApiClaimsUserFilter.cs 强>
using System.Web;
using System.Web.Http.Controllers;
namespace MyNamespace
{
public class WebApiClaimsUserFilterAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
// access to the HttpContextBase instance can be done using the Properties collection MS_HttpContext
var context = (HttpContextBase) actionContext.Request.Properties["MS_HttpContext"];
var user = new WebUserInfo(context);
actionContext.ActionArguments["claimsUser"] = user; // key name here must match the parameter name in the methods you want to populate with this instance
base.OnActionExecuting(actionContext);
}
}
}
现在,您可以通过将此过滤器应用于Web API方法(如属性或类级别)来使用此过滤器。如果您想在任何地方访问,您也可以将其添加到 WebApiConfig.cs 代码中,如此(可选)。
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new WebApiClaimsUserFilterAttribute());
// rest of code here
}
}
<强> WebApiTestController.cs 强>
这里有如何在Web API方法中使用它。请注意,匹配是根据参数名称完成的,这必须与过滤器actionContext.ActionArguments["claimsUser"]
中指定的名称相匹配。现在,您的方法将使用过滤器中添加的实例进行填充。
using System.Web.Http;
using System.Threading.Tasks;
namespace MyNamespace
{
public class WebApiTestController : ApiController
{
[WebApiClaimsUserFilterAttribute] // not necessary if registered in webapiconfig.cs
public async Task<IHttpActionResult> Get(IUserInfo claimsUser)
{
var roleId = claimsUser.RoleId;
await Task.Delay(1).ConfigureAwait(true);
return Ok();
}
}
}
这是wiki on Inversion of Control和wiki on Dependency Injection。这些术语IoC和DI通常可互换使用。简而言之,您可以定义依赖项,使用DI或IoC框架注册它们,然后将这些依赖项实例注入到运行的代码中。
有许多IoC框架,我使用了AutoFac,但你可以使用你想要的任何东西。按照这种方法,您可以定义一次注射,并随时随地访问它们。只需在构造函数中引用我的新接口,它就会在运行时注入实例。
<强> DependencyInjectionConfig.cs 强>
using System.Reflection;
using System.Web.Http;
using System.Web.Mvc;
using Autofac;
using Autofac.Integration.Mvc;
using Autofac.Integration.WebApi;
namespace MyNamespace
{
public static class DependencyInjectionConfig
{
/// <summary>
/// Executes all dependency injection using AutoFac
/// </summary>
/// <remarks>See AutoFac Documentation: https://github.com/autofac/Autofac/wiki
/// Compare speed of AutoFac with other IoC frameworks: http://nareblog.wordpress.com/tag/ioc-autofac-ninject-asp-asp-net-mvc-inversion-of-control
/// </remarks>
public static void RegisterDependencyInjection()
{
var builder = new ContainerBuilder();
var config = GlobalConfiguration.Configuration;
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterControllers(typeof(DependencyInjectionConfig).Assembly);
builder.RegisterModule(new AutofacWebTypesModule());
// here we specify that we want to inject a WebUserInfo wherever IUserInfo is encountered (ie. in a public constructor in the Controllers)
builder.RegisterType<WebUserInfo>()
.As<IUserInfo>()
.InstancePerRequest();
var container = builder.Build();
// For Web API
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
// 2 lines for MVC (not web api)
var resolver = new AutofacDependencyResolver(container);
DependencyResolver.SetResolver(resolver);
}
}
}
现在我们只需要在应用程序启动时调用它,这可以在 Global.asax.cs 文件中完成。
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Http;
namespace MyNamespace
{
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
DependencyInjectionConfig.RegisterDependencyInjection();
// rest of code
}
}
}
现在我们可以随时随地使用它。
<强> WebApiTestController.cs 强>
using System.Web.Http;
using System.Threading.Tasks;
namespace MyNamespace
{
public class WebApiTestController : ApiController
{
private IUserInfo _userInfo;
public WebApiTestController(IUserInfo userInfo)
{
_userInfo = userInfo; // injected from AutoFac
}
public async Task<IHttpActionResult> Get()
{
var roleId = _userInfo.RoleId;
await Task.Delay(1).ConfigureAwait(true);
return Ok();
}
}
}
以下是此示例中您可以从NuGet获得的依赖项。
Install-Package Autofac
Install-Package Autofac.Mvc5
Install-Package Autofac.WebApi2
我想到的另一个解决方案。您从未指定过为什么需要用户和角色ID。也许您想在继续之前检查方法中的访问级别。如果是这种情况,最好的解决方案是不仅要实现过滤器,还要创建System.Web.Http.Filters.AuthorizationFilterAttribute
的覆盖。这允许您在代码执行之前执行授权检查,如果您在Web api界面上具有不同的访问级别,则非常方便。我放在一起的代码说明了这一点,但您可以扩展它以添加对存储库的实际调用以进行检查。
<强> WebApiAuthorizationClaimsUserFilterAttribute.cs 强>
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http.Controllers;
namespace MyNamespace
{
public class WebApiAuthorizationClaimsUserFilterAttribute : System.Web.Http.Filters.AuthorizationFilterAttribute
{
// the authorized role id (again, just an example to illustrate this point. I am not advocating for hard coded identifiers in the code)
public int AuthorizedRoleId { get; set; }
public override void OnAuthorization(HttpActionContext actionContext)
{
var context = (HttpContextBase) actionContext.Request.Properties["MS_HttpContext"];
var user = new WebUserInfo(context);
// check if user is authenticated, if not return Unauthorized
if (!user.IsAuthenticated || user.UserId < 1)
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "User not authenticated...");
else if(user.RoleId > 0 && user.RoleId != AuthorizedRoleId) // if user is authenticated but should not have access return Forbidden
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden, "Not allowed to access...");
}
}
}
<强> WebApiTestController.cs 强>
using System.Web.Http;
using System.Threading.Tasks;
namespace MyNamespace
{
public class WebApiTestController : ApiController
{
[WebApiAuthorizationClaimsUserFilterAttribute(AuthorizedRoleId = 21)] // some role id
public async Task<IHttpActionResult> Get(IUserInfo claimsUser)
{
// code will only be reached if user is authorized based on the filter
await Task.Delay(1).ConfigureAwait(true);
return Ok();
}
}
}
AuthorizationFilterAttribute
是最佳选择。如果授权通过,您可以将解决方案#1中的过滤器中的代码添加到此代码中,这样您仍然可以在代码中访问用户信息以用于其他目的。答案 1 :(得分:9)
创建自定义ActionFilter类(用于OnActionExecuting):
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
namespace YourNameSpace
{
public class CustomActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ClaimsIdentity claimsIdentity = HttpContext.Current.User.Identity as ClaimsIdentity;
filterContext.ActionParameters["roleId"] = int.Parse(claimsIdentity.FindFirst("RoleId").Value);
filterContext.ActionParameters["userId"] = int.Parse(claimsIdentity.GetUserId());
}
}
}
然后选择基本控制器,控制器或操作(取决于您要应用自定义过滤器的级别),并将roleId和userId指定为操作参数:
[CustomActionFilter]
public async Task<IHttpActionResult> getTest(int roleId, int userId, int examId, int userTestId, int retrieve)
{
// roleId and userId available to use here
// Your code here
}
希望应该这样做。