SAML

SAMLで学ぶXML Schema

ハローワールド。

SAMLはXMLを利用した認証のための規格です。SAMLではXML Schemaと呼ばれるXMLの構造・内容などを定義する方法でSAMLで利用するXMLを定義します。

SAMLの仕様ではXML Schemaを利用してデータ構造を説明するため、このXML Schema知ることでSAMLへの理解の素早さに繋がります。
ただしXML Schema自体がかなり古い仕様であることもあり、解説する文章も少ないです。ここではSAMLを読むことに特化しXML Schemaの全体的な理解を高めていきます。

SAMLとXML Schemaの概要

SAMLはOASISと呼ばれる標準化団体により策定されています。最新版は2.0で、2005年3月に策定されています。
認証のための規格であり、特にSSOとして一般的に知られていると思います。

XML SchemaはW3Cにより策定されており、1つの入門書と2つの仕様のドキュメントで解説がされています。現在の最新版は第二版で、2004年10月28日に策定されています。
XMLの文章構造を定義するための方法であり、またXSDというファイル形式でプログラム上で生成や検証などを利用することが出来ます。

SAMLの主なXMLの定義はSAMLの仕様のうち"Core"と呼ばれるものに含まれています。それ以外にも"Metadata"にも記載があったりします。
この記事では、"Core"のうち主に使いそうなXMLを選択して、その中で利用されているものなどを中心にXML Schemaの意味を確認していきます。

XML Schemaを知る前に

XML Schemaには登場人物が主に3人いると考えていただくとわかりやすいと思います。

  • 要素
  • 属性
  • 要素型

これらのうち、「要素」と「属性」はXMLに直接関係するものです。残りの「要素型」はその名の通り「要素」の「型」となるものです。
XML Schemaではこの「要素型」を継承したりすることで定義でき、「要素型」を「要素」にマッピングすることで、初めて「要素」をXML中で利用できる、というイメージです。XML Schemaはこの「要素型」を作成し、「要素」として名前を与えることを目的とした文章と言っても過言ではないです。

比較的わかりやすい図が Part1 - Schema Components Diagram (non-normative) に乗っているため引用します。

下記図のうちComplex Type Definition および Simple Type Definition が要素型と考えると、要素の定義である Element Declaration は要素系と紐付いています(Identifiy−constraint というのはありますが、SAMLの定義では出てないと思います)。
一方でXML Schemaの主人公とも呼べる存在はComplex Typeです。これはElementを複数持ったもの(図中ではparticleと名前が付いています)とAttribute Useつまり属性などから作成されており、要素型は要素を持ち(必ずではない)、要素もまた)要素型を持っている(これもまた必ずではない)という関係になっています。

XML Schema Part1 Schema Components Diagram(normative)から引用。XMLの構造のクラス図

XML Schemaの基本

まずはSAMLの中心的な型としてRequestAbstractTypeの定義を見ながらXML Schemaに触れていきます。これは名前にAbstractが入っていることからも分かる通り、Requestの基底になる型です。

<?xml version="1.0" encoding="UTF-8"?> <schema targetNamespace="urn:oasis:names:tc:SAML:2.0:protocol" xmlns="http://www.w3.org/2001/XMLSchema" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" elementFormDefault="unqualified" attributeFormDefault="unqualified" blockDefault="substitution" version="2.0"> <import namespace="urn:oasis:names:tc:SAML:2.0:assertion" schemaLocation="saml-schema-assertion-2.0.xsd"/> <import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd"/> <annotation> <documentation> Document identifier: saml-schema-protocol-2.0 Location: http://docs.oasis-open.org/security/saml/v2.0/ Revision history: V1.0 (November, 2002): Initial Standard Schema. V1.1 (September, 2003): Updates within the same V1.0 namespace. V2.0 (March, 2005): New protocol schema based in a SAML V2.0 namespace. </documentation> </annotation> <complexType name="RequestAbstractType" abstract="true"> <sequence> <element ref="saml:Issuer" minOccurs="0"/> <element ref="ds:Signature" minOccurs="0"/> <element ref="samlp:Extensions" minOccurs="0"/> </sequence> <attribute name="ID" type="ID" use="required"/> <attribute name="Version" type="string" use="required"/> <attribute name="IssueInstant" type="dateTime" use="required"/> <attribute name="Destination" type="anyURI" use="optional"/> <attribute name="Consent" type="anyURI" use="optional"/> </complexType> <!-- 中略 --> </schema>

XML Schema自身もXMLで記載されているため、1行目はXML宣言から始まります。

