Службы клиентских приложений
70C# и .NET --- Основы .NET --- Службы клиентских приложений
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 (Пользователь прошел проверку) появляется только в случае успешного прохождения процедуры регистрации: