Я нашел решение своей проблемы, однако оказалось, что оно оказалось намного хуже, чем я ожидал.
По сути, для выполнения проверки учетных данных как транспорта, так и сообщения необходимо определить пользовательскую привязку. (Я нашел информацию на этот счет здесь ).
Я обнаружил, что самый простой способ сделать это - продолжить выполнять настройку в XML, но во время выполнения скопировать и немного изменить привязку netTcp из конфигурации XML. Существует буквально один переключатель, который нужно включить. Вот код на стороне обслуживания и на стороне клиента:
Сервисная сторона
ServiceHost businessHost = new ServiceHost(typeof(DHTestBusinessService));
ServiceEndpoint endpoint = businessHost.Description.Endpoints[0];
BindingElementCollection bindingElements = endpoint.Binding.CreateBindingElements();
SslStreamSecurityBindingElement sslElement = bindingElements.Find<SslStreamSecurityBindingElement>();
sslElement.RequireClientCertificate = true; //Turn on client certificate validation
CustomBinding newBinding = new CustomBinding(bindingElements);
NetTcpBinding oldBinding = (NetTcpBinding)endpoint.Binding;
newBinding.Namespace = oldBinding.Namespace;
endpoint.Binding = newBinding;
Клиентская сторона
DHTestBusinessServiceClient client = new DHTestBusinessServiceClient();
ServiceEndpoint endpoint = client.Endpoint;
BindingElementCollection bindingElements = endpoint.Binding.CreateBindingElements();
SslStreamSecurityBindingElement sslElement = bindingElements.Find<SslStreamSecurityBindingElement>();
sslElement.RequireClientCertificate = true; //Turn on client certificate validation
CustomBinding newBinding = new CustomBinding(bindingElements);
NetTcpBinding oldBinding = (NetTcpBinding)endpoint.Binding;
newBinding.Namespace = oldBinding.Namespace;
endpoint.Binding = newBinding;
Вы бы подумали, что так оно и будет, но вы ошибаетесь! :) Вот где он становится лишним. Я приписывал свои конкретные методы обслуживания с PrincipalPermission, чтобы ограничить доступ на основе ролей пользователя службы следующим образом:
[PrincipalPermission(SecurityAction.Demand, Role = "StandardUser")]
Это начало сбой, как только я применил вышеуказанные изменения. Причина была в том, что
OperationContext.Current.ServiceSecurityContext.PrimaryIdentity
заканчивал тем, что был неизвестным, без имени пользователя, без аутентификации IIdentity. Это было связано с тем, что на самом деле существует две удостоверения, представляющие пользователя: одна для сертификата X509, используемая для проверки подлинности по протоколу Transport, и одна для учетных данных имени пользователя и пароля, используемых для проверки подлинности на уровне сообщений. Когда я пересмотрел двоичные файлы WCF, чтобы понять, почему он не дал мне мой PrimaryIdentity, я обнаружил, что у него есть явная строка кода, которая заставляет его возвращать этот пустой IIdentity, если он находит более одного IIdentity. Я предполагаю, что это потому, что у него нет никакого способа выяснить, какой из них является первичным одним.
Это означает, что использование атрибута PrincipalPermission находится вне окна. Вместо этого я написал метод, имитирующий его функциональность, который может иметь дело с несколькими IIdentities:
private void AssertPermissions(IEnumerable<string> rolesDemanded)
{
IList<IIdentity> identities = OperationContext.Current.ServiceSecurityContext.AuthorizationContext.Properties["Identities"] as IList<IIdentity>;
if (identities == null)
throw new SecurityException("Unauthenticated access. No identities provided.");
foreach (IIdentity identity in identities)
{
if (identity.IsAuthenticated == false)
throw new SecurityException("Unauthenticated identity: " + identity.Name);
}
IIdentity usernameIdentity = identities.Where(id => id.GetType().Equals(typeof(GenericIdentity))).SingleOrDefault();
string[] userRoles = Roles.GetRolesForUser(usernameIdentity.Name);
foreach (string demandedRole in rolesDemanded)
{
if (userRoles.Contains(demandedRole) == false)
throw new SecurityException("Access denied: authorisation failure.");
}
}
Это не красиво (особенно то, как я определяю имя пользователя / пароль IIdentity), но оно работает! Теперь, в верхней части моих методов обслуживания, мне нужно назвать его так:
AssertPermissions(new [] {"StandardUser"});