<?xml version="1.0" encoding="UTF-8"?>

schema要素

2行目にあるschema要素ですが、XML Schemaのルートとなる要素です。定義があっさりしすぎていてこれを読むだけだと何もわかりません。

いくつかの属性がありますので、必要なものをピックアップしていきます。

xmlns から始まる属性

xmlnsから始まる属性がありますが、これはxmlのnamespaceを表すものです。XML SchemaではなくXMLに定義されているものです。XHTMLとか知っているならば、見たことあるかもしれません。

xmlnsのみの属性は、その名前空間に属する要素をプレフィックス無しで使うことが出来ます。今回ではhttp://www.w3.org/2001/XMLSchemaが指定されていますが、つまりXML Schemaの要素(schemaimport, annotationなど)をプレフィックス無しで指定することが出来ます。

targetNameSpace

targetNameSpace属性は自分自身の名前空間を表すものです。RequestAbstractTypeの名前空間は urn:oasis:names:tc:SAML:2.0:protocol ということになります。xmlnsurn:oasis:names:tc:SAML:2.0:protocolを指定する(または要素を利用する場合に毎回 と書く)ことをすれば利用できるようになります。

その他の属性

Version属性は実に簡素な説明がされています。

The other attributes (id and version) are for user convenience, and this specification defines no semantics for them.

その他の属性(id、version)はユーザーの利便性のためであり、本仕様ではこれらのセマンティクスを定義していない。

簡単に言えばほぼ意味の無い値です。

その他の属性(elementForDefault, elementFormDefault, blockDefault)についても下記の記載があります。

The blockDefault, finalDefault, attributeFormDefault, elementFormDefault and targetNamespace attributes are appealed to in the sub-sections above, as they provide global information applicable to many representation/component correspondences.

blockDefault, finalDefault, attributeFormDefault, elementFormDefault 及び targetNamespace 属性は、多くの表現とコンポーネントの対応に適用できるグローバルな情報を提供するため、上記のサブセクション(訳注:他のコンポーネントについての定義)にて説明する。

仕様として読みにくいことこの上ないですね。簡単に言うと、他の要素にこれらの属性は関わってきます。たとえばelementFormDefaultelement要素のformという属性のデフォルトの値を設定できます。これらは、名前空間などを適格にするかなどの要素ですが、SAMLのXMLを使う上ではあまり気にする値ではないです。

import 要素

import要素はその名の通り、他のXML Schemaの要素をインポートします。namespaceにはURIを、schemaLocationにはどこにファイルがあるかを指定します。

annotation 要素

annotation 要素はその名の通り注釈です。人間やコンピューターに対して、説明や注釈、その他特記事項を記載するための場所です。小子供には、doucmentation 要素とappinfo要素を持つことができ、前者が人間用、後者がコンピューター用の情報です。

complexType 要素

XML Schemaの最も重要な要素の1つ、complexType要素です。これはその名の通り複雑な型を表すものであり、XMLの構造を定義する型の1つでもあります。その他にはsimpleTypeがあります。simpleTypeは文字列や数字などに制約を加えるために使わます。一方でcomplexType は子要素や属性などを自由に付け加えることが出来ます。

name属性は要素の名前となります。

abstract属性は、その名の通り抽象要素かどうかを表します。これがtrueの場合、この要素は継承で利用できるようになります。後に継承方法は解説します。

sequence要素

complexType要素の子供で重要な子供は、属性を表すattribute要素と、要素を表すall, choice, そしてsequence要素です。
今回出現しているのは sequence要素ですね。

sequence要素は子供の要素がそれぞれ指定した通りの順番で出現することを想定しています。子供に、実際にどの要素が現れるかを表し、SAMLの場合は基本的に element要素を指定します。今回もそうですね。それ以外にもchoice要素が指定される場合もあります。

element要素

element要素は「どの要素を子供に持つか」表す要素です。

ref属性にどの要素かを表すXMLの要素の名前が入ります。

重要なのは minOccurs属性です。これは、最低何個の要素が必要化を表すものです。逆にmaxOccurs属性もあり、これは最大何個まで要素を置けるかを設定できます。
minOccurs, maxOccursともにデフォルトは1で、何も指定しない場合は必ず1つ必要な要素となります。上記の例では0が指定されているため、0個または1個の要素が必要、つまり存在しなくても問題のない要素、という事になっています。

attribute要素

attribute要素は、その名の通り属性に関する定義を表すものです。element要素が要素に対する定義なのでattribute要素が属性に関する定義という関係ですね。

