【问题标题】:Using an MVC HtmlHelper from a WebForm从 WebForm 使用 MVC HtmlHelper
【发布时间】:2010-11-23 18:43:41
【问题描述】:

我正在向混合 WebForms/MVC 站点添加一些 UI 功能。在这种情况下,我将一些 AJAX UI 功能添加到 WebForms 页面(通过 jQuery),并且数据来自 MVC JsonResult。一切都 100% 正常工作,只有一个例外:

我想实现 AntiForgeryToken 的 XSRF 保护。我已将它与纯 MVC 应用程序上的 ValidateAntiForgeryToken 属性结合使用,但想知道如何在 WebForms 中实现 Html.AntiForgeryToken() 方法。 Here's an example using a UrlHelper.

我在正确“模拟” ViewContext / RequestContext 时遇到了一些麻烦。我应该如何在 WebForms 页面中使用 HtmlHelpers?

编辑: 我希望从我的 WebForms 页面而不是 MVC JsonResult 中检索 AntiForgeryToken。

【问题讨论】:

  • 我有同样的问题 - 一个遗留的 WebForms 页面需要发布到带有 AntiForgeryToken 的 MVC 操作。我想将Html.AntiForgeryToken() 添加到 WebForms 页面而不用 MVC 重写。

标签: asp.net-mvc webforms html-helper


【解决方案1】:

我知道这是一个老问题,但我今天遇到了这个问题并想分享一下。我在 MVC4 中工作,并且有一个在 MVC(通过 RenderPartial)和 WebForms 之间共享的 webform 控件(.ascx)。在那个控制中,我需要一个防伪令牌。幸运的是,现在您可以在 Web 表单中使用一个助手,就像这样简单:

<%= AntiForgery.GetHtml() %>

这将像在 MVC 中一样呈现您的防伪令牌。

Here's the MS link to it.

【讨论】:

  • 太好了,这是解决WebForms 2/MVC 3及更高版本问题的更好方法。
【解决方案2】:

关键方法在MVC源码中:GetAntiForgeryTokenAndSetCookie

这会创建一个名为 AntiForgeryData 的内部密封类的实例。

实例被序列化为 cookie “__RequestVerificationToken_” + 应用程序路径的 base 64 编码版本。

AntiForgeryData 的同一实例被序列化为隐藏输入。

AntiForgeryData 的独特部分由RNGCryptoServiceProvider.GetBytes() 获得

所有这些都可以在 WebForms 页面中被欺骗,唯一混乱的地方是隐藏的密封类的序列化。不幸的是,关键方法(GetAntiForgeryTokenAndSetCookie)依赖于ViewContext.HttpContext.Request 来获取cookie,而WebForm 需要使用HttpContext.Current.Request


更新

没有太多的测试和大量的代码,但我想我已经通过一点反思解决了这个问题。在我使用反射的地方,我留下了上面注释掉的等效行:

using System;
using System.Reflection;
using System.Web;
using System.Web.Mvc;

/// <summary>Utility to provide MVC anti forgery tokens in WebForms pages</summary>
public class WebFormAntiForgery
{
    /// <summary>Create an anti forgery token in a WebForms page</summary>
    /// <returns>The HTML input and sets the cookie</returns>
    public static string AntiForgeryToken()
    {
        string formValue = GetAntiForgeryTokenAndSetCookie();

        // string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
        var mvcAssembly = typeof(HtmlHelper).Assembly;
        var afdType = mvcAssembly.GetType("System.Web.Mvc.AntiForgeryData");
        string fieldName = Convert.ToString(afdType.InvokeMember(
            "GetAntiForgeryTokenName",
            BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod,
            null,
            null,
            new object[] { null }));

        TagBuilder builder = new TagBuilder("input");
        builder.Attributes["type"] = "hidden";
        builder.Attributes["name"] = fieldName;
        builder.Attributes["value"] = formValue;
        return builder.ToString(TagRenderMode.SelfClosing);
    }

