Службы клиентских приложений

70

Visual Studio упрощает использование служб аутентификации, которые ранее создавались для веб-приложений ASP.NET. Благодаря этой возможности, один и тот же механизм аутентификации может применяться для Windows- и веб-приложений.

Обеспечивает эту возможность модель поставщиков, которая главным образом основана на классах Membership (поставщик членства) и Roles (поставщик ролей) из пространства имен System.Web.Security. С помощью класса Membership можно проверять, создавать, удалять и находить пользователей, а также изменять их пароли и делать многие другие вещи.

Класс Roles позволяет добавлять и удалять роли, а также извлекать и изменять роли для пользователей. То, где хранятся данные о ролях и пользователях, зависит от поставщика.

Поставщик ActiveDirectoryMembershipProvider, например, предусматривает применение для получения доступа к пользователям и ролям базы данных Active Directory, а поставщик SqlMembershipProvider — базы данных SQL Server. В .NET 4 для служб клиентских приложений предлагаются еще два поставщика: ClientFormsAuthenticationMembershipProvider и ClientWindowsAuthenticationMembershipProvider.

Службы клиентских приложений применяются вместе с механизмом аутентификации с помощью форм (Forms Authentication). Для этого сначала понадобится запустить сервер приложений, а затем использовать службу из Windows Forms или Windows Presentation Foundation (WPF).

Службы приложений

Для использования служб клиентских приложений можно создать проект ASP.NET Web Service (Веб-служба ASP.NET), в котором предлагаются различные службы приложений.

Для этого проекта должен быть реализован поставщик членства. Можно воспользоваться каким-то существующим поставщиком либо создать новый специальный. В рассматриваемом примере кода определяется новый класс поставщика SampleMembershipProvider, унаследованный от базового класса MembershipProvider.

Определение класса MembershipProvider содержится в пространстве имен System.Web.Security внутри сборки System.Web.ApplicationServices. Все абстрактные методы из базового класса должны быть обязательно переопределены. Для входа в систему реализовать необходимо только метод ValidateUser. Все остальные методы могут генерировать исключение NotSupportedException, подобно тому, как показано на примере свойства ApplicationName. Для хранения имен пользователей и их паролей в примере кода используется коллекция Dictionary<string, string>. Разумеется, реализацию можно изменить, например, извлекать имена пользователей и пароли из базы данных:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web.Security;

namespace MySecurity
{
    public class SampleMembershipProvider : MembershipProvider
    {
        private Dictionary<string, string> users = new Dictionary<string, string>();
        internal static string ManagerUserName = "Manager".ToLowerInvariant();
        internal static string EmployeeUserName = "Employee".ToLowerInvariant();


        public override void Initialize(string name, NameValueCollection config)
        {
            users.Add(ManagerUserName, "secret@Pa$$w0rd");
            users.Add(EmployeeUserName, "s0me@Secret");

            base.Initialize(name, config);
        }

        public override string ApplicationName
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }
        
        // Переопределение абстрактных членов класса Membership
        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            throw new NotImplementedException();
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }

        public override bool EnablePasswordReset
        {
            get { throw new NotImplementedException(); }
        }

        public override bool EnablePasswordRetrieval
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override string GetUserNameByEmail(string email)
        {
            throw new NotImplementedException();
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredPasswordLength
        {
            get { throw new NotImplementedException(); }
        }

        public override int PasswordAttemptWindow
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { throw new NotImplementedException(); }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresUniqueEmail
        {
            get { throw new NotImplementedException(); }
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            throw new NotImplementedException();
        }


        public override bool ValidateUser(string username, string password)
        {
            if (users.ContainsKey(username.ToLowerInvariant()))
            {
                return password.Equals(users[username.ToLowerInvariant()]);
            }
            return false;
        }
    }
}

Для использования ролей должен быть реализован поставщик ролей. В рассматриваемом здесь примере для этого создается класс SampleRoleProvider, унаследованный от базового класса RoleProvider, с реализацией методов GetRolesForUser() и IsUserlnRole():

using System;
using System.Web.Security;