name属性およびtype属性は、これもまたその通りです。それぞれ要素の名前と要素の型を表します。

use属性も見ただけでわかると思いますが、requiredが指定されれば属性として指定必須となり、optionalでは指定しなくてもよいという意味になります。デフォルトでは optionalです。その他にもprohibitedがあります(ちゃんと調べてないですが、多分禁止するものです。型を拡張するときに使うのだと思います)。

どのような要素になるのか

上記のXMLからcomplexTypeの部分のみを抜き出しました。
下記の要素型を要素として扱う場合、どのような要素になるのか見ていきましょう。と言っても下記の方は継承でしか利用しないのですが、イメージが湧くようにしています。

<complexType name="RequestAbstractType" abstract="true"> <sequence> <element ref="saml:Issuer" minOccurs="0"/> <element ref="ds:Signature" minOccurs="0"/> <element ref="samlp:Extensions" minOccurs="0"/> </sequence> <attribute name="ID" type="ID" use="required"/> <attribute name="Version" type="string" use="required"/> <attribute name="IssueInstant" type="dateTime" use="required"/> <attribute name="Destination" type="anyURI" use="optional"/> <attribute name="Consent" type="anyURI" use="optional"/> </complexType> <!-- 上記を要素として扱うと --> <RequestAbstractType ID="ID(必須)" Version="2.0(必須)" IssueInstant="20230226T012345+0900(必須)" Destination="https://exmaple.com/saml/sso/login(任意)"> <saml:Issuer>https://sp.example.com/SAML2(任意)</saml:Issuer> <ds:Signature>...署名情報(任意)</ds:Signature> <samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/> </RequestAbstractType>

という感じになります。

継承して要素を定義する

RequestAbstractTypeというのは、SAMLのRequestに関する素となる要素型となります。
これを継承して、認証のリクエストであるAuthnRequestの要素を定義していますが、その方法を確認していきましょう。

<element name="AuthnRequest" type="samlp:AuthnRequestType"/> <complexType name="AuthnRequestType"> <complexContent> <extension base="samlp:RequestAbstractType"> <sequence> <element ref="saml:Subject" minOccurs="0"/> <element ref="samlp:NameIDPolicy" minOccurs="0"/> <element ref="saml:Conditions" minOccurs="0"/> <element ref="samlp:RequestedAuthnContext" minOccurs="0"/> <element ref="samlp:Scoping" minOccurs="0"/> </sequence> <attribute name="ForceAuthn" type="boolean" use="optional"/> <attribute name="IsPassive" type="boolean" use="optional"/> <attribute name="ProtocolBinding" type="anyURI" use="optional"/> <attribute name="AssertionConsumerServiceIndex" type="unsignedShort" use="optional"/> <attribute name="AssertionConsumerServiceURL" type="anyURI" use="optional"/> <attribute name="AttributeConsumingServiceIndex" type="unsignedShort" use="optional"/> <attribute name="ProviderName" type="string" use="optional"/> </extension> </complexContent> </complexType>

schema直下にあるelement要素

element要素には大きく2つの存在があります。それが「グローバル」と「ローカル」です。
schema要素の直下に存在する場合は「グローバル」として定義されます。正確にはいくつか制限があり、例えばref形式が利用できないなどあります。それ以外の要素は「ローカル」となります。

「グローバル」のelement要素は、別のスキーマから参照できるため、プログラム的に言えば外部にexportするためのものです。

例えばAuthnRequestでは下記の定義がされています。

<element name="AuthnRequest" type="samlp:AuthnRequestType"/>

これは外部にAuthnRequestという名前で定義しています。要素の型はsamlp:AuthnRequestTypeとなりますね。
この定義をすることで、外部でAuthnRequest要素を利用することが出来ます。

complexContent要素とextension要素で型を継承する

AuthnRequestRequestAbstractTypeを継承して型を作成しています。そのためにはcomplexContent要素とextension要素を利用しています。

complexContent要素は特定のcomplexTypeを「拡張」または「制限」するために利用するための要素です。
子供の要素にextension要素を置くと「拡張」(本記事では継承と呼ぶ)ことになり、restriction要素を置くと「制限」できます。

extension要素は base 属性に指定した型に子要素と型を加えます。子要素は既存の型の末尾に追加のみ許されています。ちなみに将来のバージョンでは様々な拡張方法が追加されるようですね。楽しみです。

今回は RequestAbstractTypebase要素に指定されているため、XML-Schemaの基本で記載した要素を拡張するということですね。

