在ASP.NET Core中获取对自定义中间件中所请求的Controller和Action的引用

时间:2018-01-31 06:36:21

标签: c# asp.net-core asp.net-core-mvc

我正在开发一个自定义中间件来验证调用API的客户端。

我使用属性来定义Action是否需要身份验证,但我无法弄清楚如何获取对所请求的 Controller Action 的引用调用方法。

以下是我目前的代码

AuthenticateClient.cs

public class AuthenticateClient
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    private readonly GenericUnitOfWork _worker;

    public AuthenticateClient(RequestDelegate next, ApiDbContext db, IHttpContextAccessor httpContext, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<Utility.LCLog.Settings> settings)
    {
        _next = next;
        _logger = loggerFactory.CreateLogger(settings.Value.ApplicationName);
        _worker = new GenericUnitOfWork(new AppHelper(httpContext, db, env));
    }

    public async Task Invoke(HttpContext context)
    {
        if (!context.Request.Headers.Keys.Contains("ClientAuth"))
        {
            _logger.LogWarning("ClientAuth missing in request", new string[] { "Host: " + context.Request.Host, "IP: " + context.Request.HttpContext.Connection.RemoteIpAddress });

            context.Response.StatusCode = 400;
            await context.Response.WriteAsync("ClientAuth missing from request header values");

            return;
        }
        else
        {
            string[] tmp = context.Request.Headers["ClientAuth"].ToString().Split("/");

            if (tmp.Length != 2)
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsync("The format of the ClientAuth value is wrong");

                return;
            }
            else
            {
                Client client;
                string key, pass;

                key = tmp[0];
                pass = tmp[1];

                client = await _worker.GetRepo<Client>().SingleOrDefault(clnt => clnt.Active && clnt.Key.Equals(key) && clnt.Password.Equals(pass));

                if (client == null)
                {
                    _logger.LogWarning("Client authentication failed", new string[] { "Key: " + key, "Password: " + pass, "Host: " + context.Request.Host, "IP: " + context.Request.HttpContext.Connection.RemoteIpAddress });

                    context.Response.StatusCode = 401;
                    await context.Response.WriteAsync("Authentication failed");

                    return;
                }
            }                
        }

        await _next.Invoke(context);
    }
}

ClientAuthenticationAttribute.cs

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ClientAuthenticationAttribute : Attribute
{
    private readonly bool _authRequired;

    public ClientAuthenticationAttribute(bool authRequired = true)
    {
        _authRequired = authRequired;
    }

    public bool AuthRequired { get { return _authRequired; } }
}

1 个答案:

答案 0 :(得分:1)

我建议您拆分身份验证授权的逻辑,并将它们保存在不同的位置。

回顾here

  • 身份验证是验证您是谁的过程。

  • 授权是验证我们知道您是谁,您可以访问特定资源的过程

您目前正在尝试做的是在中间件组件中对您的用户进行身份验证和授权。虽然可能可能通过将所有这些逻辑移动到您使用api框架注册的过滤器(无论是ASP.NET核心MVC,Web API 2还是其他东西)来实现它,但这意味着您的其他任何中间件组件都无法访问用户数据(我猜测,这是您首先选择在中间件中实现它的原因之一)。

鉴于您对身份验证和授权分离的新知识,可能的解决方案是执行以下操作:

仅用于身份验证的中间件

在您的中间件中,只关注 进行身份验证,并在以后的管道中将授权保留给组件。实际上,这意味着您的中间件应该执行以下操作:

  1. 查找用户令牌,Cookie或您用于验证其请求的任何内容
  2. 如果不存在,请将请求视为匿名,并调用下一个管道组件,而不将用户附加到请求上下文。
  3. 如果存在有效令牌,请从中解析用户数据(例如,从JWT解析用户的声明,在数据库中查找角色等等)并将其存储在请求上下文中。我发现创建IPrincipal并为其设置context.Request.User以及直接向上下文字典添加信息都很有用。
  4. 在请求上下文中注册用户,调用下一个管道组件。
  5. 假设认证用户

    的授权

    您现在可以重新编写授权逻辑,以假设已经在请求上下文中注册了经过身份验证的用户。

    在ASP.NET Web API 2应用程序中,您实现了从AuthorizationFilterAttribute继承的自定义过滤器属性,以确保它运行第一个过滤器。例如,在我当前的应用程序中,我们具有以下属性来授权用户具有特定声明。请注意,它没有做任何工作来确定用户是谁;如果用户未附加到上下文,则响应只是Unauthorized。您可以在这里变得更加复杂,并且对于缺少访问权限的用户的身份验证请求不同地处理匿名请求,例如,将匿名请求重定向到登录表单,同时重定向无法访问错误页面的用户,同时说明这一点。

    [AttributeUsage(validOn: AttributeTargets.Method)]
    public class AuthorizeClaimsFilterAttribute : AuthorizationFilterAttribute
    {
        public AuthorizeClaimsFilterAttribute(string claimType, string claimValue)
        {
            ClaimType = claimType;
            ClaimValue = claimValue;
        }
    
        public string ClaimType { get; }
        public string ClaimValue { get; }
    
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            if (!(actionContext.RequestContext.Principal is ClaimsPrincipal principal)
                || !principal.HasClaim(x => x.Type == ClaimType && x.Value == ClaimValue))
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
    }
    

    要使用它,我们只需用它来装饰动作方法:

    [AuthorizeClaimsFilter("urn:ourapp:claims:admin", true)]
    public IHttpActionResults OnlyAdminsCanAccess() { /* ... */ }