====== SAML Security token 取得の実装 ====== ここでは、[[study:java:sharepointonline:poc|POC of consuming Sharepoint online]]のPOC段階2について、Javaの実装例を紹介します。 {{keywords>Get SAML Security token from Sharepoint online}} ===== 動作環境について ===== テスト環境についてですが、Java7(IBM J9 VM (build 2.6, JRE 1.7.0 Windows 7 amd64-64 Compressed References 20150701_255667 (JIT enabled, AOT enabled))で、テストを行いました。\\ ビルドには、Mavenを使用しました。\\ 必要なlibrary dependencyを以下に示します。 org.springframework spring-web 4.3.25.RELEASE org.apache.commons commons-text 1.3 org.apache.httpcomponents httpclient 4.5.13 org.json json 20210307 org.bouncycastle bctls-fips 1.0.12.1 provided httpclientとして、apacheのhttpclientを使用していますが、Okhttpを使っても構いません。\\ また、RestTemplateを使用するため、spring-webを導入しています。\\ 特に注目して欲しい のは、boucycastleライブラリです。bouncycastleライブラリを導入する理由については、Binary token取得のjava実装編で紹介します。 ===== Main class code ===== 実装のメインになるコードを以下に示します。\\ private static final ResourceBundle RSC = ResourceBundle.getBundle("com.app.sample.ws.application"); private static final String STS_AUTH_ENDPOINT = "https://xxxxx.contoso.com/adfs/services/trust/2005/usernamemixed"; private Map namespacePrefixes = new HashMap(); static { //register the prefix of NameSpace namespacePrefixes.put("t", "http://schemas.xmlsoap.org/ws/2005/02/trust"); } public String receiveSamlSecurityToken() { String _token = ""; try { //request entity RequestEntity _requestEntity = RequestEntity .post(new URI(STS_AUTH_ENDPOINT)) .header("content-type", "application/soap+xml; charset=utf-8") .body(buildSamlSecurityRequestEnvelope()); ★Point1 RestTemplate _restTemplate = new RestTemplate(); _restTemplate.setRequestFactory(buildHttpComponentsClientHttpRequestFactory()); ★Point2 ResponseEntity _responseEntity = _restTemplate.exchange(_requestEntity, String.class); DOMResult _result = new DOMResult(); Transformer _transformer = TransformerFactory.newInstance().newTransformer(); _transformer.transform(new StringSource(_responseEntity.getBody()), _result); Document _definitionDocument = (Document) _result.getNode(); final String XPATH_EXPRESSION = "//t:RequestedSecurityToken/*"; ★Point3 Node _tokenNode = getXPathExpression(XPATH_EXPRESSION).evaluateAsNode(_definitionDocument); _token = nodeToXmlString(_tokenNode); ★Point4 if ("".equals(_token)) { logger.error("Unable to authenticate: empty token"); } } catch (Exception e) { logger.error("failed to receive SAML security token", e); } return _token; }//receiveSamlSecurityToken //build SAML request envelope private String buildSamlSecurityRequestEnvelope() { //UserName token mapping Map _mapRequest = new HashMap(); _mapRequest.put("sts_auth_url", STS_AUTH_ENDPOINT); _mapRequest.put("loginuser", RSC.getString("soap.auth.username")); _mapRequest.put("loginpass", RSC.getString("soap.auth.password")); //replace placeHolder StringSubstitutor _substitutor = new StringSubstitutor(_mapRequest, "%(", ")"); String _finalXMLRequest = _substitutor.replace(RSC.getString("soap.saml.token.request")); return _finalXMLRequest; }//buildSamlSecurityRequestEnvelope private HttpComponentsClientHttpRequestFactory buildHttpComponentsClientHttpRequestFactory() throws Exception { return HttpComponentsClientHttpRequestFactoryBuilder.build(); } //create xPathExpression private XPathExpression getXPathExpression(String expression) { XPathExpression _xPathExpressioin = XPathExpressionFactory.createXPathExpression(expression, namespacePrefixes); return _xPathExpressioin; } //convert nodes to XML string private String nodeToXmlString(Node node) throws Exception { StringWriter _writer = new StringWriter(); Transformer _transformer = TransformerFactory.newInstance().newTransformer(); _transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); _transformer.setOutputProperty(OutputKeys.INDENT, "no"); _transformer.transform(new DOMSource(node), new StreamResult(_writer)); return _writer.toString(); } ★Point1\\ SAML requestメッセージを作成する部分になります。\\ SOAP Envelopeメッセージ(xml)は、application.propertiesに格納されています。\\ application.propertiesの中身を以下に示します。 soap.auth.username=Sharepointユーザーアカウント soap.auth.password=ユーザーパスワード soap.saml.token.request=http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issuehttp://www.w3.org/2005/08/addressing/anonymous%(sts_auth_url)%(loginuser)%(loginpass)urn:federation:MicrosoftOnlinehttp://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKeyhttp://schemas.xmlsoap.org/ws/2005/02/trust/Issueurn:oasis:names:tc:SAML:1.0:assertion ★Point2\\ HttpComponentsClientHttpRequestFactoryを作成する部分です。\\ ごく一般的設定になりますが、ポイントとしてUser-Agentの指定です。((User-Agentを指定する理由は、SharePointサイトからの調整を回避するためです。詳細は[[https://docs.microsoft.com/ja-jp/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online#how-to-decorate-your-http-traffic-to-avoid-throttling|調整を回避するために、http トラフィックを装飾する方法]]を参照してください。))\\ コードの一部分を以下に示します。 public class HttpComponentsClientHttpRequestFactoryBuilder { public static HttpComponentsClientHttpRequestFactory build() { final int TIMEOUT = 5; //create connection manager PoolingHttpClientConnectionManager _cm = new PoolingHttpClientConnectionManager(); _cm.setMaxTotal(128); //default 20 _cm.setDefaultMaxPerRoute(24); //default 2 //request configuration RequestConfig _requestConfig = RequestConfig.custom() .setConnectTimeout(TIMEOUT * 1000) .setConnectionRequestTimeout(TIMEOUT * 1000) .setSocketTimeout(TIMEOUT * 1000) .build(); //create HttpClient HttpClientBuilder _builder = HttpClientBuilder.create() .setUserAgent("NONISV|Contoso|Sharepoint-demo/1.0") .setConnectionManager(_cm) .setDefaultRequestConfig(_requestConfig); HttpComponentsClientHttpRequestFactory _factory = new HttpComponentsClientHttpRequestFactory(_builder.build()); return _factory; } } ★Point3\\ SAML:Assertionを取り出す部分です。 ResponseのXMLからelementをXPathを利用して取り出します。\\ elementがnamespaceのものならXPathExpression生成時、namespaceを渡す必要があります。\\ ★Point4\\ 取り出したSAML:Assertionは、Node(Pretty-type)です。POCでも触れましたが、Binary tokenを取得する際、渡すSAML:Assertionはraw-typeです。\\ そのため、ここでNodeをStringに変換しています。 ~~DISCUSSION~~