ASP.NET MVC中的ServiceStack.NET Windows身份验证(NTLM)

时间:2013-03-06 09:37:10

标签: c# asp.net-mvc-4 servicestack windows-authentication

如何在基于ASP.NET MVC4的ServiceStack项目中实现Windows身份验证?

我开始使用AppHost中添加的全局请求过滤器:

private void ConfigureAuth(Funq.Container container)
{
    this.RequestFilters.Add((httpReq, httpResp, requestDto) =>
    {
        var user = HttpContext.Current.User.Identity;
        if (!user.IsAuthenticated ||
            !user.Name.Contains(_myTestUser)) //todo: check username here in database (custom logic) if it has access to the application
            httpResp.ReturnAuthRequired();
    });
}

这将打开一个登录对话框,如果输入正确(用户名存在且输入了有效密码,并且myTestUser设置为此),则会产生成功的响应。 如果有任何错误,将再次显示登录对话框。 - 这对我来说听起来不错。 但在第二个登录窗口中重新输入正确的用户后,它将停止工作。该对话框再次打开,如果它再次不正确。过滤器函数内没有遇到断点。

知道可能导致这种情况的原因吗?

这就是我在web.config中添加的内容:

<authentication mode="Windows"/>
<authorization>
  <deny users="?" /> <!--only allow authenticated users-->
</authorization>

我想完全锁定网站并仅使用其特定权限(角色)启用对数据库中指定Windows用户的访问。我需要实现自定义逻辑来访问“用户和角色列表”。 也许在MVC4 / ASP.NET中有另一种方法可以做到这一点?

4 个答案:

答案 0 :(得分:12)

Windows Intranet的ServiceStack自定义身份验证

我整天都在反对这一点,并提出以下建议。

首先是用例:

您使用的是Windows身份验证的企业内部网。您在web.config中设置了身份验证模式=“Windows”,就是这样!

