当表单处于空闲状态时,ASP.NET MVC 操作参数未绑定

Posted

技术标签:

【中文标题】当表单处于空闲状态时,ASP.NET MVC 操作参数未绑定【英文标题】:ASP.NET MVC Action Parameters Not Binding when form sits idle 【发布时间】:2017-07-16 09:59:58 【问题描述】:

我的 mvc 5 Web 应用程序遇到一个奇怪的问题,我似乎无法弄清楚。该应用程序是我为我的组织构建的内部 Web 应用程序。它主要由使用域连接计算机的员工访问,但也将通过移动设备和平板电脑访问。我想通过 windows auth 和 AD 为前者提供自动登录体验(如果他们已经登录到域,则无需输入凭据)我还希望能够为所有其他用户提供自定义登录屏幕,而不是浏览器原生提示。为了实现这一点,我创建了一个单独的 Web 应用程序,它对 Windows 用户进行身份验证并将加密的 cookie 发送回具有用户角色的主应用程序。非基于 Windows 的浏览器在主应用程序中显示一个登录页面,该页面针对 AD 进行身份验证并检索用户的角色。对于每种类型的登录,角色都将转换为声明,并为用户创建联合令牌。 我的问题是,当用户通过重定向登录到 windows auth 应用程序时,会出现一个奇怪的问题。我提交的任何表单(无论是标准表单提交还是 AJAX 帖子)都必须在加载页面后一分钟内提交,否则发送到控制器操作的参数不会绑定(null)。如果用户通过自定义登录页面登录,则不存在此问题。

这是在 global.asax 中执行初始身份验证的代码:

Protected Sub Application_AuthenticateRequest()

    Dim user As System.Security.Principal.IPrincipal = HttpContext.Current.User


    If user Is Nothing Then


        'First check if an authentication cookie is has been generated from the windows login
        'authentication app
        Dim authCookie As HttpCookie = Request.Cookies(".ConnectAUTH")

        If Not authCookie Is Nothing Then

            ' Extract the roles from the cookie, and assign to our current principal, which is attached to the HttpContext.
            Dim ticket As FormsAuthenticationTicket = FormsAuthentication.Decrypt(authCookie.Value)


            Dim claims As New List(Of Claim)
            For Each role In ticket.UserData.Split(";"c)
                claims.Add(New Claim(ClaimTypes.Role, role))
            Next


            Dim claimIdent As New ClaimsIdentity(claims, "Custom")

            claimIdent.AddClaim(New Claim(ClaimTypes.WindowsAccountName, ticket.Name))
            claimIdent.AddClaim(New Claim(ClaimTypes.NameIdentifier, ticket.Name))
            Dim claimPrinc As New ClaimsPrincipal(claimIdent)
            Dim token = New SessionSecurityToken(claimPrinc)
            Dim sam = FederatedAuthentication.SessionAuthenticationModule
            sam.WriteSessionTokenToCookie(token)

            HttpContext.Current.User = New ClaimsPrincipal(claimIdent)


            Return

        Else    'User hasn't been authenticated

            Dim ConnectBaseURL = Request.Url.GetLeftPart(UriPartial.Authority) & "/"
            Dim mvcPath As String = Request.Url.ToString.Replace(ConnectBaseURL, "")

            'If user is requesting the login page then let them authenticate through there
            If mvcPath.ToUpper.Contains("ACCOUNT/LOGIN") Or mvcPath.ToUpper.Contains("ACCOUNT/LOGUSERIN") Or mvcPath.ToUpper.Contains("SIGNALR") Then Exit Sub

            'for brevity i will omit the code below:
            ' Basically it checks whether the browser is windows based if so then it redirects the user to
            ' a windows login authenticator which authenticates the user against Active Directory, builds and sends
            ' an encrypted cookie with a list of roles/groups that the user belongs to.  When this function detects that cookie
            ' it decrypts it, sets up a claims identity, add the user roles and creates a federated authentication token

            'If the the browser is not windows based then it redirects the user to a custom login page which is tied to an MVC
            'action that will also authenticate the user against active directory and and set up the claims identity ...


        End If
    End If


End Sub

