OpenSAML


一、背景知識:

      SAML即安全斷言標記語言,英文全稱是Security Assertion Markup Language。它是一個基於XML的標准,用於在不同的安全域(security domain)之間交換認證和授權數據。在SAML標准定義了身份提供者(identity provider)和服務提供者(service provider),這兩者構成了前面所說的不同的安全域。 SAML是OASIS組織安全服務技術委員會(Security Services Technical Committee)的產品。

    SAML(Security Assertion Markup Language)是一個XML框架,也就是一組協議,可以用來傳輸安全聲明。比如,兩台遠程機器之間要通訊,為了保證安全,我們可以采用加密等措施,也可以采用SAML來傳輸,傳輸的數據以XML形式,符合SAML規范,這樣我們就可以不要求兩台機器采用什么樣的系統,只要求能理解SAML規范即可,顯然比傳統的方式更好。SAML 規范是一組Schema 定義。

可以這么說,在Web Service 領域,schema就是規范,在Java領域,API就是規范。

  SAML 作用

SAML 主要包括三個方面:

1.認證申明。表明用戶是否已經認證,通常用於單點登錄。

2.屬性申明。表明 某個Subject 的屬性。

3.授權申明。表明 某個資源的權限。

  SAML框架

SAML就是客戶向服務器發送SAML 請求,然后服務器返回SAML響應。數據的傳輸以符合SAML規范的XML格式表示。

SAML 可以建立在SOAP上傳輸,也可以建立在其他協議上傳輸。

因為SAML的規范由幾個部分構成:SAML Assertion,SAML Prototol,SAML binding等

  安全 
由於SAML在兩個擁有共享用戶的站點間建立了信任關系,所以安全性是需考慮的一個非常重要的因素。SAML中的安全弱點可能危及用戶在目標站點的個人信息。SAML依靠一批制定完善的安全標准,包括SSL和X.509,來保護SAML源站點和目標站點之間通信的安全。源站點和目標站點之間的所有通信都經過了加密。為確保參與SAML交互的雙方站點都能驗證對方的身份,還使用了證書。

   應用

   目前SAML已經在很多商業/開源產品得到應用推廣,主要有:

IBM Tivoli Access Manager 
Weblogic 
Oblix NetPoint 
SunONE Identity Server 
Baltimore, SelectAccess 
Entegrity Solutions AssureAccess 
Internet2 OpenSAML 
Yale CAS 3 
Netegrity SiteMinder 
Sigaba Secure Messaging Solutions 
RSA Security ClearTrust 
VeriSign Trust Integration Toolkit 
Entrust GetAccess 7

 

二、基於 SAML的SSO

下面簡單介紹使用基於SAML的SSO登錄到WebApp1的過程(下圖源自SAML 的 Google Apps SSO,筆者偷懶,簡單做了修改)

saml_workflow_vertical2

 

此圖片說明了以下步驟。

  1. 用戶嘗試訪問WebApp1。
  2. WebApp1 生成一個 SAML 身份驗證請求。SAML 請求將進行編碼並嵌入到SSO 服務的網址中。包含用戶嘗試訪問的 WebApp1 應用程序的編碼網址的 RelayState 參數也會嵌入到 SSO 網址中。該 RelayState 參數作為不透明標識符,將直接傳回該標識符而不進行任何修改或檢查。
  3. WebApp1將重定向發送到用戶的瀏覽器。重定向網址包含應向SSO 服務提交的編碼 SAML 身份驗證請求。
  4. SSO(統一認證中心或叫Identity Provider)解碼 SAML 請求,並提取 WebApp1的 ACS(聲明客戶服務)網址以及用戶的目標網址(RelayState 參數)。然后,統一認證中心對用戶進行身份驗證。統一認證中心可能會要求提供有效登錄憑據或檢查有效會話 Cookie 以驗證用戶身份。
  5. 統一認證中心生成一個 SAML 響應,其中包含經過驗證的用戶的用戶名。按照 SAML 2.0 規范,此響應將使用統一認證中心的 DSA/RSA 公鑰和私鑰進行數字簽名。
  6. 統一認證中心對 SAML 響應和 RelayState 參數進行編碼,並將該信息返回到用戶的瀏覽器。統一認證中心提供了一種機制,以便瀏覽器可以將該信息轉發到 WebApp1 ACS。
  7. WebApp1使用統一認證中心的公鑰驗證 SAML 響應。如果成功驗證該響應,ACS 則會將用戶重定向到目標網址。
  8. 用戶將重定向到目標網址並登錄到 WebApp1。

