前回はSP-RedirectedのSSOシナリオまで試しましたので、今回はようやくSAMLの属性を取ってきます。TAIがSubject
を生成してくれることはわかっていますので、これをどうやって取ってくるかというと、
Subject subject = WSSubject.getCallerSubject();
となります。標準APIのSubject.getSubject()
にはJAAS1.0の設計上のミスがあるらしく、WSSubject.getCallerSubject()
を使用しなさいということのようです1。
SAML属性の取得
先に主題であるSAML属性の取得からやってしまいます。細かいところは後から見ていきます。
まずはSubject
からcom.ibm.websphere.wssecurity.wssapi.token.SAMLToken
を取得します。さらにSAMLToken
からList<com.ibm.wsspi.wssecurity.saml.data.SAMLAttribute>
を取ってきます。SAML属性はSAMLAttribute.getName()
、値(String[]
)はSAMLAttribute.getStringAttributeValue()
で取得できます。
LinkedHashMap<String, String> samlmap = new LinkedHashMap<String,String>();
Set<SAMLToken> samlTokenSet = callerSubject.getPrivateCredentials(SAMLToken.class);
for (SAMLToken samlToken : samlTokenSet) {
List<SAMLAttribute> allAttributes = samlToken.getSAMLAttributes(); //SAMLTokenの属性情報を取得
if (allAttributes.isEmpty()) {
out.println("<p class=\"text-danger\">no SAML attribute</p>");
return;
}
for (SAMLAttribute anAttribute : allAttributes) {
String attributeName = anAttribute.getName(); //属性名を取得
String[] attributeValues = anAttribute.getStringAttributeValue(); //属性値配列を取得
String value = null;
if(attributeValues.length == 1) {
value = attributeValues[0];
} else if(attributeValues.length == 0) {
value = "no value";
} else {
StringBuilder sb = new StringBuilder("[");
for(int i = 0; i < attributeValues.length; i++) {
sb.append(attributeValues[i]);
sb.append(", ");
}
sb.append("]");
value = sb.toString();
}
samlmap.put(attributeName, value);
}
}
toTable(samlmap, out); //LinkedHashMapをHTMLのテーブルに出力
MailAddress alto@mognet.net
Description [hi! i'm alto the border collie,lovely dog,]
Locality Tokyo
EmployeeNumber 100
GivenName Alto
UserPassword {SSHA512}4IQR42cG2N4Wqu/SCFM1eRLwENTFYWWcJoN0DcGjiQz/aZR0eU7csdshMugJqTVbE7KNJ6AcSswHdqkjU1ap2BnwNcd1AY3H
DepartmentNumber 0
TelephoneNumber 0120123456
BusinessCategory sheep dog
UserId alto
CarLicense 持ってません
DisplayName alto the sweet
SirName Benjiamin
MognetId alto1
CommonName Jiang Benjiamin Alto of Mountfield
無事にLDAPのユーザ情報(OpenAMでSAML属性としてマッピングした名前とその値)が取得できました。
HttpServletRequestからの情報
ここから細かい話になります。まずはどんなSubject
が返ってきてどのような情報が入っているのか見てみたいと思います。とその前に、HttpServletRequest
にも認証タイプ、リモートユーザ、プリンシパルといったオブジェクトを取得するメソッドがありますので、まずはここからみてみます。
private void printRequestAuthinfo(HttpServletRequest request, HttpServletResponse response, PrintWriter out) {
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
out.println("<h1>Request Authentication Info</h1>");
map.put("AuthType", request.getAuthType());
map.put("Remote User", request.getRemoteUser());
map.put("Principal", request.getUserPrincipal().toString());
toTable(map, out); //LinkedHashMapをHTMLテーブルにしてoutに出力するメソッド
}
Request Authentication Info
AuthType BASIC
Remote User /www.alto.net/openam/p0BwwjTucmBs0UiYUGpu0f4Z31JM
Principal p0BwwjTucmBs0UiYUGpu0f4Z31JM
Basic認証とみなされるようです。Principalにはリモートユーザ名の末尾文字列しか持っていないようです。そしてリモートユーザがなんだか見慣れない文字列になっています。これはIdPが生成した一時的なユーザ名です。
SAMLリクエストパラメータ
今回のSSOシナリオでは、NameIDFormat
としてurn:oasis:names:tc:SAML:2.0:nameid-format:transient
を指定しています。どこで指定しているかといえば、IdPにアクセスする際のURLです2。具体的には、リクエストのURLクエリでNameIDFormat=urn:oasis:names:tc:SAML:2.0:nameid-format:transient
と指定しています。ここで改めてIdP-initiatedにおけるHTTPパラメタの意味を整理してみます。ちなみに、全文はこうです。
https://www.alto.net/openam/idpssoinit?metaAlias=/idp&spENtityID=https://www.docker.mognet.net/samlsps/acs&binding=urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST&NameIDFormat=urn:oasis:names:tc:SAML:2.0:nameid-format:transient&RelayState=https://www.docker.mognet.net/snoop`
パラメタ名 | 値 | 内容 |
---|---|---|
metaAlias | /idp |
OASISの文書にも記載されておらずわかりづらいですが、SP-initiatedシナリオで、IdP認証後のSPへのリダイレクト時には/sp となります3。 |
spENtityID | [ACS_URL] | SPからインポートしたentityID を指定します。 |
binding | ~省略~:HTTP-POST |
SAMLのバインディング(HTTPのPOSTなのかSOAP(artifact)なのか、つまりどんなリクエスト形式を使用するか)を指定します。 |
NameIDFormat | ~省略~transient |
IdPが認証した時に発行するユーザ名の属性です。persistent を指定すると永続的な名前(つまり同じユーザでログインしたら同じユーザ名になる)、transient を指定すると一時的な名前になります。他にemail 等の属性値を指定することもできます。 |
RelayState | リダイレクト先URL | そのままですがACSへのアクセス後にリダイレクトする先のURLを指定します。 |
というわけで、このユーザー名はIdPが適当に振った文字列ということでした。
Subjectの情報
それではSubjectを取得します。とりあえず文字列にして出力(toString()
)してみます。
Subject callerSubject = WSSubject.getCallerSubject(); //Subjectを取得
out.println("<h2>Caller Subject toString()</h2>");
out.println("<p class=\"text-info\">" + callerSubject.toString() + "</p>");
Subject:
Principal: https://www.alto.net/openam/p0BwwjTucmBs0UiYUGpu0f4Z31JM
Public Credential: com.ibm.ws.security.auth.WSCredentialImpl@3857789b
Private Credential: {
com.ibm.wsspi.security.cred.cacheKey=saml_acs_tai:sso_saml20_1868203562n2095008900,
com.ibm.wsspi.security.cred.uniqueId=https://www.alto.net/openam/p0BwwjTucmBs0UiYUGpu0f4Z31JM,
com.ibm.wsspi.security.cred.realm=https://www.alto.net/openam,
Saml20TaiSsoPartners=saml_acs_tai:sso_saml20_,
com.ibm.wsspi.security.cred.securityName=p0BwwjTucmBs0UiYUGpu0f4Z31JM,
com.ibm.wsspi.security.cred.groups=[]
}
Private Credential: com.ibm.ws.wssecurity.platform.websphere.wssapi.token.impl.WasSAML20TokenImpl:s2b428665157184b8683f56e597fafa926c4a8d2c4
Private Credential: com.ibm.ws.security.token.SingleSignonTokenImpl@f4a3dc8a
Private Credential: com.ibm.ws.security.token.AuthenticationTokenImpl@95ad9ac0
Private Credential: com.ibm.ws.security.token.AuthorizationTokenImpl@dfa455b1
いろいろ入ってますね。一個めのPrivate Credentialには固定値が入っていますが、他はオブジェクトへのポインタになっています。Private Credentialセット内に5つのオブジェクトが確認でき、うち一つ目はなんだかitaratable
なもののようです(serializeされて表示されています)。これはgetClass().getName()
してみたところ、java.util.Hashtable
でした。その中身も含め、Subject
は全てWASが生成したオブジェクトの集合ですね。
ちなみに、このうち以下のオブジェクトはnull
でした。
- Public Credential: com.ibm.ws.security.auth.WSCredentialImple
- Private Credential: com.ibm.ws.wssecurity.platform.websphere.wssapi.token.impl.WasSAML20TokenImpl:s2b428665157184b8683f56e597fafa926c4a8d2c4
- Private Credential: com.ibm.ws.security.token.SingleSignonTokenImpl@f4a3dc8a
- Private Credential: com.ibm.ws.security.token.AuthenticationTokenImpl@95ad9ac0
"Private Credential: com.ibm.ws.security.token.AuthorizationTokenImpl"だけがnot null
だったわけですが、これをさらにIntrospection
してみると、以下のような内容でした。
name | type | getter | setter | value |
---|---|---|---|---|
attributeNames | java.util.Enumeration | getAttributeNames | n/a | java.util.Collections$3@97ee02c7 |
basicAuth | boolean | isBasicAuth | n/a | false |
bytes | [B | getBytes | n/a | [B@2328114e |
class | java.lang.Class | getClass | n/a | class com.ibm.ws.security.token.AuthorizationTokenImpl |
expiration | long | getExpiration | n/a | 1517654532343 |
forwardable | boolean | isForwardable | n/a | true |
name | java.lang.String | getName | n/a | com.ibm.ws.security.token.AuthorizationTokenImpl |
principal | java.lang.String | getPrincipal | n/a | user:https://www.alto.net/openam/gzzUy3PD1zVMI8BnrgkHL6uXgCHZ |
token | com.ibm.wsspi.security.ltpa.Token | getToken | setToken | 1517654532343 |
uniqueID | java.lang.String | getUniqueID | n/a | saml_acs_tai:sso_saml20_1868203562n54927714 |
valid | boolean | isValid | n/a | true |
version | short | getVersion | n/a | 1 |
何度かやり直しながらなので、一時的な値は前に載せているものと異なりますが、確かに"attributeNames"といった情報が入っていそうなフィールドがみつかりました。SAMLToken
自体はインタフェースなので、どこかに実装クラスがあるはずですが、これ以上はWASが提供するjar
をデコンパイルしていかないと追いかけられませんね。というわけで今回はSAML属性の値をサーブレットから取得しました、そしてそれはSubject
内のAuthorizationTokenImpl
に入っていそうだというお話でした。