====== 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~~