在不使用 WIFI 的情况下在 WCF 服务调用中包含 SAML 2.0 令牌
Posted
技术标签:
【中文标题】在不使用 WIFI 的情况下在 WCF 服务调用中包含 SAML 2.0 令牌【英文标题】:Including SAML2.0 token in WCF service call without using WIF 【发布时间】:2014-02-13 02:28:19 【问题描述】:我正在尝试设置受ADFS
保护的WCF
服务。我目前能够请求令牌并使用 WIF
和 Thinktecture IdentityModel 4.5
使用以下代码将其与请求一起发送:
static SecurityToken GetToken()
var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
"https://fs2.server2012.local/adfs/services/trust/13/usernamemixed")
TrustVersion = TrustVersion.WSTrust13
;
if (factory.Credentials != null)
factory.Credentials.UserName.UserName = @"username";
factory.Credentials.UserName.Password = "password";
var rst = new RequestSecurityToken
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
AppliesTo = new EndpointReference(
"https://wcfservicecertificate/wcfservice/Service.svc/wstrust"),
;
var channel = factory.CreateChannel();
RequestSecurityTokenResponse rstr;
return channel.Issue(rst, out rstr);
有了这个我可以使用ChannelFactory.CreateChannelWithIssuedToken
调用WCF服务:
var factory = new ChannelFactory<IService>(binding,
new EndpointAddress("https://wcfservicecertificate/wcfservice/Service.svc/wstrust"));
if (factory.Credentials != null)
factory.Credentials.SupportInteractive = false;
factory.Credentials.UseIdentityConfiguration = true;
var proxy = factory.CreateChannelWithIssuedToken(GetToken());
var result= proxy.GetData(2);
这可以按预期工作,但只能在(移动)Windows 平台上使用。我也希望能够在 ios 和 android 上使用相同的原理。使用this article,我能够使用以下代码从 ADFS 请求安全令牌:
const string soapMessage =
@"<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope""
xmlns:a=""http://www.w3.org/2005/08/addressing""
xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"">
<s:Header>
<a:Action s:mustUnderstand=""1"">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
<a:To s:mustUnderstand=""1"">https://fs2.server2012.local/adfs/services/trust/13/UsernameMixed</a:To>
<o:Security s:mustUnderstand=""1"" xmlns:o=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"">
<o:UsernameToken u:Id=""uuid-6a13a244-dac6-42c1-84c5-cbb345b0c4c4-1"">
<o:Username>username</o:Username>
<o:Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"">password</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body>
<trust:RequestSecurityToken xmlns:trust=""http://docs.oasis-open.org/ws-sx/ws-trust/200512"">
<wsp:AppliesTo xmlns:wsp=""http://schemas.xmlsoap.org/ws/2004/09/policy"">
<a:EndpointReference>
<a:Address>https://wcfservicecertificate/wcfservice/Service.svc/wstrust</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</trust:KeyType>
<trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
<trust:TokenType>urn:oasis:names:tc:SAML:2.0:assertion</trust:TokenType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>";
var webClient = new WebClient();
webClient.Headers.Add("Content-Type", "application/soap+xml; charset=utf-8");
var result = webClient.UploadString(
address: "https://fs2.server2012.local/adfs/services/trust/13/UsernameMixed",
method: "POST",
data: soapMessage);
这会产生一个 SAML2.0 令牌,我想向我们的 WCF 服务发送请求以进行身份验证。有各种来源(包括前面提到的文章)表明这应该是可能的,但我还没有找到解决方案。
任何帮助将不胜感激。
【问题讨论】:
我不知道这个问题的答案,但我很快就要自己解决这些问题,所以把它当作一个学习练习。根据我的研究,似乎更准确的问题陈述是“WCF 服务期望令牌在消息中的什么位置?”。我还不能让谷歌就这个问题给我一个直接的答案。祝你好运! 只是一个想法,但如果您使用 WebClient 获取 SAML 令牌,我会假设您将使用 WebClient 或其他 http 客户端向 WCF 端点发出请求。如果是这样,您可以使用 Fiddler 之类的工具检查您的工作 http 请求(*** c# 代码),然后使用 WebClient 复制它。 我建议您考虑使用 OAuth 2 和 JWT 令牌而不是 WS-Trust 和 SAML。 【参考方案1】:您可以使用将 SAML 与 OAuth 或其他授权技术结合使用的混合解决方案之一。这对网络钓鱼技术更安全。对于仅 SAML 的方法,您可以参考以下链接:How to pass security tokenfrom one wcf service to another wcf service。据说需要在webconfig上开启saveBootstrapTokens
属性。
这个链接也很有用:Availability of Bootstrap Tokens
【讨论】:
【参考方案2】:无需使用 WIF 即可轻松完成。让我们完全避免 WIF 和 .Net 框架,并在 Java 中进行,以进行说明。首先像您所做的那样使用模板方法调用安全令牌服务。然后,您需要从响应中提取 SAML,对其进行 Base64 编码并将其填充到对受保护 WCF 服务的后续请求的 Autorization 标头中。如果您正在为不可否认性编码,您可能还需要对 ProofKey 执行相同的操作。此外,为了简洁起见,我只显示使用用户名/密码的身份验证,因为证书身份验证涉及更多工作 - 您必须对消息的一部分(SHA1)进行哈希处理,然后使用证书的私钥加密哈希,然后将其添加为 xml元素到原始消息等...
这里是java帮助代码:
import java.io.*;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.Instant;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
public class SecurityService
private String _username;
private String _password;
private String _stsUrl;
private String _samlAssertion;
private String _samlEncoded;
private String _binarySecret;
private String _workingDirectory;
private String _platformUrl;
private String _soapBody;
private Integer _responseCode;
private Integer _plaformResponseCode;
private String _response;
private String _platformResponse;
private String _xproofSignature;
private Map<String, String> _headerDictionary;
public void setUsername(String username)
this._username = username;
public void setPassword(String password)
this._password = password;
public void setStsUrl(String stsUrl)
this._stsUrl = stsUrl;
public String getStsUrl()
return _stsUrl;
public void setplatformUrl(String platformUrl)
this._platformUrl = platformUrl;
public String getSamlAssertion()
return _samlAssertion;
public String getSamlEncoded()
return _samlEncoded;
public String getSoapBody()
return _soapBody;
public Integer getResponseCode()
return _responseCode;
public Integer getPlatformResponseCode()
return _plaformResponseCode;
public String getResponse()
return _response;
public String getPlatformResponse()
return _platformResponse;
public String getXProofSignature()
return _xproofSignature;
public String getBinarySecret()
return _binarySecret;
public String gePlatFormUrl()
return _platformUrl;
public void setHeaderDictionary(Map<String, String> headerDictionary)
this._headerDictionary = headerDictionary;
public Map<String, String> getHeaderDictionary()
return _headerDictionary;
public SecurityService() throws Exception
public SecurityService(Boolean useConfig) throws Exception
if (useConfig)
this._workingDirectory = System.getProperty("user.dir") + "\\app.config";
this.getProperties();
public void sendAuthenticatedGet() throws Exception
URL obj = new URL(_platformUrl);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// optional default is GET
con.setRequestMethod("GET");
// Add request header
con.setRequestProperty("Authorization", "Saml " + _samlEncoded);
con.setRequestProperty("X-ProofSignature", _xproofSignature);
_plaformResponseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null)
response.append(inputLine);
in.close();
_platformResponse = response.toString();
public void sendAuthenticatedPost(String body) throws Exception
URL obj = new URL(_platformUrl);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
//add request header
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json");
// Add request header
con.setRequestProperty("Authorization", "Saml " + _samlEncoded);
con.setRequestProperty("X-ProofSignature", _xproofSignature);
// Add Azure Subscription Key using generic Add Headers method
if (_headerDictionary != null)
for (String key : _headerDictionary.keySet())
con.setRequestProperty(key, _headerDictionary.get(key));
_soapBody = body;
// Send post request
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
//wr.writeBytes(urlParameters);
wr.writeBytes(_soapBody);
wr.flush();
wr.close();
_responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null)
response.append(inputLine);
in.close();
_response = response.toString();
// HTTP POST request
public void sendPostToSts() throws Exception
URL obj = new URL(_stsUrl);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
//add request header
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/soap+xml");
String body = getTemplateCertificate();
_soapBody = (((body.replace("[Created]", Instant.now().toString())).replace("[Expires]", Instant.now()
.plusSeconds(300).toString())).replace("[username]", _username)).replace("[password]", _password).replace("[stsUrl]", _stsUrl);
// Send post request
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
//wr.writeBytes(urlParameters);
wr.writeBytes(_soapBody);
wr.flush();
wr.close();
_responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null)
response.append(inputLine);
in.close();
_response = response.toString();
// Get Binary Secret
// <trust:BinarySecret></trust:BinarySecret>
final Pattern patternBinarySecret = Pattern.compile("<trust:BinarySecret>(.+?)</trust:BinarySecret>");
final Matcher matcherBinarySecret = patternBinarySecret.matcher(response.toString());
matcherBinarySecret.find();
_binarySecret = matcherBinarySecret.group(1);
// Get the SAML Assertion
final Pattern patternEncryptedAssertion = Pattern.compile("<trust:RequestedSecurityToken>(.+?)</trust:RequestedSecurityToken>");
final Matcher matcherEncryptedAssertion = patternEncryptedAssertion.matcher(response.toString());
matcherEncryptedAssertion.find();
_samlAssertion = matcherEncryptedAssertion.group(1);
byte[] proofKeyBytes = _binarySecret.getBytes("UTF-8");
String encoded = Base64.getEncoder().encodeToString(proofKeyBytes);
byte[] decoded = Base64.getDecoder().decode(encoded);
// SAML Stuff - Works beautifully
byte[] samlBytes = _samlAssertion.getBytes("UTF-8");
_samlEncoded = Base64.getEncoder().encodeToString(samlBytes);
_xproofSignature = this.encode(_samlAssertion, _binarySecret);
private static String readFile( String file ) throws IOException
BufferedReader reader = new BufferedReader( new FileReader(file));
String line = null;
StringBuilder stringBuilder = new StringBuilder();
String ls = System.getProperty("line.separator");
try
while( ( line = reader.readLine() ) != null )
stringBuilder.append( line );
stringBuilder.append( ls );
return stringBuilder.toString();
finally
reader.close();
// Embedded WS-Trust template for username/password RST
private static String getTemplate ()
return "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://www.w3.org/2005/08/addressing\" xmlns:u= \"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"><s:Header><a:Action s:mustUnderstand= \"1\">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action><a:MessageID>urn:uuid:cfea5555-248c-46c3-9b4d- 54936b7f815c</a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand=\"1\">[stsUrl]</a:To><o:Security s:mustUnderstand=\"1\" xmlns:o=\"http://docs.oasis- open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><u:Timestamp u:Id=\"_0\"><u:Created>[Created] </u:Created><u:Expires>[Expires]</u:Expires></u:Timestamp><o:UsernameToken u:Id=\"uuid-e273c018-1da7-466e-8671-86f6bfe7ce3c- 17\"><o:Username>[username]</o:Username><o:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username- token-profile-1.0#PasswordText\">[password] </o:Password></o:UsernameToken></o:Security></s:Header><s:Body><trust:RequestSecurityToken xmlns:trust=\"http://docs.oasis- open.org/ws-sx/ws-trust/200512\"><wsp:AppliesTo xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy \"><wsa:EndpointReference xmlns:wsa=\"http://www.w3.org/2005/08/addressing \"><wsa:Address>https://mbplatform/</wsa:Address></wsa:EndpointReference></wsp:AppliesTo><trust:RequestType>http://docs.oasis- open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType><trust:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token- profile-1.1#SAMLV2.0</trust:TokenType></trust:RequestSecurityToken></s:Body></s:Envelope>";
private String encode(String key, String data) throws Exception
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
return Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(data.getBytes("UTF-8")));
private void getProperties() throws Exception
Properties prop = new Properties();
String fileName = _workingDirectory;
InputStream is = new FileInputStream(fileName);
prop.load(is);
_username = prop.getProperty("app.username");
_password = prop.getProperty("app.password");
_platformUrl = prop.getProperty("app.platformUrl");
_stsUrl = prop.getProperty("app.stsUrl");
这里是示例用法:
SecurityService mbss = new SecurityService(true);
mbss.sendPostToSts();
System.out.println("CONTACTING AZURE SECURITY TOKEN SERVICE");
System.out.println("\nSending 'POST' request to URL : " + mbss.getStsUrl());
System.out.println("\nPost parameters : \n" + mbss.getSoapBody());
System.out.println("\nResponse Code : " + mbss.getResponseCode());
System.out.println("\nHERE IS THE SAML RESPONSE\n");
System.out.println(mbss.getResponse());
System.out.println("\nHERE IS THE BINARY SECRET\n");
System.out.println(mbss.getBinarySecret());
System.out.println("\nHERE IS THE SAML ASSERTION\n");
System.out.println(mbss.getSamlAssertion());
System.out.println("\nHERE IS THE ENCODED SAML ASSERTION\n");
System.out.println(mbss.getSamlEncoded());
System.out.println("\nHERE IS THE X-PROOF SIGNATURE\n");
System.out.println(mbss.getXProofSignature());
System.out.println("\nNOW CONTACTING WCF SERVICES WITH SECURITY HEADER\n");
mbss.sendAuthenticatedGet();
System.out.println("\nSending 'GET' request to URL : " + mbss.gePlatFormUrl());
System.out.println("Response Code : " + mbss.getPlatformResponseCode());
System.out.println("\nHERE ARE THE RESULTS FOLKS...ENJOY\n");
System.out.println(mbss.getPlatformResponse());
【讨论】:
以上是关于在不使用 WIFI 的情况下在 WCF 服务调用中包含 SAML 2.0 令牌的主要内容,如果未能解决你的问题,请参考以下文章
在不提示的情况下在 Powershell 中获取当前用户的凭据对象
如何在不脱离控制台的情况下在 Windows 中的 Python 中执行 os.execv()?
如何在不为每条记录调用发送方法的情况下在 Kafka Avro 生产者中发送对象的 ArrayList? [复制]