    static string GetAntiForgeryTokenAndSetCookie()
    {
        var mvcAssembly = typeof(HtmlHelper).Assembly;
        var afdType = mvcAssembly.GetType("System.Web.Mvc.AntiForgeryData");

        // new AntiForgeryDataSerializer();
        var serializerType = mvcAssembly.GetType("System.Web.Mvc.AntiForgeryDataSerializer");
        var serializerCtor = serializerType.GetConstructor(new Type[0]);
        object serializer = serializerCtor.Invoke(new object[0]); 

        // string cookieName = AntiForgeryData.GetAntiForgeryTokenName(HttpContext.Current.Request.ApplicationPath);
        string cookieName = Convert.ToString(afdType.InvokeMember(
            "GetAntiForgeryTokenName",
            BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod,
            null,
            null,
            new object[] { HttpContext.Current.Request.ApplicationPath }));

        // AntiForgeryData cookieToken;
        object cookieToken;
        HttpCookie cookie = HttpContext.Current.Request.Cookies[cookieName];
        if (cookie != null)
        {
            // cookieToken = Serializer.Deserialize(cookie.Value);
            cookieToken = serializerType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, null, serializer, new object[] { cookie.Value });
        }
        else
        {
            // cookieToken = AntiForgeryData.NewToken();
            cookieToken = afdType.InvokeMember(
                "NewToken",
                BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod,
                null,
                null,
                new object[0]);

            // string cookieValue = Serializer.Serialize(cookieToken);
            string cookieValue = Convert.ToString(serializerType.InvokeMember("Serialize", BindingFlags.InvokeMethod, null, serializer, new object[] { cookieToken }));

            var newCookie = new HttpCookie(cookieName, cookieValue) { HttpOnly = true };

            HttpContext.Current.Response.Cookies.Set(newCookie);
        }

        // AntiForgeryData formToken = new AntiForgeryData(cookieToken)
        // {
        //     CreationDate = DateTime.Now,
        //     Salt = salt
        // };
        var ctor = afdType.GetConstructor(new Type[] { afdType });
        object formToken = ctor.Invoke(new object[] { cookieToken });

        afdType.InvokeMember("CreationDate", BindingFlags.SetProperty, null, formToken, new object[] { DateTime.Now });
        afdType.InvokeMember("Salt", BindingFlags.SetProperty, null, formToken, new object[] { null });

        // string formValue = Serializer.Serialize(formToken);
        string formValue = Convert.ToString(serializerType.InvokeMember("Serialize", BindingFlags.InvokeMethod, null, serializer, new object[] { formToken }));
        return formValue;
    }
}

然后用法类似于MVC:

WebFormAntiForgery.AntiForgeryToken()

它创建与 MVC 方法相同的 cookie 和 HTML。

我没有使用 salt 和 domain 方法,但它们很容易添加。

【讨论】:

  • 嗨,我刚刚尝试在 webforms 应用程序(.NET 4.0,MVC 3.0)中实现这一点,并从这一行得到“对象引用未设置为对象的实例”错误:var serializerCtor = serializerType.GetConstructor(新类型[0]);有人可以帮忙吗?我在这里有点超出我的深度。
  • @BFOT 这是在 ASP.Net 2,MVC 1 上编写的,完全是 hack - 我正在反映他们的防伪令牌内容,但它都是私有方法。很有可能这需要对每个新版本的 ASP 和 MVC 进行一些更新,下载 MVC 3 源代码并查看方法是如何变化的——如果我们非常幸运,他们甚至可能已经暴露了它们,以便 WebForms 可以使用它们。
  • 感谢您的回复。这个周末我去看看最新的 MVC 源码,看看能不能解决。如果有空闲时间,请随时发布上述代码的修订版本。 ;)
  • 看我的帖子;有一个新的受支持的助手,您可以使用它来获取您的网络表单页面中的功能。
  • @BFOT @pvanhouten 在 MVC3 中有更好的方法,现在内置了完全支持的 AntiForgery.GetHtml() 方法。我的解决方案仅适用于 MVC1-2。
【解决方案3】:

默认情况下,ASP.NET WebForms 已经包含验证事件和视图状态的措施。 Phil Haack 在链接的帖子中对此进行了一些讨论。 XSRF 缓解策略在here (Scott Hanselman)here (Dino Esposito) 讨论

【讨论】:

  • 很好的链接,谢谢。我希望找到一种专门使用 AntiForgeryToken 的方法,因为 MVC Web 服务的“消费者”在 WebForms 页面上(我不会在 MVC 中重写。)
【解决方案4】:

您可以在控制器中创建一个新的 HtmlHelper,然后从那里拉取反 xrsf:

var htmlHelper = new HtmlHelper(
    new ViewContext(
        ControllerContext, 
        new WebFormView("omg"), 
        new ViewDataDictionary(), 
        new TempDataDictionary()), 
        new ViewPage());

var xsrf = htmlHeler.AntiForgeryToken;

myObject.XsrfToken = xsrf;

return JsonResult(myObject);

【讨论】:

  • 我的理解是AntiForgeryToken设置了一个cookie,并且注入了一个隐藏的表单域,所以两者可以比较。这是如何实现的?
猜你喜欢
  • 2015-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多