我尝试使用AD FS在WCF应用程序中授权Winforms客户端。
private void button_Login_Click(object sender, EventArgs e)
{
RequestSecurityTokenResponse rstr;
var genericToken = Token.GetToken(@"cpofeatures\testuser", "Abc_123", "https://app1.cpofeatures.com/Service1.svc", out rstr) as GenericXmlSecurityToken;
textBox_Log.Text += genericToken.Id;
// This one works fine.
TryManualAuth(genericToken);
// This one fails.
TryWcf(genericToken);
}
void TryWcf(GenericXmlSecurityToken token)
{
ChannelFactory<IService1> channelFactory = new ChannelFactory<IService1>("WS2007FederationHttpBinding_IService1", new EndpointAddress("http://app1.cpofeatures.com/Service1.svc"));
channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
var service = channelFactory.CreateChannelWithActAsToken(token);
service.GetData(123);
Log("Result from service: " + service.GetData(123));
}
private void TryManualAuth(GenericXmlSecurityToken genericToken)
{
const string certSubject = "s-pdc.cpofeatures.com";
//const string certSubject = "CN = ADFS Signing - s-pdc.cpofeatures.com";
var relyingPartyId = "https://app1.cpofeatures.com/Service1.svc";
if (genericToken != null)
{
//Setup the handlers needed to convert the generic token to a SAML Token
var tokenHandlers = new SecurityTokenHandlerCollection(new SecurityTokenHandler[] { new SamlSecurityTokenHandler() });
tokenHandlers.Configuration.AudienceRestriction = new AudienceRestriction();
tokenHandlers.Configuration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(relyingPartyId));
var trusted = new TrustedIssuerNameRegistry(certSubject);
tokenHandlers.Configuration.IssuerNameRegistry = trusted;
//convert the generic security token to a saml token
var samlToken = tokenHandlers.ReadToken(new XmlTextReader(new StringReader(genericToken.TokenXml.OuterXml)));
//convert the saml token to a claims principal
var identities = tokenHandlers.ValidateToken(samlToken);
foreach (var claimsIdentity in identities)
{
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
//Display token information
Log("Name : " + claimsPrincipal.Identity.Name);
Log("Auth Type : " + claimsPrincipal.Identity.AuthenticationType);
Log("Is Authed : " + claimsPrincipal.Identity.IsAuthenticated);
foreach (var c in claimsPrincipal.Claims)
Log(c.Type + " / " + c.Value);
}
}
}
//The token handler calls this to check the token is from a trusted issuer before converting it to a claims principal
//In this case I authenticate this by checking the certificate name used to sign the tokenpublic
class TrustedIssuerNameRegistry : IssuerNameRegistry
{
private string _certSubject;
public TrustedIssuerNameRegistry(string certSubject) { _certSubject = certSubject; }
public override string GetIssuerName(SecurityToken securityToken)
{
var x509Token = securityToken as X509SecurityToken; if (x509Token != null && x509Token.Certificate.SubjectName.Name != null && x509Token.Certificate.SubjectName.Name.Contains(_certSubject)) return x509Token.Certificate.SubjectName.Name;
throw new SecurityTokenException("Untrusted issuer.");
}
}
private void Log(string what)
{
textBox_Log.Text += what + Environment.NewLine;
}
以下是我在TryWcf方法中获得的异常消息:
************** Exception Text **************
System.ServiceModel.Security.SecurityNegotiationException: SOAP security negotiation with 'https://s-pdc.cpofeatures.com/adfs/services/trust/2005/certificatemixed' for target 'https://s-pdc.cpofeatures.com/adfs/services/trust/2005/certificatemixed' failed. See inner exception for more details. ---> System.InvalidOperationException: The client certificate is not provided. Specify a client certificate in ClientCredentials.
Server stack trace:
at System.ServiceModel.ClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement, Boolean disableInfoCard)
at System.ServiceModel.ClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
at System.ServiceModel.Security.SecurityProtocol.AddSupportingTokenProviders(SupportingTokenParameters supportingTokenParameters, Boolean isOptional, IList`1 providerSpecList)
at System.ServiceModel.Security.SecurityProtocol.OnOpen(TimeSpan timeout)
at System.ServiceModel.Security.WrapperSecurityCommunicationObject.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.SecurityChannelFactory`1.ClientSecurityChannel`1.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at System.ServiceModel.ICommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Security.IssuanceTokenProviderBase`1.DoNegotiation(TimeSpan timeout)
--- End of inner exception stack trace ---
Server stack trace:
at System.ServiceModel.Security.IssuanceTokenProviderBase`1.DoNegotiation(TimeSpan timeout)
at System.ServiceModel.Security.IssuanceTokenProviderBase`1.GetTokenCore(TimeSpan timeout)
at System.IdentityModel.Selectors.SecurityTokenProvider.GetToken(TimeSpan timeout)
at System.ServiceModel.Security.Tokens.IssuedSecurityTokenProvider.GetTokenCore(TimeSpan timeout)
at System.IdentityModel.Selectors.SecurityTokenProvider.GetToken(TimeSpan timeout)
at System.ServiceModel.Security.SecurityProtocol.TryGetSupportingTokens(SecurityProtocolFactory factory, EndpointAddress target, Uri via, Message message, TimeSpan timeout, Boolean isBlockingCall, IList`1& supportingTokens)
at System.ServiceModel.Security.SymmetricSecurityProtocol.TryGetTokenSynchronouslyForOutgoingSecurity(Message message, SecurityProtocolCorrelationState correlationState, Boolean isBlockingCall, TimeSpan timeout, SecurityToken& token, SecurityTokenParameters& tokenParameters, SecurityToken& prerequisiteWrappingToken, IList`1& supportingTokens, SecurityProtocolCorrelationState& newCorrelationState)
at System.ServiceModel.Security.SymmetricSecurityProtocol.SecureOutgoingMessageCore(Message& message, TimeSpan timeout, SecurityProtocolCorrelationState correlationState)
at System.ServiceModel.Security.MessageSecurityProtocol.SecureOutgoingMessage(Message& message, TimeSpan timeout, SecurityProtocolCorrelationState correlationState)
at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.DoOperation(SecuritySessionOperation operation, EndpointAddress target, Uri via, SecurityToken currentToken, TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.GetTokenCore(TimeSpan timeout)
at System.IdentityModel.Selectors.SecurityTokenProvider.GetToken(TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionClientSettings`1.ClientSecuritySessionChannel.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.CallOpenOnce.System.ServiceModel.Channels.ServiceChannel.ICallOnce.Call(ServiceChannel channel, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce(TimeSpan timeout, CallOnceManager cascade)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at WindowsFormsApplication1.ServiceReference1.IService1.GetData(Int32 value)
at WindowsFormsApplication1.Form1.TryWcf(GenericXmlSecurityToken token) in c:\sources\adfs\WcfService1\WindowsFormsApplication1\Form1.cs:line 50
at WindowsFormsApplication1.Form1.button_Login_Click(Object sender, EventArgs e) in c:\sources\adfs\WcfService1\WindowsFormsApplication1\Form1.cs:line 40
以下是用于从ADFS获取令牌的代码:
internal class Token
{
public static SecurityToken GetToken(string username, string password, string appliesTo, out RequestSecurityTokenResponse rsts)
{
WS2007HttpBinding binding = new WS2007HttpBinding();
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(binding, new EndpointAddress("https://s-pdc.cpofeatures.com/adfs/services/trust/13/usernamemixed"));
trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
trustChannelFactory.Credentials.UserName.UserName = username;
trustChannelFactory.Credentials.UserName.Password = password;
RequestSecurityToken requestToken = new RequestSecurityToken(RequestTypes.Issue) {
KeyType = KeyTypes.Bearer,
};
requestToken.AppliesTo = new EndpointReference(appliesTo);
WSTrustChannel tokenClient = (WSTrustChannel)trustChannelFactory.CreateChannel();
SecurityToken token = tokenClient.Issue(requestToken, out rsts);
return token;
}
}
WCF服务的Web.config:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
</configSections>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
<add key="ida:FederationMetadataLocation" value="https://s-pdc.cpofeatures.com/FederationMetadata/2007-06/FederationMetadata.xml" />
<add key="ida:ProviderSelection" value="productionSTS" />
</appSettings>
<location path="FederationMetadata">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials useIdentityConfiguration="true">
<!--Certificate added by Identity and Access Tool for Visual Studio.-->
<serviceCertificate findValue="CN=app1.cpofeatures.com" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName">
</serviceCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add scheme="http" binding="ws2007FederationHttpBinding" />
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<bindings>
<ws2007FederationHttpBinding>
<binding name="">
<security mode="Message">
<message>
<issuerMetadata address="https://s-pdc.cpofeatures.com/adfs/services/trust/mex" />
</message>
</security>
</binding>
</ws2007FederationHttpBinding>
</bindings>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
<!--
To browse web app root directory during debugging, set the value below to true.
Set to false before deployment to avoid disclosing web app folder information.
-->
<directoryBrowse enabled="true" />
</system.webServer>
<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="https://app1.cpofeatures.com/Service1.svc" />
<add value="http://app1.cpofeatures.com/Service1.svc" />
</audienceUris>
<issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
<authority name="http://s-pdc.cpofeatures.com/adfs/services/trust">
<keys>
<add thumbprint="9E9DDC6708052E36D13020A9A3238016EDD57B60" />
</keys>
<validIssuers>
<add name="http://s-pdc.cpofeatures.com/adfs/services/trust" />
</validIssuers>
</authority>
</issuerNameRegistry>
<!--certificationValidationMode set to "None" by the the Identity and Access Tool for Visual Studio. For development purposes.-->
<certificateValidation certificateValidationMode="None" />
</identityConfiguration>
</system.identityModel>
</configuration>
winforms的App.config很大,它是通过添加服务参考功能生成的,而且我必须添加以下内容:
<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="https://app1.cpofeatures.com/Service1.svc" />
<add value="http://app1.cpofeatures.com/Service1.svc" />
</audienceUris>
</identityConfiguration>
</system.identityModel>
</configuration>
因此,验证令牌的手动方式是有效的,并且我能够从LDAP中获取声明,包括用户名,电子邮件等,但WCF客户端做了不同的事情。
据我所知,WCF尝试使用证书进行身份验证: https://s-pdc.cpofeatures.com/adfs/services/trust/2005/certificatemixed
我需要配置哪个证书,或者是否可以将WCF配置为与我的自定义代码相同的方式工作? 据我了解,它只需要验证令牌。
我还缺少什么?
更新:
我也尝试过:
var service = channelFactory.CreateChannelWithIssuedToken(token);
获得例外:&#34;派生密钥令牌无法从秘密中获取密钥&#34;
Server stack trace:
at System.ServiceModel.Security.Tokens.DerivedKeySecurityToken.Initialize(String id, Int32 generation, Int32 offset, Int32 length, String label, Byte[] nonce, SecurityToken tokenToDerive, SecurityKeyIdentifierClause tokenToDeriveIdentifier, String derivationAlgorithm, Boolean initializeDerivedKey)
at System.ServiceModel.Security.SendSecurityHeader.SignWithSupportingTokens()
at System.ServiceModel.Security.SendSecurityHeader.CompleteSecurityApplication()
at System.ServiceModel.Security.SecurityAppliedMessage.OnWriteMessage(XmlDictionaryWriter writer)
at System.ServiceModel.Channels.BufferedMessageWriter.WriteMessage(Message message, BufferManager bufferManager, Int32 initialOffset, Int32 maxSizeQuota)
at System.ServiceModel.Channels.TextMessageEncoderFactory.TextMessageEncoder.WriteMessage(Message message, Int32 maxMessageSize, BufferManager bufferManager, Int32 messageOffset)
at System.ServiceModel.Channels.HttpOutput.SerializeBufferedMessage(Message message, Boolean shouldRecycleBuffer)
at System.ServiceModel.Channels.HttpOutput.Send(TimeSpan timeout)
at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.SendRequest(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.DoOperation(SecuritySessionOperation operation, EndpointAddress target, Uri via, SecurityToken currentToken, TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.GetTokenCore(TimeSpan timeout)
at System.IdentityModel.Selectors.SecurityTokenProvider.GetToken(TimeSpan timeout)
at System.ServiceModel.Security.SecuritySessionClientSettings`1.ClientSecuritySessionChannel.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.CallOpenOnce.System.ServiceModel.Channels.ServiceChannel.ICallOnce.Call(ServiceChannel channel, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce(TimeSpan timeout, CallOnceManager cascade)
at System.ServiceModel.Channels.ServiceChannel.EnsureOpened(TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
答案 0 :(得分:0)
您可以尝试以下方法,
void TryWcf(GenericXmlSecurityToken token)
{
...
var service = channelFactory.CreateChannelWithIssuedToken(token);
...
}
并将以下属性添加到您的WCF web.config
<security mode="TransportWithMessageCredential">
<message establishSecurityContext="true">