SAML开发

在我开发过程中主要是我们作为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会被格式化为:



c


transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4")用于设置缩进的大小

7.传输SAML

如果有需要POST请求,可通过简单form表单自动提交



注意:

opensaml 3 默认加密算法是sha256,如果需要sha1可能需要降低到opensaml 2
断言可以对assertion做,也可以对整个response做签名。根据需要自选。

你可能感兴趣的:(SAML开发)