问题
I need to generate and issue a token to clients based on username/password authentication. I've tried several approaches to solving this problem, but all of them have run into issues.
My first plan was to implement WS-Trust Issue on my WCF endpoint. The example I found which did this used:
[OperationContract(Action = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue",
ReplyAction = "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue")]
Message IssueToken(Message rstMessage);
However, the changes to WIF in 4.5 to integrate it into the .NET Framework proper have broken the rest of the sample code to convert Message into RequestSecurityToken. WSTrustRequestSerializer seems to do this, but it requires WSTrustSerializationContext, and there's little information on how to create or configure this context object.
I tried simply serializing out the JWT I want to use for my SecurityToken type into a string and returning it to the client, but it looks like deserializing it into a SecurityToken which WCF can use would require me to ship the JWTSecurityToken and Handler on the client, something I want to avoid. While the WS-Trust bindings seem to sidestep this somehow and produce a GenericXmlSecurityToken, I can't seem to find how to create one of these myself.
Any thoughts on either how to serialize/deserialize the RequestSecurityToken and RequestSecurityTokenResponse objects within WS-Trust, or how to serialize/deserialize the token outside of the WS-Trust framework? Or other ideas?
回答1:
What I did was this: I created my own version of a response message which had the bits I needed to create a GenericXmlSecurityToken. This is what's generally returned from a WSTrustChannel, so it seemed like the right thing to do. Thankfully, most of the parameters for a GenericXmlSecurityToken wrapping a JWT are null; I only needed the serialized token, serialized with WriteToken on the JWTSecurityTokenHandler in the service, and the validFrom and validTo values.
Client code:
XmlElement element = document.CreateElement("wsse", "BinarySecurityToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
element.SetAttribute("ValueType", "urn:ietf:params:oauth:token-type:jwt");
element.SetAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
UTF8Encoding encoding = new UTF8Encoding();
element.InnerText = Convert.ToBase64String(encoding.GetBytes(jwtToken));
GenericXmlSecurityToken token = new GenericXmlSecurityToken(
element,
null,
validFrom,
validTo,
null,
null,
null);
var binding = new WS2007FederationHttpBinding(WSFederationHttpSecurityMode.TransportWithMessageCredential);
binding.Security.Message.IssuedKeyType = SecurityKeyType.BearerKey;
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.IssuedTokenType = "urn:ietf:params:oauth:token-type:jwt";
var factory2 = new ChannelFactory<IService1>(binding, new EndpointAddress("https://localhost:44300/Service1.svc"));
factory2.Credentials.SupportInteractive = false;
factory2.Credentials.UseIdentityConfiguration = true;
var proxy = factory2.CreateChannelWithIssuedToken(token);
var info = proxy.DoWork();
Relevant bits of the web.config:
The binding:
<ws2007FederationHttpBinding>
<binding>
<security mode="TransportWithMessageCredential">
<message issuedKeyType="BearerKey" establishSecurityContext="false" issuedTokenType="urn:ietf:params:oauth:token-type:jwt"/>
</security>
</binding>
</ws2007FederationHttpBinding>
The identityModel section:
<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="--audienceUri--"/>
</audienceUris>
<securityTokenHandlers>
<add type="--namespace--.CustomJWTSecurityTokenHandler, --my dll--" />
<securityTokenHandlerConfiguration>
<certificateValidation certificateValidationMode="PeerTrust"/>
</securityTokenHandlerConfiguration>
</securityTokenHandlers>
<issuerNameRegistry>
<trustedIssuers>
<add name="--issuer--" thumbprint="--thumbprint--"/>
</trustedIssuers>
</issuerNameRegistry>
</identityConfiguration>
</system.identityModel>
And the CustomJWTSecurityTokenHandler from this question (only the validIssuer part was required for my scenario): How to configure MIcrosoft JWT with symmetric key?
I haven't seen the issuedTokenType attribute used elsewhere, but I found it was essential to getting my code to work. Without it, I got this error: "MessageSecurityException: Cannot find a token authenticator for the 'Microsoft.IdentityModel.Tokens.JWT.JWTSecurityToken' token type. Tokens of that type cannot be accepted according to current security settings."
This might be overkill as a solution, but I think it minimizes the amount of custom code and centralizes it in places I feel more comfortable with.
Thanks to both user2338856 and leastprivilege for getting me partway there!
回答2:
AFAIK, you need a WSFederationBinding for this. Out of the box this only supports Saml tokens. To have it support Jwt tokens, you need to add a securitytokenhandler that is capable of handling it in the WiF pipeline. Unfortunately, the experimental handler from Microsoft is not that configuration file friendly so you need to subclass it to allow you to specify the properties in config. Alternatively, you can use the ThinkTecture Jwt handler, which is also not extremely easy to set up in config. Setting all of this up will take you quite some time and I am not aware of any examples on the web that do this.
来源:https://stackoverflow.com/questions/16312907/delivering-a-jwt-securitytoken-to-a-wcf-client