在我开发过程中主要是我们作为IDP,做统一登录后发送给第三方SP做免登录。
主要就几个过程:
1.SP通过发送SAML AuthnRequest到IDP,来请求鉴别用户身份
2.IDP验证身份,然后通过SAML产物响应(SAML Artifact Response)发送回SP
其中几个点:
1.引入jar初始化
opensaml 3 采用的是分开打包maven的方式,需要引入很多个模块,opensaml 2 是整体maven直接引入即可。例如:
opensam3.x.x版本:
org.opensaml opensaml-core 3.2.0 org.opensaml opensaml-saml-api 3.2.0 org.opensaml opensaml-saml-impl 3.2.0 org.opensaml opensaml-messaging-api 3.2.0 org.opensaml opensaml-messaging-impl 3.2.0 org.opensaml opensaml-soap-api 3.2.0 org.opensaml opensaml-soap-impl 3.2.0
opensaml2.x.x版本:
org.opensaml opensaml 2.6.4 ch.qos.logback logback-core 1.1.7 ch.qos.logback logback-classic 1.1.7 commons-logging commons-logging 1.2 org.opensaml xmltooling 1.4.4
2.记得初始化SAML,否则会出现NPE
opensaml2.x.x版本:
Security.addProvider(new BouncyCastleProvider()); DefaultBootstrap.bootstrap();
opensam3.x.x版本:
InitializationService.initialize();
3.创建SAML对象
SAML对象的创建使用了工厂模式和构建者模式,涉及到链式配置和类型准换。
创建SAML断言对象的方法如下:
XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory(); Assertion assertion = (Assertion) builderFactory .getBuilder(Assertion.DEFAULT_ELEMENT_NAME) .buildObject(Assertion.DEFAULT_ELEMENT_NAME);
为了避免大量不需要的代码,使用泛型的工具方法是一个好主意。可以通过如下方法生成不同类型的对象:
public static
T buildSAMLObject(final Class clazz) { XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory(); //opensaml3.x.x XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory(); //opensaml2.x.x QName defaultElementName = (QName) clazz.getDeclaredField( "DEFAULT_ELEMENT_NAME").get(null); T object = (T) builderFactory.getBuilder(defaultElementName) .buildObject(defaultElementName); return object; `}`
通过使用上面的方法,就可以将生成断言对象的代码简化为一行;
OpenSAMLUtils.buildSAMLObject(Assertion.class);
4.创建response或者request
根据所需要的xml去创建response或者requset即可,例如:
Response response = buildSAMLObject(Response.class); response.setIssueInstant(new DateTime()); response.setID(generateSecureRandomId()); response.setVersion(SAMLVersion.VERSION_20); response.setDestination(url);
5.OpenSAML中的密码签名
签名是密码学一个证明数据完整性的手段。OpenSAML中提供了工具来对短信信息经行签名以及验证签名。由于断言的表现形式是一种XML,所以其签名也是基于XML签名方法。
其大致流程为:
1.确定需要确认XML文档中哪些内容签名,通过URI将这些数据项目表示为引用资源 ,以Reference元素表示。
对于断言消息来说,整个断言都是需要加密的,所以其URI为断言的ID;
2.对待签名的数据进行转化处理,包括制定的编码规则、规范化算法以及应用于已签名数据的XSLT转换,transform元素用于指定要应用的算法;
3.转化后,对每个Reference元素中引用的URI资源应用消息摘要算法(由于是对整个断言签名,所以只会出现一个Reference元素);
DigestMethod元素指定元素应用的消息摘要算法,消息摘要值将存储在DigestValue元素中;
4.构造包含Reference元素的SignedInfo元素,
5.使用CananonicalizationMethod元素指定的规范化算法对SignedInfo元素进行规范化,如果不进行规范化,验证XML签名将可能因为XML结构或者表示方便不同而失败; XML规范标准见此处;
6.计算SignedInfo元素的摘要,并使用SignatureMethod元素中声明的签名算法,对其进行签名,得到的签名值放到SignatureValue元素中;
7.添加KeyInfo元素(可选),表面签名所使用的密钥信息,如果双方已经提前协商好密钥信息,就可以省却该项;
8.构造Signature元素,其中包括SignedInfo,SignedValue,KeyInfo等元素;
核实和验证XML的签名:
1.和验证摘要值: 重新计算Refernce元素引用的数据对象的摘要值,计算过程包括应用Transforms元素制定进行转换,并使用DigestMethod元素指定的算法计算转化结果摘要,最后将计算得到的摘要值与DigestValue元素中的值进行比较
2.核实和验证签名: 使用KeyInfo元素包含的或者从外部资源获得的密钥信息重新计算SignedInfo元素中的签名;将计算得到的签名值与SignatureVlue元素的值进行比较。
5.1 对数据签名
在OpenSAML中,每一个实现SignableXMLObject接口的对象都可以被签名。签名的产生一共分为4步:
1.创建签名类对象并赋予签名属性:
Signature signature = OpenSAMLUtils.buildSAMLObject(Signature.class); //设定证书对象,其中包含密钥 signature.setSigningCredential(credential); //设定签名算法 signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); //设定XML规范化算法 signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
2.将SignableXMLObject对象和签名类对象绑定起来:
signableXMLObject.setSignature(signature);
3.将SignableXMLObject对象序列化为XML对象
//根据需要是对response签名还是对assertion,自由选择业务所需 assertion.setSignature(signature); response.setSignature(signature); //opensaml3.x.x XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object).marshall(object); //opensaml2.x.x SecurityConfiguration secConfig = Configuration.getGlobalSecurityConfiguration(); String keyInfoGeneratorProfile = "XMLSignature"; SecurityHelper.prepareSignatureParams(signature, signature.getSigningCredential(), secConfig, keyInfoGeneratorProfile); Configuration.getMarshallerFactory().getMarshaller(assertion).marshall(assertion);
4.使用Signer工具类生成签名
Signer.signObject(signature);
5.2 验证签名
在验证签名之前最好是判断该消息是否被签名:
if (!assertion.isSigned()) { throw new RuntimeException("The SAML Assertion was not signed"); }
验证签名的第一步是判断该签名是否符合SAML签名的标准声明,也就是是否应用了XML的规范化算法:
SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); profileValidator.validate(assertion.getSignature());
然后再进行真正密码学意义上的验证签名:
SignatureValidator sigValidator = new SignatureValidator(credential); sigValidator.validate(assertion.getSignature());
SignatureValidator对象中被设置了credential对象,其中包含密钥信息,直接使用即可。
6.转换格式为string后进行base64加密
Element element = null; if (object instanceof SignableSAMLObject && ((SignableSAMLObject) object).isSigned() && object.getDOM() != null) { element = object.getDOM(); } else { Marshaller out = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); //opensaml3.x.x Marshaller out = Configuration.getMarshallerFactory().getMarshaller(object).marshall(object);//opensaml2.x.x element = object.getDOM(); } Transformer transformer = TransformerFactory.newInstance().newTransformer(); StreamResult result = new StreamResult(new StringWriter()); DOMSource source = new DOMSource(element); transformer.transform(source, result); String xmlString = result.getWriter().toString();
其中 transform可以进行格式化,达到你想要的目的。
例如使用transformer.setOutputProperty(OutputKeys.INDENT, "yes")可以用于设置缩进的。
如果有下面这样一个xml文档:
c
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4")用于设置缩进的大小
7.传输SAML
如果有需要POST请求,可通过简单form表单自动提交
注意:
opensaml 3 默认加密算法是sha256,如果需要sha1可能需要降低到opensaml 2
断言可以对assertion做,也可以对整个response做签名。根据需要自选。