从 AntiForgeryToken() 获取原始值(不是 html)

Posted

技术标签:

【中文标题】从 AntiForgeryToken() 获取原始值(不是 html)【英文标题】:Get the raw values (not html) from AntiForgeryToken() 【发布时间】:2015-09-26 23:22:54 【问题描述】:

这个美丽的抽象让您可以将@html.AntiForgeryToken() 放在 cshtml 文件中,该文件神奇地扩展为类似的东西;

<input name="__RequestVerificationToken" type="hidden" value="JjMHm5KJQ/qJsyC4sgifQWWX/WmADmNvEgHZXXuB07bWoL84DrmQzE6k9irVyFSJ5VSYqeUIXgl4Dw4NHSotLwflGYTyECzLvrgzbtonxJ9m3GVPgUV7Z6s2Ih/klUB78GN7Fl4Gj7kxg62MEoGcZw175eVwTmkKJ0XrtEfD5KCVvYIMHNY8MT2l+qhltsGL87c9dII42AVoUUQ2gTvfPg==" />

在提供页面之前通过 mvc。但是,我的页面有一些 javascript 进行 ajax 调用,这些调用不包含令牌,即使它已添加到表单中。他们目前正在获得预期的[HttpAntiForgeryException]: A required anti-forgery token was not supplied or was invalid.,因为他们没有令牌。我知道我可以从 DOM 中解析出值,但我不应该这样做。还有其他访问/获取此值的方法吗?明确地说,我的意思是我想要一个方法的重载,它只将值作为字符串或某种具有名称和值的对象返回。

为了提供更多上下文,我的表单和相关的 JS 看起来有点像这样;

<form action="/settings" method="post"><input name="__RequestVerificationToken" type="hidden" value="JjMHm5KJQ/qJsyC4sgifQWWX/WmADmNvEgHZXXuB07bWoL84DrmQzE6k9irVyFSJ5VSYqeUIXgl4Dw4NHSotLwflGYTyECzLvrgzbtonxJ9m3GVPgUV7Z6s2Ih/klUB78GN7Fl4Gj7kxg62MEoGcZw175eVwTmkKJ0XrtEfD5KCVvYIMHNY8MT2l+qhltsGL87c9dII42AVoUUQ2gTvfPg==" />    <fieldset>
        <h3>User Settings</h3>
        <ul>
            <li>
            label for="password">Password</label>
                <a href="#" id="change_password" class="changePasswordButton">Edit</a>
                <div id="password_section" class="inlineedit">
                    <div>
                        <span for="existing_password">Current password</span> <input autocomplete="off" class="required" id="existing_password" name="existing_password" type="password" />
                    </div>
                    <div>
                        <span for="new_password">New password</span> <input autocomplete="off" class="required" id="new_password" name="new_password" type="password" />
                        <span id="password_strength" />
                    </div>
                    <div>
                        <span for="confirm_password">Confirm password</span> <input autocomplete="off" class="required" id="confirm_password" name="confirm_password" type="password" />
                    </div>
                    <div class="inlinesave">
                        <input type="button" value="Change" onclick="onPostChangePassword();"/>
                        <a href="#" id="cancel_password" class="cancel">Cancel</a>
                    </div>
                </div>
            </li>
    // a bunch more of these that call their own onPostChangeSetting method

onPostChangePassword() 然后进行一些输入验证;

 if(validPWD && validNewPWD && validConfirmPWD && current_pwd != new_pwd)
                        // Post the password change
                        var currentAjaxRequest = $.ajax(
                            type: "POST",
                            url: "/settings/preferences/changepassword",
                            cache: false,
                            data: password: $('#new_password').val(), current: $('#existing_password').val(),confirm: $('#confirm_password').val(),
                            success: password_success,
                            error: password_error,
                            dataType: "json"
                        );
                        return true;
                  

理想情况下(因为这是在 cshtml 文件中逐字记录的)会被这样修改;

data: password: $('#new_password').val(), current: $('#existing_password').val(),confirm: $('#confirm_password').val(),
__RequestVerificationToken:@Html.AntiForgeryValue() 

