在不使用WIFI的情况下在WCF服务调用中包含SAML 2.0令牌

时间:2014-01-22 20:10:42

标签: c# wcf .net-4.5 saml-2.0 adfs

我正在尝试设置受WCF保护的ADFS服务。我目前能够使用WIFThinktecture 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服务以进行身份​​验证。有各种来源(包括前面提到的文章)说明这应该是可能的,但我还没有找到解决方案。

任何帮助都将不胜感激。

2 个答案:

答案 0 :(得分:1)

您可以使用混合解决方案之一,将SAML与OAuth或其他授权技术结合使用。这对于网络钓鱼技术更安全。对于仅SAML方法,您可以参考以下链接:How to pass security tokenfrom one wcf service to another wcf service。据说你需要在webconfig上启用saveBootstrapTokens属性。

此链接也很有用:Availability of Bootstrap Tokens

答案 1 :(得分:0)

这可以在不使用WIF的情况下轻松完成。让我们完全避免使用WIF和.Net框架,并使用Java进行说明。首先使用模板方法调用安全令牌服务,就像您所做的那样。然后,您需要从响应中提取SAML,Base64对其进行编码并将其填充到后续请求的Autorization标头中,并将其填充到受保护的WCF服务中。如果您要编写不可否认代码,则可能还需要对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());