OpenSAML 3 で AuthnRequest を 生成する
(毎日更新すると約束したな。アレは嘘だ。ごめんなさい)
概要
自前で SAML 認証用の ServiceProvider (SP) を作っている。
Java で書いてるので Shibboleth 社のOSSライブラリである OpenSAML を利用しているが、 7月末でOpenSAML 2 系が EOL になるため、v3 系に移行しようと頑張っている。
問題点
v2 系では以下のように AuthnRequest の生成を実装していた。
DefaultBootstrap.bootstrap(); IssuerBuilder issuerBuilder = new IssuerBuilder(); Issuer issuer = issuerBuilder.buildObject(SAMLConstants.SAML20_NS, "Issuer", "samlp"); issuer.setValue("http://hogehoge.hoge"); NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder(); NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject(); nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); AuthnRequestBuilder authnRequestBuilder = new AuthnRequestBuilder(); AuthnRequest authnRequest = authnRequestBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:protocol", "AuthnRequest", "samlp"); authnRequest.setIssuer(issuer); authnRequest.setNameIDPolicy(nameIdPolicy); authnRequest.setAssertionConsumerServiceURL("http://hogehoge.hogehoge/acs"); authnRequest.setIssueInstant(new DateTime()); authnRequest.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); authnRequest.setVersion(SAMLVersion.VERSION_20); authnRequest.setID("ctestid890123456789012345678901234567890"); Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(authnRequest); Element authDOM = marshaller.marshall(authnRequest); DOMImplementationLS domImplLS = (DOMImplementationLS) authDOM.getOwnerDocument().getImplementation(); LSSerializer serializer = domImplLS.createLSSerializer(); serializer.getDomConfig().setParameter("xml-declaration", false); String messageXml = serializer.writeToString(authDOM); Deflater deflater = new Deflater(Deflater.DEFLATED, true); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater); deflaterOutputStream.write(messageXml.getBytes()); deflaterOutputStream.close(); String samlRequest = new String(Base64.encodeBase64(byteArrayOutputStream.toByteArray(), false)); samlRequest = URLEncoder.encode(samlRequest, "UTF-8");
ひとまずこのソースのまま OpenSAML 2.6.4 から OpenSAML 3.1.1 に置き換えたところ、
DefaultBootstrap.bootstrap();
Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(authnRequest);
の2箇所でエラーが出てしまった。
解決策
DefaultBootstrap.bootstrap();
v2 系では DefaultBootstrap.bootstrap();
のようにライブラリの初期化を行っていたが、v3 系ではそれとは異なり、 InitializationService.initialize();
のように初期化を行うようだ。
Initialization and Configuration - OpenSAML 3 - Confluence
Various components of the OpenSAML library must be initialized before they can be used. Since the library is in itself modular, the initialization process takes a modular approach use the Java Service API.
The 2 primary components with which to be familiar are:
- org.opensaml.core.config.Initializer
- org.opensaml.core.config.InitializationService
Configuration.getMarshallerFactory()〜
Configuration
クラスが存在しないため、エラーが出ていた。
上記の DefaultBootstrap のこともあり、どうも初期化の扱いがいろいろ変わっているようで、インスタンスをどこから取得すればいいのかなーと調べていたところ、Shibboleth 社の Developers Forum で以下のトピックを発見した。
shibboleth.1660669.n2.nabble.com
トピック内でやりとりされている通り、InitializationService#initialize()
で初期化された各種インスタンスは XMLObjectProviderRegistrySupport
クラスから直接取得することができるようで、XMLObjectProviderRegistrySupport#getMarshallerFactory()
を利用することで無事取得することができた。
(単にMarshallerFactory
クラスを new してもからっぽのインスタンスができるだけ、だそうです。)
(また Unmarshaller の場合も同様のようです。)
結論
上記をふまえ、以下のように実装することで解決。
IssuerBuilder issuerBuilder = new IssuerBuilder(); Issuer issuer = issuerBuilder.buildObject(SAMLConstants.SAML20_NS, "Issuer", "samlp"); issuer.setValue("http://hogehoge.hoge"); NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder(); NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject(); nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); AuthnRequestBuilder authnRequestBuilder = new AuthnRequestBuilder(); AuthnRequest authnRequest = authnRequestBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:protocol", "AuthnRequest", "samlp"); authnRequest.setIssuer(issuer); authnRequest.setNameIDPolicy(nameIdPolicy); authnRequest.setAssertionConsumerServiceURL("http://hogehoge.hogehoge/acs"); authnRequest.setIssueInstant(new DateTime()); authnRequest.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); authnRequest.setVersion(SAMLVersion.VERSION_20); authnRequest.setID("ctestid890123456789012345678901234567890"); MarshallerFactory marshallerFactory = XMLObjectProviderRegistrySupport.getMarshallerFactory(); Marshaller marshaller = marshallerFactory.getMarshaller(authnRequest); Element authDOM = marshaller.marshall(authnRequest); DOMImplementationLS domImplLS = (DOMImplementationLS) authDOM.getOwnerDocument().getImplementation(); LSSerializer serializer = domImplLS.createLSSerializer(); serializer.getDomConfig().setParameter("xml-declaration", false); String messageXml = serializer.writeToString(authDOM); Deflater deflater = new Deflater(Deflater.DEFLATED, true); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater); deflaterOutputStream.write(messageXml.getBytes()); deflaterOutputStream.close(); String samlRequest = new String(Base64.encodeBase64(byteArrayOutputStream.toByteArray(), false)); samlRequest = URLEncoder.encode(samlRequest, "UTF-8");