tl;dr 有没有办法在 AntiForgeyToken 变成一串 html 之前与它进行交互?

【问题讨论】:

好奇:如果您要“解析”XHR 的其他数据(例如password),为什么“您不必这样做”(对于令牌)?答案是否最终不得不(在 POST 请求中生成并发送它无论如何)? @EdSF by don't have to 我的意思是我应该能够将数据作为对象服务器端使用,所以我没有 JS 拦截 ajax 调用,从DOM,将其添加为标头,然后让执行继续。当页面被提供时,我应该能够像在 HTML 中一样将值嵌入到 ajax 调用中。可悲的是,您不能直接这样做,但这只是因为该库设计不佳。 仍然好奇 :) 基于此(以及下面的答案)似乎改变了事情的运作方式,我感到有点不安全:) 如果 我没看错,我可以重复使用该值(重播、模拟等)? @EdSF 它可以防止重播。您可以在我的 cookie 或 DOM 中重放令牌的唯一方法是,如果您已经暴露了一个跨站点脚本漏洞,以便您解析我的 DOM 或读取我的 cookie。浏览器都不允许这些事情。 【参考方案1】:

您可以使用这样的代码(例如 _Layout.cshtml)将 AntiForgery 标头添加到所有 Ajax POST 请求中,或者您可以针对特定请求对其进行调整。 (代码假定您使用的是 jQuery)

@functions
    private static string TokenHeaderValue()
    
        string cookieToken, formToken;
        AntiForgery.GetTokens(null, out cookieToken, out formToken);
        return cookieToken + ":" + formToken;
    


<script type="text/javascript">
    $(document).ajaxSend(function (event, jqxhr, settings) 
        if (settings.type == "POST")    
            jqxhr.setRequestHeader('@ValidateHttpAntiForgeryTokenAttribute.RequestVerificationTokenName',
                                   '@TokenHeaderValue()');
        
    );
</script>

在这些 Ajax 调用的服务器端,您需要调用 the overload of AntiForgery.Validate that takes the cookie and form token,您可以通过将此属性添加到通过 Ajax 调用的操作方法(显式地,或通过父控制器,或通过过滤器)来启用它

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
                AllowMultiple = false, Inherited = true)]
public sealed class ValidateHttpAntiForgeryTokenAttribute 
                                 : FilterAttribute, IAuthorizationFilter


    public const string RequestVerificationTokenName = "RequestVerificationToken";

    public void OnAuthorization(AuthorizationContext filterContext)
    
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        
            ValidateRequestHeader(filterContext.HttpContext.Request);
        
        else
        
            AntiForgery.Validate();
        
    

    private static void ValidateRequestHeader(HttpRequestBase request)
    
        string cookieToken = string.Empty;
        string formToken = string.Empty;

        var tokenValue = request.Headers[RequestVerificationTokenName];

        if (string.IsNullOrEmpty(tokenValue) == false)
        
            string[] tokens = tokenValue.Split(':');

            if (tokens.Length == 2)
            
                cookieToken = tokens[0].Trim();
                formToken = tokens[1].Trim();
            
        

        AntiForgery.Validate(cookieToken, formToken);
    

【讨论】:

我并没有完全使用它(不需要服务器端代码),但我最终向 HtmlHelper 添加了一个扩展方法,并像上面的示例一样使用它。【参考方案2】:

查看 ILSpy 中的 Html.AntiForgeryToken() 并复制出构建输入标签的代码并创建您自己的仅返回令牌字符串的扩展方法。

【讨论】:

【参考方案3】:

简短回答:没有本地方法可以做您想做的事情,但正如之前的回答所述,您可以创建一个 HTML 帮助器方法。

这家伙似乎有一个可行的解决方案:https://***.com/a/16057568/724222

【讨论】:

以上是关于从 AntiForgeryToken() 获取原始值(不是 html)的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS Web Api AntiForgeryToken CSRF

MVC 从 View 获取数据到 Controller

如何通过 AngularJs 发送 AntiForgeryToken?

Html.AntiForgeryToken() 仍然需要吗?

ajax中加上AntiForgeryToken防止CSRF攻击

如何在 ajax 中使用 AntiForgeryToken