使用自定义身份验证自定义AuthorizeAttribute

时间:2012-10-01 11:23:36

标签: asp.net-mvc authorize-attribute custom-authentication

我使用ASP.NET MVC 4 Web应用程序作为某些WCF服务的前端。 所有用户登录/注销和会话控制都在后端完成。 MVC应用程序应该只存储具有会话ID的单个cookie。我的客户端不允许使用表单身份验证,所有内容都必须自定义。

我在web.config中设置了以下内容:

  <system.web>
...
    <authentication mode="None" />
  </system.web>

  <system.webServer>
    <modules>
...
      <remove name="FormsAuthentication" />
...    
    </modules>
  </system.webServer>

我还有一个全局过滤器:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        // Force all actions to request auth. Only actions marked with [AllowAnonymous] will be allowed.
        filters.Add(new MyAuthorizeAttribute());
    }
}

在Global.asax中调用

   FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

我已经使用[AllowAnonymous]标记了每个不需要授权的控制器和操作。

现在我必须实现MyAuthorizeAttribute。我尝试了一些教程,但没有一个完全符合我的场景。

基本上,我必须针对每个操作处理以下方案:

  1. 如果有有效的cookie,则应考虑当前请求 授权(不会检查任何角色,只有一种用户)。
  2. 如果没有cookie,我应该 覆盖默认的MVC处理程序(尝试加载帐户/登录) 并将用户重定向到主页/索引页面,并显示用户的消息 应该登录。
  3. 如果WCF方法调用抛出 FaultException我们的自定义SecurityFault说 该会话已过期(SecurityFault具有自定义枚举字段 其中包含异常的原因),我应该破坏我的习惯 会话cookie并再次将用户重定向到Home / Index页面 用户应该登录的消息,因为他的上次会话有 过期。对于所有其他的SecurityFaults,我可以让他们通过 - 我 有一个全局错误处理程序。
  4. 据我了解,我需要覆盖AuthorizeCore(检查我的cookie以查看会话是否存在且仍然有效)和HandleUnauthorizedRequest(将用户重定向到Home / Index而不是默认登录页面)。

    对于重定向,我试过:

        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {            
            base.HandleUnauthorizedRequest(filterContext);
            filterContext.Result = new RedirectResult("/Home/Index/NeedsLogin");
        }
    

    这似乎处理了第二次罚款的情况(我不确定那个基本呼叫,但它是否需要?)。

    对于第一种情况,我需要实现AuthorizeCore。我不确定,如何正确地做到这一点。我已经看到AuthorizeAttribute有一些用于处理缓存情况的代码,可能还有更多隐藏的功能,我不想破解它。

    对于第三种情况,我不确定MyAuthorizeAttribute是否能够处理它。 AuthorizeAttribute可以捕获Action内部发生的异常,或者我必须在我的全局错误处理程序中处理SecurityFault.SessionExpired情况吗?

3 个答案:

答案 0 :(得分:7)

不完全确定我得到了它但是如果您创建一个继承自System.Web.MVC.Authorize属性的自定义授权过滤器。

    public class CustomAuthorize : AuthorizeAttribute
    {
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (CookieIsValid(filterContext.Request.Cookies["cookieyouwant"])
        {
             filterContext.Result = new RedirectResult("DestUrl");
        }
        else
        {
            filterContext.Result = new RedirectResult("/Home/Index/NeedsLogin");
        }
    }
}

然后装饰你需要使用这个授权的方法来实现这个技巧吗?

答案 1 :(得分:3)

以下是我现在的表现:

  public class MyAuthorizeAttribute : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            bool authorized = false;

            /// MVC 4 boilerplate code follows
            if (filterContext == null)
                throw new ArgumentNullException("filterContext");

            bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
                          || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);

            if (skipAuthorization)
            {
                return;
            }

            if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
            {
                throw new InvalidOperationException(
                    "MyAuthorizeAttribute cannot be used within a child action caching block."
                );
            }
            // end of MVC code


            // custom code
            if (!AuthorizeCore(filterContext.HttpContext))
            {
                // if not authorized from some other Action call, let's try extracting user data from custom encrypted cookie
                var identity = MyEncryptedCookieHelper.GetFrontendIdentity(filterContext.HttpContext.Request);
                // identity might be null if cookie not received
                if (identity == null)
                {
                    filterContext.HttpContext.User = new GenericPrincipal(new GenericIdentity(""), null);
                }
                else
                {
                    authorized = true;
                    filterContext.HttpContext.User = new MyFrontendPrincipal(identity);
                }

                // make sure the Principal's are in sync - there might be situations when they are not!
                Thread.CurrentPrincipal = filterContext.HttpContext.User;
            }

            // MVC 4 boilerplate code follows
            if (authorized)
            {
                // ** IMPORTANT **
                // Since we're performing authorization at the action level, the authorization code runs
                // after the output caching module. In the worst case this could allow an authorized user
                // to cause the page to be cached, then an unauthorized user would later be served the
                // cached page. We work around this by telling proxies not to cache the sensitive page,
                // then we hook our custom authorization code into the caching mechanism so that we have
                // the final say on whether a page should be served from the cache.

                HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
                cachePolicy.SetProxyMaxAge(new TimeSpan(0));
                cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
            }
            else
            {
                HandleUnauthorizedRequest(filterContext);
            }
            //end of MVC code
        }

        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext == null)
                throw new ArgumentNullException("httpContext");

            // check to make sure the user is authenticated as my custom identity
            var principal = httpContext.User as MyFrontendPrincipal;
            if (principal == null)
                return false;

            var identity = principal.Identity as MyFrontendIdentity;
            if (identity == null)
                return false;

            return true;
        }

        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {            
            // default MVC result was:
            // filterContext.Result = new HttpUnauthorizedResult();

            // but I redirect to index login page instead of kicking 401
            filterContext.Result = new RedirectResult("/Home/Index/NeedsLogin");
        }

        // MVC 4 boilerplate code follows
        private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
        {
            validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
        }

        // This method must be thread-safe since it is called by the caching module.
        protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
        {
            if (httpContext == null)
                throw new ArgumentNullException("httpContext");

            bool isAuthorized = AuthorizeCore(httpContext);
            return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
        }
    } 

但它不处理我的第三种情况,所以我将在全局错误处理程序中实现它。

答案 2 :(得分:3)

关于您的第一个要求:

正如您已经发现的那样,OnAuthorization负责处理多个方面,包括:缓存。
如果您只想自定义验证用户凭据的方式,我建议您改为覆盖AuthorizeCore。 E.g:

public class ClientCookieAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        HttpCookie cookie = httpContext.Request.Cookies[_tokenCookieName];

        bool isAuthenticated = ValidateUserByCookie(cookie);

        return isAuthenticated;
    }

    private bool ValidateUserByCookie(HttpCookie cookie)
    {
        var result = false;
        // Perform validation
        // You could include httpContext as well, to check further information
        return result;
    }

    private static const string _tokenCookieName = "myCookieName";
}

您可能还想看一下其他主题:

  1. SO - Custom Authorize Attribute
  2. ASP.NET - Custom AuthorizationFilter redirect problems
  3. Diary of a ninja