如果用户被重定向到自定义登录页面,以下是验证用户身份的代码:

        <AllowAnonymous>
    <HttpPost>
    <ValidateAntiForgeryToken>
    <OutputCache(NoStore:=True, Duration:=0, Location:=OutputCacheLocation.None, VaryByParam:="None")>
    Public Function LogUserIn(model As User, returnURL As String) As ActionResult

        Try
            If ModelState.IsValid Then

                If Membership.ValidateUser(model.UserName, model.PassWord) Then

                    'Call helper function to get all user roles and convert them to claims
                    Dim userClaims As List(Of Claim) = New LDAPHelper().GetUserGroups(model.UserName)
                    userClaims.Add(New Claim(ClaimTypes.WindowsAccountName, model.UserName))
                    userClaims.Add(New Claim(ClaimTypes.NameIdentifier, model.UserName))
                    userClaims.Add(New Claim(ClaimTypes.Name, model.UserName))
                    Dim claimIdent As New ClaimsIdentity(userClaims, "Custom")

                    Dim claimPrinc As New ClaimsPrincipal(claimIdent)
                    Dim token = New SessionSecurityToken(claimPrinc)
                    Dim sam = FederatedAuthentication.SessionAuthenticationModule
                    sam.WriteSessionTokenToCookie(token)


                    If returnURL Is Nothing Then
                        Return Redirect("~/")
                    Else
                        Return Redirect(returnURL)
                    End If

                Else
                    ModelState.AddModelError("LoginFailure", "The username/password combination was invalid")

                End If



            End If

            Return Nothing

        Catch ex As Exception
            ModelState.AddModelError("LoginFailure", ex.Message)
            Return Nothing
        End Try

    End Function

我已尝试通过不从 Windows auth 应用程序发送表单 cookie 并在重定向回主应用程序后对声明和令牌创建进行硬编码,从而消除等式中的表单 cookie。每次 Application_AuthenticateRequest 被命中时,HttpContext.Current.User 对象都保持设置为有效的 claimPrincipal。我已经实现了一个自定义 AuthorizeAttribute,并且用户总是经过身份验证和授权。有趣的是,如果我在参数以 null 传递后立即再次点击表单上的提交按钮,它就可以工作。我在网上搜索了一个类似的问题 - 没有 - 我希望这里有人有一个想法。

【问题讨论】:

【参考方案1】:

我的回答可能完全不相关,但我只是想提供帮助。我们有类似的东西。当我们从 MVC4 中的 Web api 切换到 MVC5 时。我们有一个检查令牌的 AuthorizationAttribute。我们阅读了它,设置了主体,但是当我们点击控制器操作时,主体消失了(在 mVC5 中 - 在以前的版本中这有效)。简短的故事是它与 MVC5 的异步特性有关。我们改变了实现另一个属性的方式,该属性实现了 IAuthenticationFilter(这是 mVC5 期望设置主体的地方)。 这和你有什么关系?我认为你也有异步的问题,你需要弄清楚 MVC5 期望在哪个事件中设置主体(Application_AuthenticateRequest 可能不是这个地方)。其他任何东西都可能设置在错误的线程上,并且在您到达控制器时就消失了。

【讨论】:

感谢 Willy 的想法,但是当您使用会话身份验证令牌时,您不需要在每个请求上设置主体。一旦用户登录并通过身份验证,就会使用声明身份/主体为他们创建会话令牌,并保持设置状态,直到该令牌无效或他们注销。为了确保我检查了控制器操作并且用户身份仍然设置但发送到操作的参数为空。

以上是关于当表单处于空闲状态时,ASP.NET MVC 操作参数未绑定的主要内容,如果未能解决你的问题,请参考以下文章

当表单在 ASP.NET MVC 5 应用程序中有效时触发 jQuery 函数

ASP.NET MVC5 Bootstrap 3 模态表单未验证客户端并回发到页面

如何在 ASP.NET MVC 中使用多个表单元素

在 ASP.Net MVC 中的同一视图中分离两个表单

在ASP.NET MVC Razor模式里面提交表单数据时,都有一个type=submit的input按钮,

在 ASP.Net Web 表单/MVC 中动态加载和添加字段