13

我有一个表格,用户无需登录即可发布。但是,如果他的电子邮件被识别,则需要密码。密码表单通过 Ajax 验证,如果成功,则提交主表单。两种形式都需要有效的 AntiForgeryToken。

问题是,作为副产品的密码检查也会让用户登录(来自客户端的要求)。这会使令牌无效,并且无法发送主表单。

我已经尝试以编程方式生成一个新令牌,但我无法让它工作。

关于如何解决这个问题的任何想法?

最终解决方案

我发现这个问题有助于输入反思。然而,这也是为什么在正常情况下你会避免破解内部类型的主要原因,是这些类型在版本之间的程序集之间进行了很多处理。正如 Betty 建议的那样,使用 ILSpy 查找内容。

这是最终代码。

if (signIn)
    FormsAuth.SignIn(user.Email, false);


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

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


string text = HttpContext.Request.Form[fieldName];
object antiForgeryData = serializerType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, null, serializer, new object[] { text });

afdType.GetProperty("Username").SetValue(antiForgeryData, 
    signIn ? user.Email : string.Empty, 
    null);

string newToken = Convert.ToString(serializerType.InvokeMember(
    "Serialize",
    BindingFlags.InvokeMethod,
    null,
    serializer,
    new object[] { antiForgeryData }));

return Content(JsonConvert.SerializeObject(new
                                                {
                                                    success = true,
                                                    newAntiForgeryToken = newToken
                                                }), Constant.JsonContentType);

升级网页 2.0

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

        string fieldName = "__RequestVerificationToken";

        var serializerType = mvcAssembly.GetType("System.Web.Helpers.AntiXsrf.AntiForgeryTokenSerializer");
        var serializerCtor = serializerType.GetConstructor(new Type[0]);
        object serializer = serializerCtor.Invoke(new object[0]);


        string text = HttpContext.Request.Form[fieldName];
        string newToken = String.Empty;

        if (!String.IsNullOrEmpty(text))
        {
            object antiForgeryToken = serializerType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, null,
                                                                 serializer, new object[] { text });

            afdType.GetProperty("Username").SetValue(antiForgeryToken,
                                                     signIn ? user.Email : string.Empty,
                                                     null);

            newToken = Convert.ToString(serializerType.InvokeMember(
                "Serialize",
                BindingFlags.InvokeMethod,
                null,
                serializer,
                new[] { antiForgeryToken }));
        }
4

2 回答 2

4

当前用户存储在表单数据中的防伪令牌中,并在回发时与当前用户进行比较。

您应该能够以 Phil Haack 在这篇文章中所做的相同方式在回发时提取表单令牌。

然后使用 AntiForgeryDataSerializer 类对令牌进行反序列化,更新当前用户,再次对其进行序列化,并将其放回表单中,然后再进行检查。或者完全使用您自己的属性替换 validate 方法。

或者,您可以尝试使用密码 ajax 请求发回更新的令牌并更新表单,而不是在主表单回发上更新它。无论哪种方式,基本方法都是相同的,反序列化、更新用户、序列化、替换令牌。

string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
string text = context.Request.Form[antiForgeryTokenName];
AntiForgeryDataSerializer serializer = new AntiForgeryDataSerializer();

AntiForgeryData antiForgeryData = serializer.Deserialize(text); 
antiForgeryData.Username = AntiForgeryData.GetUsername(context.User);
string newToken = serializer.Serialize(antiForgeryData);    

AntiForgeryDataSerializer 和 AntiForgeryData 是内部类,因此您必须使用一些基本反射来调用它们的方法。

于 2012-02-04T02:53:21.483 回答
1

更新了最终答案以说明对 AntiForgeryTokenSerializer 构造函数的更改:

    const string serializerAssembly = "System.Web.Helpers.AntiXsrf.AntiForgeryTokenSerializer";
    const string cryptoAssembly = "System.Web.Helpers.AntiXsrf.MachineKey40CryptoSystem";
    const string token = "System.Web.Helpers.AntiXsrf.AntiForgeryToken";
    const string fieldName = "__RequestVerificationToken";

    Assembly mvcAssembly = typeof (AntiForgery).Assembly;
    Type afdType = mvcAssembly.GetType(token);

    Type serializerType = mvcAssembly.GetType(serializerAssembly);
    Type cryptoType = mvcAssembly.GetType(cryptoAssembly);
    var constructors = serializerType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    ConstructorInfo cryptoConstructor = cryptoType.GetConstructor(new Type[0]);
    var crypto = cryptoConstructor.Invoke(new object[0]);
    object serializer = constructors[0].Invoke(new object[] { crypto });

    string text = currentContext.Request.Form[fieldName];
    string newToken = String.Empty;

    if (!String.IsNullOrEmpty(text))
    {
        object antiForgeryToken = serializerType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, null,
                                                              serializer, new object[] {text});

        afdType.GetProperty("Username").SetValue(antiForgeryToken,
                                                 signIn ? user.Email : string.Empty,
                                                 null);

        newToken = Convert.ToString(serializerType.InvokeMember(
            "Serialize",
            BindingFlags.InvokeMethod,
            null,
            serializer,
            new[] {antiForgeryToken}));
    }

    return newToken;
于 2013-01-09T21:48:57.330 回答