例えば、既存の子要素である saml:Issuerds:Signaturesamlp:Extensionsの3つに加えsaml:Subjectsamlp:NameIDPolicyなどの5つが追加されます。全てminOccursが0なので存在しなくても問題ないですが、sequenceで定義されているため、存在する場合は順番はかえてはいけません。

文字列を制限する

SAMLでは一部制限する型が出てきます。すでに記載したとおりですがrestriction要素を使うと「制限」をすることが出来ます。
紹介した時はcomplexType及びcomplexContent要素を利用しましたが、その他にSAMLに登場するパターンを確認してみます。

SimpleType要素は子要素も属性も存在しない要素型です。子要素が存在しないと言うと意味が分かりづらいですが、文字列を表す型という感じです。

SAMLでは認可に関する状態を表すAuthzDecisionStatementという要素があり、その要素の属性にDecision属性というのがあります。
このDecision属性はPermit, Deny, Indenterminateの3つあります。認可にIndenterminateが合って良いのかわからないですが(SAMLは認可フレームワークじゃないんで良いってことだと思います)、このDecisionの型をどうやって定義するか。このときにrestriction要素が必要です。

実際の型は下記となっています。
restrictionbasestringとし、制限する際にはenumerationで利用可能な値を定義していきます。

<simpleType name="DecisionType"> <restriction base="string"> <enumeration value="Permit"/> <enumeration value="Deny"/> <enumeration value="Indeterminate"/> </restriction> </simpleType>

いずれか1つ(以上)

complexType要素の登場人物ではsequenceの他にallchoiceがあるといいました。

all要素は「指定した複数の要素が任意の順番で現れていいよ」ということを設定するものです。これはSAMLの要素に登場しなかったので紹介だけにとどめておきます。

choice要素はSAMLの要素の中でも重要なAssertion要素でも利用されているので見ていきましょう。

<element name="Assertion" type="saml:AssertionType"/> <complexType name="AssertionType"> <sequence> <element ref="saml:Issuer"/> <element ref="ds:Signature" minOccurs="0"/> <element ref="saml:Subject" minOccurs="0"/> <element ref="saml:Conditions" minOccurs="0"/> <element ref="saml:Advice" minOccurs="0"/> <choice minOccurs="0" maxOccurs="unbounded"> <element ref="saml:Statement"/> <element ref="saml:AuthnStatement"/> <element ref="saml:AuthzDecisionStatement"/> <element ref="saml:AttributeStatement"/> </choice> </sequence> <attribute name="Version" type="string" use="required"/> <attribute name="ID" type="ID" use="required"/> <attribute name="IssueInstant" type="dateTime" use="required"/> </complexType>

今までの説明を踏まえると上記の要素及び要素型定義はスラスラ読めると思います。この中でchoiceというものだけを知らないですよね。

choice要素は「子要素の任意の1ついずれかを指定個」を表す要素です。つまり今回で言えばsaml:Statementsaml:AuthnStatementsaml:AuthzDecisionStatementsaml:AttributeStatementのいずれか、ということです。
属性にあるminOccursmaxOccursは前述のとおりです。maxOccursunboundedが指定されていますが、これは上限は無いですよ、という意味であるため、今回は4要素いずれかが0以上続きますよ、という意味になります。

例えば下記のような定義いずれもAssertion型として定義通りのものとなります。

任意の子要素1つも存在しない.xml
<Assertion Version="2.0" ID="ID" IssuInstant="20230222T012345+0900"> <saml:Issuer>...</saml:Issuer> </Assertion>
Statementが1つxml
<Assertion Version="2.0" ID="ID" IssuInstant="20230222T012345+0900"> <saml:Issuer>...</saml:Issuer> <saml:Statement>...</saml:Statement> </Assertion>
AuthnStatementが3つxml
<Assertion Version="2.0" ID="ID" IssuInstant="20230222T012345+0900"> <saml:Issuer>...</saml:Issuer> <saml:AuthnStatement>...</saml:AuthnStatement> <saml:AuthnStatement>...</saml:AuthnStatement> <saml:AuthnStatement>...</saml:AuthnStatement> </Assertion>

違う種類の項目が複数存在するとエラーになるように思いますが、実際にはこれは許可されます。

StatementとAuthnStatementが1つづつxml
<Assertion Version="2.0" ID="ID" IssuInstant="20230222T012345+0900"> <saml:Issuer>...</saml:Issuer> <saml:Statement>...</saml:AuthnStatement> <saml:AuthnStatement>...</saml:AuthnStatement> </Assertion>