GAEで特定のUrlに対して、特定のユーザのみにアクセスを許可する場合はapp.yamlで下記のように設定をすればよい。
app.yaml - url: /do/something/ script: something.py login: required
これでGAEアプリのユーザのみが上記Urlへのアクセスが可能となる。ユーザではない、または未ログインのものが上記UrlにアクセスするとGoogleアカウント(またはOpen Id)のログインページへとリダイレクトされる。そこでユーザ名とパスワードを入力してログインすれば上記のUrlへとリダイレクトバックされる。これがブラウザ上の動作であれば問題はないが、プログラム上から認証を必要とするUrlへとアクセスする場合は困る。
認証を必要とするGAEアプリのUrlに対してリクエストを行う場合は下記の手順で行う必要がある。
- ClientLoginサービスにログインしAuthトークンを取得する
- 取得したAuthトークンを利用しGAEアプリにログインしCookieを取得する
- 取得したCookieとともにHttpGETやらHttpPOSTを行う
各項目を順を追って説明する。
ClientLoginサービスにログインしAuthトークンを取得する
string GetAuth() { var request = (HttpWebRequest)WebRequest.Create("http://www.google.com/accounts/ClientLogin"); var content = "Email=test@gmail.com&Passwd=testpass&service=ah&accountType=HOSTED_OR_GOOGLE"; var byteArray = Encoding.UTF8.GetBytes(content); request.ContentLength = byteArray.Length; request.ContentType = "application/x-www-form-urlencoded"; request.Method = "POST"; var dataStream = request.GetRequestStream(); dataStream.Write(byteArray, 0, byteArray.Length); dataStream.Close(); var response = (HttpWebResponse)request.GetResponse(); var stream = response.GetResponseStream(); var reader = new StreamReader(stream); var loginStuff = reader.ReadToEnd(); reader.Close(); var auth = loginStuff.Substring(loginStuff.IndexOf("Auth")).Replace("Auth=", "").TrimEnd('\n'); return auth; }まずhttp://www.google.com/accounts/ClientLoginへと必要な情報をポストする。各項目の詳細な説明は下記を参照して欲しい。
ClientLogin for Installed Applications
今回は最終的にGAEアプリへとログインしたいのでserviceはahとなる。これがカレンダーであればclなどそれぞれに対応したものへと変わる。ログインに成功するとSID、LSIDとともにAuthが返却されるのでAuthのみ取得する。他の二つは不要。
取得したAuthトークンを利用しGAEアプリにログインしCookieを取得する
CookieContainer GetCookies(string auth) { var cookies = new CookieContainer(); var url = string.Format("http://test.appspot.com/_ah/login?auth={0}", System.Web.HttpUtility.UrlEncode(auth)); var request = (HttpWebRequest)WebRequest.Create(url); request.AllowAutoRedirect = false; request.CookieContainer = cookies; request.ContentType = "application/x-www-form-urlencoded"; request.Method = "GET"; var response = (HttpWebResponse)request.GetResponse(); var stream = response.GetResponseStream(); var reader = new StreamReader(stream); var result = reader.ReadToEnd(); reader.Close(); return cookies; }先ほど取得したAuthトークンを使用しGAEアプリへとログインする。実際にはhttp://test.appspot.comの部分をアクセスしたいGAEアプリのUrlへと変更して欲しい。ログインに成功するとACSIDというクッキーが返却されているはずだ。余談ながらGAEアプリへのログイン後、POSTではなくGETでよい場合は、下記のようにリクエストすれば指定のUrlへとリダイレクトされる。
http://test.appspot.com/_ah/login?auth={0}&continue=http://test.appspot.com/do/something/
ただ今回はPOSTでリクエストしたいのでAllowAutoRedirect=falseで自動リダイレクトを無効化している。
取得したCookieとともにHttpGETやらHttpPOSTを行う
void PostToGAE() { var auth = GetAuth(); var cookies = GetCookies(auth); var url = string.Format("http://test.appspot.com/do/something/"); var content = "testvalue=test"; var request = (HttpWebRequest)WebRequest.Create(url); request.KeepAlive = false; request.CookieContainer = cookies; var byteArray = Encoding.UTF8.GetBytes(content); request.ContentLength = byteArray.Length; request.ContentType = "application/x-www-form-urlencoded"; request.Method = "POST"; var dataStream = request.GetRequestStream(); dataStream.Write(byteArray, 0, byteArray.Length); dataStream.Close(); HttpWebResponse response = (HttpWebResponse)request.GetResponse(); var stream = response.GetResponseStream(); var reader = new StreamReader(stream); var result = reader.ReadToEnd(); reader.Close(); }取得したCookieをそれ以降のリクエストと一緒に送ってやれば認証されたユーザとしてサーバサイドで認識される。
※見て分かると思うけれど、上記方法は中間でトラフィックを監視している人物がいた場合に、その人物がクッキーを使いまわすだけで簡単になりすましが可能となる。そのため繊細な情報などを扱う場合はhttpsを使用するのが望ましい。httpsを使用した場合は返却されるクッキー名がSACSIDとなる。
仕組みが分かれば至極単純なのが理解できたと思う。上記のコードは動作テスト用に組んだものなので例外処理などまったくの手付かずなので使用される場合は随時手を加えてもらいたい。
余談
私がこの動作テストを行ったときに正常に動作させるまで二日ほどかかってしまった。その要因はサーバサイドのハンドラーをDjango-nonrelで実装していたのだが、POSTリクエストがcsrf protectionに引っかかっていたせいだった。ずっとGAEの認証側の問題だと考えてそちらばかり追っかけていたので、Djangoのほうまで頭が回らなかった。気付いてしまえば単純な問題だけれど、はまるときははまる。仮にDjango-nonrelを使用する場合は忘れずにハンドラーへ@csrf_exemptを付与するようにしてほしい。
下記、はまっているときにstackoverflowへ質問した内容。
Programmatically login to google app engine c#