第1回、第2回、第3回では、OpenID Authentication 1.1 を読んで仕組みを学びましたが、今回は Modes を見ながら実際にColdFusion 7でコードを書いきました。
入力URLのページからopenid.serverとopenid.delegate(あれば)を抽出し、次にassociate、続いてcheckid_setup、最後にcheck_authenticationの処理をするコード。
まずはURLページから必要情報を抽出:
<cfset OpenID = StructNew() />
<cfset OpenID.Identity = form.openid_url />
<!--- 入力URLのページ情報を取得 --->
<cfhttp method="get" url="#OpenID.Identity#"></cfhttp>
<cfset pageContent = cfhttp.Filecontent />
<!--- head部分のみを取り出し、XMLオブジェクトに変換する --->
<cfset IsHeader = REFindNoCase("<head>.*</head>",pageContent,1,true) />
<cfif IsHeader.pos[1] GT 0>
<cfset textHeader = Mid(pageContent,IsHeader.pos[1],IsHeader.len[1]) />
<cfset xmlHeader = XmlParse(textHeader) />
<!--- linkタグのみを抽出する --->
<cfset linkElements = XmlSearch(xmlHeader, "/head/link")>
<!--- linkタグ配列をループし、openid.serverとopenid.delegate情報を取得 --->
<cfloop index="i" from="1" to="#ArrayLen(linkElements)#">
<cfif linkElements[i].XmlAttributes.rel IS "openid.server">
<cfset OpenID.Server = linkElements[i].XmlAttributes.href>
</cfif>
<cfif linkElements[i].XmlAttributes.rel IS "openid.delegate">
<cfset OpenID.Delegate = linkElements[i].XmlAttributes.href>
</cfif>
</cfloop>
</cfif>
次にassociate:
<!--- HTTP通信 --->
<cfhttp method="post" url="#OpenID.Server#">
<cfhttpparam type="formfield" name="openid.mode" value="associate" />
<cfhttpparam type="formfield" name="openid.assoc_type" value="HMAC-SHA1" />
<cfhttpparam type="formfield" name="openid.session_type" value="" />
</cfhttp>
<!--- レスポンス --->
<cfset responses = ListToArray(cfhttp.FileContent, "#chr(13)##chr(10)#")>
<cfset Associate = StructNew() />
<cfloop index="i" from="1" to="#ArrayLen(responses)#">
<cfset Associate["#ListGetAt(responses[i], 1, ":")#"] = ListGetAt(responses[i], 2, ":") />
</cfloop>
その後、checkid_setup:
<cfset OpenID.Nonce = CreateUUID() />
<!--- IdPに送るデータの整理 --->
<cfset Setting = StructNew() />
<cfset Setting.mode = "checkid_setup" />
<cfif IsDefined("OpenID.Delegate")>
<cfset Setting.user_identity = OpenID.Identity />
<cfset Setting.identity = OpenID.Delegate />
<cfelse>
<cfset Setting.identity = OpenID.Identity />
</cfif>
<cfset Setting.assoc_handle = Associate.assoc_handle />
<cfset Setting.return_to = "http://#CGI.HTTP_HOST##CGI.SCRIPT_NAME#?nonce=#OpenId.nonce#" />
<cfset Setting.trust_root = "http://#CGI.HTTP_HOST#" />
<!--- クエリストリング --->
<cfset query_string ="">
<cfloop collection="#Setting#" item="keys">
<cfset query_string = "#IIF(Len(query_string) EQ 0, De("?"), De("#query_string#&"))#openid.#LCase(keys)#=#UrlEncodedFormat(StructFind(Setting, keys), "utf-8")#" />
</cfloop>
<!--- ページ遷移 --->
<cflocation addtoken="no" url="#OpenID.Server##query_string#" />
<cfif IsDefined("url.nonce")>
<a href="test.cfm">test.cfm</a>
<cfif IsDefined("url.openid.mode") AND url.openid.mode IS "id_res">
<!--- HTTP通信 --->
<cfhttp method="post" url="#session.OpenID.Server#">
<cfhttpparam type="formfield" name="openid.mode" value="check_authentication" />
<cfhttpparam type="formfield" name="openid.assoc_handle" value="#url.openid.assoc_handle#" />
<cfhttpparam type="formfield" name="openid.sig" value="#url.openid.sig#" />
<cfhttpparam type="formfield" name="openid.signed" value="#url.openid.signed#" />
<!--- url.openid.signedに含まれるmode以外の値 --->
<cfloop list="#url.openid.signed#" index="columns">
<cfif columns IS NOT "mode">
<cfhttpparam type="formfield" name="openid.#LCase(columns)#" value="#StructFind(url, "openid.#columns#")#" />
</cfif>
</cfloop>
</cfhttp>
<!--- サーバからの返答 --->
<cfoutput>#cfhttp.FileContent#</cfoutput>
</cfif>
<cfabort>
</cfif>
はじめはcfhttpを使ってcheck_immediateを試みましたが、IdP側から
Fatal error: Call to a member function whichEncoding() on a non-object in /home/dev/openid/Library/Auth/OpenID/Server.php on line 1267
というエラーが戻ってくるのであきらめました。 これは、cfhttpparamで設定した値の name が自動的に大文字になって送られてしまうからのようです。
check_setupを使うことにして、cfhttpではなくcflocationでIdPへページ遷移するようにしました。nonceはCreateUUID関数で生成して、IdP移動前にアクセス情報とともにセッション情報に格納するようにします。
例外処理がまだ実装されていないのと、変数名がごちゃごちゃなのを整理して、cfloginと組み合わせられないかと模索しつつ次回へ続きます。
タグ: Authentication, code, ColdFusion, OpenID, Weight Watch App