生成報文示例代碼:

package test;

import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.xml.*;
import org.opensaml.common.SAMLVersion;
import org.joda.time.DateTime;
import org.opensaml.saml2.core.*;
import org.opensaml.saml2.core.impl.*;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.util.XMLHelper;
import org.w3c.dom.Element;

import java.io.*;
import java.math.BigInteger;
import java.security.SecureRandom;

public class OpenSaml {
	static {
		try {
			DefaultBootstrap.bootstrap();
		} catch (ConfigurationException e) {
			e.printStackTrace();
		}
	}

	public void generateRequestURL() throws Exception {
		  String consumerServiceUrl = "http://localhost:8080/consume.jsp";  // Set this for your app
		  String website = "https://www.efesco.com";  // Set this for your app

		  AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
		  AuthnRequest authnRequest = authRequestBuilder.buildObject(SAMLConstants.SAML20P_NS, "AuthnRequest", "samlp");
		  authnRequest.setIsPassive(false);
		  authnRequest.setIssueInstant(new DateTime());
		  authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
		  authnRequest.setAssertionConsumerServiceURL(consumerServiceUrl);
		  authnRequest.setID(new BigInteger(130, new SecureRandom()).toString(42));
		  authnRequest.setVersion(SAMLVersion.VERSION_20);

		  IssuerBuilder issuerBuilder = new IssuerBuilder();
		  Issuer issuer = issuerBuilder.buildObject(SAMLConstants.SAML20_NS, "Issuer", "samlp" );
		  issuer.setValue(website);
		  authnRequest.setIssuer(issuer);

		  NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
		  NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
		  nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient");
		  nameIdPolicy.setAllowCreate(true);
		  authnRequest.setNameIDPolicy(nameIdPolicy);

		  RequestedAuthnContextBuilder requestedAuthnContextBuilder = new RequestedAuthnContextBuilder();
		  RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
		  requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
		 
		  AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
		  AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject(SAMLConstants.SAML20_NS, "AuthnContextClassRef", "saml");
		  authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");

		  requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
		  authnRequest.setRequestedAuthnContext(requestedAuthnContext);

		  Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(authnRequest);
		  Element authDOM = marshaller.marshall(authnRequest);
		  StringWriter requestWriter = new StringWriter();
		  XMLHelper.writeNode(authDOM, requestWriter);
		  String messageXML = requestWriter.toString();
		  System.out.println(messageXML);

	}
	public static void main(String[] args) throws Exception {
		OpenSaml openSaml = new OpenSaml();
		openSaml.generateRequestURL();
	}
}

  

解析報文示例代碼:

import org.apache.commons.codec.binary.Base64;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.saml2.core.*;
import org.opensaml.saml2.core.impl.*;
import org.opensaml.xml.io.*;
import org.opensaml.xml.security.x509.BasicX509Credential;
import org.w3c.dom.*;
import org.opensaml.xml.*;
import org.apache.commons.codec.binary.Base64;

import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.security.spec.*;
import javax.xml.parsers.*;

public class SAMLResponseHandler {