namespace MySecurity
{
    public class SampleRoleProvider : RoleProvider
    {
        internal static string ManagerRoleName = "Manager".ToLowerInvariant();
        internal static string EmployeeRoleName = "Employee".ToLowerInvariant();

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(name, config);
        }

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            throw new NotImplementedException();
        }

        public override string ApplicationName
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override void CreateRole(string roleName)
        {
            throw new NotImplementedException();
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            throw new NotImplementedException();
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            throw new NotImplementedException();
        }

        public override string[] GetAllRoles()
        {
            throw new NotImplementedException();
        }

        public override string[] GetRolesForUser(string username)
        {
            if (string.Compare(username, SampleMembershipProvider.ManagerUserName, true) == 0)
            {
                return new string[] { ManagerRoleName };
            }
            else if (string.Compare(username, SampleMembershipProvider.EmployeeUserName, true) == 0)
            {
                return new string[] { EmployeeRoleName };
            }
            else
            {
                return new string[0];
            }

        }

        public override string[] GetUsersInRole(string roleName)
        {
            throw new NotImplementedException();
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            string[] roles = GetRolesForUser(username);
            foreach (var role in roles)
            {
                if (string.Compare(role, roleName, true) == 0)
                {
                    return true;
                }
            }
            return false;

        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            throw new NotImplementedException();
        }

        public override bool RoleExists(string roleName)
        {
            throw new NotImplementedException();
        }
    }
}

Службы аутентификации должны конфигурироваться в файле Web.config. В производственной системе ради безопасности в этом файле следует также сконфигурировать и шифрование SSL для сервера, на котором размещаются эти службы:

<?xml version="1.0"?>
<configuration>

  <system.web.extensions>
    <scripting>
      <webServices>
        <authenticationService enabled="true" requireSSL="false" />
        <roleService enabled="true" />
      </webServices>
    </scripting>
  </system.web.extensions>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <membership defaultProvider="SampleMembershipProvider">
      <providers>
        <add name="SampleMembershipProvider" type="MySecurity.SampleMembershipProvider"/>
      </providers>
    </membership>
    <roleManager enabled="true" defaultProvider="SampleRoleProvider">
      <providers>
        <add name="SampleRoleProvider"
            type="MySecurity.SampleRoleProvider"/>
      </providers>
    </roleManager>
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

</configuration>

Клиентское приложение

При построении клиентского приложения применяется WPF. В Visual Studio предлагается специальный проект под названием Services (Службы), который позволяет использовать службы клиентских приложений. Для примера здесь в качестве механизма аутентификации можно указать службу Forms Authentication (Аутентификация с помощью форм), а в качестве места размещения этой службы и службы ролей — определенный ранее адрес http://localhost:55555/AppServices.

Все, что требуется сделать в конфигурации этого проекта — это добавить ссылки на сборки System.Web и System.Web.Extensions и изменить конфигурационный файл приложения так, чтобы он указывал на поставщиков членства и ролей, использующих классы ClientFormsAuthenticationMembershipProvider и ClientRoleProvider, а также на адрес используемой этими поставщиками веб-службы.

<?xml version="1.0"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
  </startup>
  <system.web>
    <membership defaultProvider="ClientAuthenticationMembershipProvider">
      <providers>
        <add name="ClientAuthenticationMembershipProvider" 
           type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, 
              Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
           serviceUri="http://localhost:55555/AppServices/Authentication_JSON_AppService.axd" />
      </providers>
    </membership>
    <roleManager defaultProvider="ClientRoleProvider" enabled="true">
      <providers>
        <add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, 
           System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
           serviceUri="http://localhost:55555/AppServices/Role_JSON_AppService.axd" cacheTimeout="86400" />
      </providers>
    </roleManager>
  </system.web>
  <appSettings>
    <add key="ClientSettingsProvider.ServiceUri" value="" />
  </appSettings>
</configuration>

В Windows-приложении используются элементы управления Label, TextBox, PasswordBox и Button. Элемент управления Label с содержимым User Validated (Пользователь прошел проверку) появляется только в случае успешного прохождения процедуры регистрации:

Пример аутентификации пользователя
Пройди тесты
Лучший чат для C# программистов