你的策略是:

  1. 您不知道该用户是谁,因为他们不在您的用户表或ActiveDirectory组或其他任何内容中。在这种情况下,您将赋予它们“guest”的角色并相应地修剪UI。也许给他们一个电子邮件链接来请求访问。

  2. 您的用户列表中有该用户,但尚未为其分配角色。因此,给他们扮演“用户”的角色,并如上所述修剪UI。也许他们可以看到他们的东西,但没有别的。

  3. 用户在您的列表中并已被分配了一个角色。最初,您将通过手动更新数据库中的UserAuth表来分配角色。最终,您将获得一项服务,为授权用户提供此服务。

  4. 让我们来看看代码。

    服务器端

    在ServiceStack Service层,我们根据https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization

    创建自定义凭据授权提供程序
          public class CustomCredentialsAuthProvider : CredentialsAuthProvider
            {
                public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
                {
                    //NOTE: We always authenticate because we are always a Windows user! 
                    // Yeah, it's an intranet  
                    return true;
                }
    
                public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
                {
    
                    // Here is why we set windows authentication in web.config
                    var userName = HttpContext.Current.User.Identity.Name;
    
                    //  Strip off the domain
                    userName = userName.Split('\\')[1].ToLower();
    
                    // Now we call our custom method to figure out what to do with this user
                    var userAuth = SetUserAuth(userName);
    
                    // Patch up our session with what we decided
                    session.UserName = userName;
                    session.Roles = userAuth.Roles;            
    
                    // And save the session so that it will be cached by ServiceStack 
                    authService.SaveSession(session, SessionExpiry);
                }
    
            }
    

    这是我们的自定义方法:

         private UserAuth SetUserAuth(string userName)
                {
                    // NOTE: We need a link to the database table containing our user details
                    string connStr = ConfigurationManager.ConnectionStrings["YOURCONNSTRNAME"].ConnectionString;
                    var connectionFactory = new OrmLiteConnectionFactory(connStr, SqlServerDialect.Provider);
    
                    // Create an Auth Repository
                    var userRep = new OrmLiteAuthRepository(connectionFactory);
    
                    // Password not required. 
                    const string password = "NotRequired";
    
                    // Do we already have the user? IE In our Auth Repository
                    UserAuth userAuth = userRep.GetUserAuthByUserName(userName);
    
                    if (userAuth == null ){ //then we don't have them}
    
                    // If we don't then give them the role of guest
                    userAuth.Roles.Clear();
                    userAuth.Roles.Add("guest")
    
                    // NOTE: we are only allowing a single role here               
    
                    // If we do then give them the role of user
                    // If they are one of our team then our administrator have already given them a role via the setRoles removeRoles api in ServiceStack
                   ...
    
                    // Now we re-authenticate out user
                    // NB We need userAuthEx to avoid clobbering our userAuth with the out param
                    // Don't you just hate out params?
    
                    // And we re-authenticate our reconstructed user
                    UserAuth userAuthEx;
                    var isAuth = userRep.TryAuthenticate(userName, password, out userAuthEx);
                    return userAuth;
                }
    

    在appHost Configure中,在函数末尾添加以下ResponseFilters

        ResponseFilters.Add((request, response, arg3) => response.AddHeader("X-Role",request.GetSession(false).Roles[0]));
        ResponseFilters.Add((request, response, arg3) => response.AddHeader("X-AccountName", request.GetSession(false).UserName));
    

    这会向客户端发送一些额外的标题,以便我们可以根据用户的角色修剪UI。

    客户端

    在客户端,当我们向服务器发出第一个请求时,我们按照自定义身份验证的要求POST一个UserName和Password。两者都设置为“NotRequired”,因为我们将通过HttpContext.Current.User.Identity.Name知道用户在服务器端的用户。

    以下使用AngularJS进行AJAX通信。

        app.run(function($templateCache, $http, $rootScope) {
    
            // Authenticate and get X-Role and X-AccountName from the response headers and put it in $rootScope.role
    
            // RemeberMe=true means that the session will be cached 
            var data={"UserName" : "NotRequired", "Password" : "NotRequired", "RememberMe": true };
    
            $http({ method : 'POST', url : '/json/reply/Auth', data : data }).
                success(function (data, status, headers, config) {
                // We stash this in $rootScope for later use!
                    $rootScope.role = headers('X-Role');
                    $rootScope.accountName = headers('X-AccountName');
                    console.log($rootScope.role);
                    console.log($rootScope.role);
                }).
                error(function (data, status, headers, config) {
                    // NB we should never get here because we always authenticate
                    toastr.error('Not Authenticated\n' + status, 'Error');
                });
        };
    

答案 1 :(得分:3)

从版本4.0.21开始,可能值得注意。已经实现了一个Windows身份验证提供程序,如下所示:https://github.com/ServiceStack/ServiceStack/blob/master/release-notes.md#windows-auth-provider-for-aspnet

答案 2 :(得分:2)

更新查看下面的@BiffBaffBoff注释。看起来像Windows Auth已经推出。

我实现了一个非常简单的NTLM Auth提供程序。如果我有时间,我会在插件中包装它并在GitHub上发布。但就目前而言:

web.config - 这会阻止匿名用户进行连接(如问题所述):

<system.web>
  <authentication mode="Windows" />
  <authorization>
    <deny users="?" />
  </authorization>
...
</system.web>

CredentialsAuthProvider的简单包装 - 其他一些auth逻辑需要Credentials或Digest,这是最容易使用的基础:

public class NTLMAuthProvider : CredentialsAuthProvider
{
    public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
    {
        return !string.IsNullOrWhiteSpace(session.UserName);
    }
}

预先请求过滤器,以查看IIS提供的身份:

        this.PreRequestFilters.Add((req, resp) =>
        {
            IAuthSession session = req.GetSession();
            if (session.UserName == null)
            {
                session.UserName = ((HttpRequestWrapper)req.OriginalRequest).LogonUserIdentity.Name;

                // Add permissions & roles here - IUserAuthRepository, ICacheClient, etc.

                req.SaveSession(session);
            }
        });

答案 3 :(得分:1)

您是否在配置文件的system.web元素中启用了模拟?

<identity impersonate="true"/>

如果有人试图访问受限制的资源,那么可能会导致第二次失败。

您提到过要实现自定义逻辑以确定哪些用户可以访问系统。我认为

<allow roles="DomainName\WindowsGroup" />
<deny users="*" />

还不够。如果它很好,但除此之外,您还可以实施可能对您有所帮助的custom role provider。这需要使用Forms身份验证而不是Windows,但这并不一定意味着您无法使用Windows凭据对用户进行身份验证 - 这只意味着您必须自己做一些繁重的工作。