  private static final String certificateS = "MIIENTCCAx2gAwIBAgIUDFWeXo2US+Je8Erqdc2IvREy8IswDQYJKoZIhvcNAQEF" +
"BQAwYjELMAkGA1UEBhMCVVMxGzAZBgNVBAoMEkNvbm5lY3RpZmllciwgSW5jLjEV" +
"MBMGA1UECwwMT25lTG9naW4gSWRQMR8wHQYDVQQDDBZPbmVMb2dpbiBBY2NvdW50" +
"BhMCVVMxGzAZBgNVBAoMEkNvbm5lY3RpZmllciwgSW5jLjEVMBMGA1UECwwMT25l" +
"BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ymFFiFfvDY/YsHFNg7sLON3luGo" +
"TG9naW4gSWRQMR8wHQYDVQQDDBZPbmVMb2dpbiBBY2NvdW50IDQ1NTAxMIIBIjAN" +
"I84UQx3N8nwl5ayfOJM3KC4AvExeWQQxfc2nO01SPrgJEy/DLr8OeFIXEVVBPVFe" +
"MKa2TnOARRImshLFzehOu0S+3AcrTWUnQccjpdpC/VUY8z65ntfm0W0XHtJ3HkVW" +
"uUMPl63X/OU7RLm0ALKahMs9+WV7LcwP/CkDGYUr2UcXz1Ehrcqh6x8FGx90OJCl" +
"Ws06mWpZYMSlMhNnT2cjN2+50HpU+51mearoZ6uKhD9SwpU4WkIFvfG1GGqj3ZS2" +
"mTvw1V7RZ28XV7ou5TUEf5YfpsWZ8FMAisiPZpO/mJCBqTSi2KjWN6P/rwIDAQAB" +
"IDQ1NTAxMB4XDTE0MDgwMzIxNDcyMloXDTE5MDgwNDIxNDcyMlowYjELMAkGA1UE" +
"o4HiMIHfMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFwXtgC2NizDcjsi2SM+Jzt5" +
"cMt/MIGfBgNVHSMEgZcwgZSAFFwXtgC2NizDcjsi2SM+Jzt5cMt/oWakZDBiMQsw" +
"FAxVnl6NlEviXvBK6nXNiL0RMvCLMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B" +
"CQYDVQQGEwJVUzEbMBkGA1UECgwSQ29ubmVjdGlmaWVyLCBJbmMuMRUwEwYDVQQL" +
"d0Ld0d2Dt6Gvsczba6fsbdmka9sdjLAfkA9dasdA3sFkasyqoiMN09123jJAooAI" +
"AQUFAAOCAQEA0FiaxTnK6D9HwirzOcQ0a7/lqqXHnm9nOw6bUS9TKlMNkoV0CqIq" +
"I6r8zWcB1CqsvrPsB4c3jB0Uc3u8hl+mOkvPUsMOsfM1fV+iGMFl4bYpd/HxQOpv" +
"tWMpi0TPat/WrbNOEPikahZwMK/XycoZ09VaXFoooSpYoOAaS4pAEwfabneAt1Pu" +
"O0IS6PrERgRFOe0ww2K9SNImvDLpH1rd239PUXKFFAtasuZhw6ol+kJwgylcyEHU" +
"SHHfYGDkRCVStrFN5uzPOurZKEfa9NETAKN5p2VetJ6+G9xPV05ONjDNZQLpo+VY" +
"eewqdHDL2SDOiEAblF1hYy5dDb/Fjc3W0Q==";

  public void handle(String responseMessage) {
    // Read certificate
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    InputStream inputStream = new ByteArrayInputStream(Base64.decodeBase64(certificateS.getBytes("UTF-8")));
    X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
    inputStream.close();

    BasicX509Credential credential = new BasicX509Credential();
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(certificate.getPublicKey().getEncoded());
    PublicKey key = keyFactory.generatePublic(publicKeySpec);
    credential.setPublicKey(key);

    // Parse response
    byte[] base64DecodedResponse = Base64.decodeBase64(responseMessage);

    ByteArrayInputStream is = new ByteArrayInputStream(base64DecodedResponse);
    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    documentBuilderFactory.setNamespaceAware(true);
    DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
    Document document = docBuilder.parse(is);
    Element element = document.getDocumentElement();

    UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
    Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
    XMLObject responseXmlObj = unmarshaller.unmarshall(element);
    Response responseObj = (Response) responseXmlObj;
    Assertion assertion = responseObj.getAssertions().get(0);
    String subject = assertion.getSubject().getNameID().getValue();
    String issuer = assertion.getIssuer().getValue();
    String audience = assertion.getConditions().getAudienceRestrictions().get(0).getAudiences().get(0).getAudienceURI();
    String statusCode = responseObj.getStatus().getStatusCode().getValue();

    org.opensaml.xml.signature.Signature sig = assertion.getSignature();
    org.opensaml.xml.signature.SignatureValidator validator = new org.opensaml.xml.signature.SignatureValidator(credential);
    validator.validate(sig);
  }
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM