GAE/Jの認証をタイムアウトさせる

先のログで、「GAE/Jでセッション・タイムアウトを定義(web.xml)しただけでは、再認証をさせることができない」と書いた。

ここのところはちょっと混乱があって、再認証機構はセッション・タイムアウトとは基本的に関係ない。フォーム認証のケース(GAE/Jの認証も一種のフォーム認証といえる)では、セッションオブジェクトがExpireしたかどうか、が一つの「再認証のタイミング」を提供するだけである。
基本認証の場合、再認証は、HTTPサーバーのコネクション・タイムアウトによって提供される。「HTTPサーバーのコネクション・タイムアウト < Webアプリケーションのセッションタイムアウト」として、セッション・オブジェクトの生存期間が切れる前に再認証をさせる、というのが昔からあるやり方。

GAE/Jは、Webアプリケーションサーバーが単独で動いているようなもの(ローカルサーバーは、Tomcatど同様に8080ポートで立ち上がる)で、appengine-web.xmlが(HTTPサーバーがない分の)不足を一部補っている。

先のログの末尾に、「ACSIDというCookieを消すと再認証がかかる」と書いた。要するにACSID(アクセスID)で認証済みか否かの判断をしている。このCookieの生存期間は(デフォルトでは)1日に設定される。このログでは一つの方法として、「ACSID Cookieを上書き」してタイムアウト時間を調整してみる。

以下の作業は、GAE/JのインストールされたGanymede(Eclipse3.4)で行った。

BasicAction.javaの変更

Actionを定義する基底クラスを以下のようにする。以下のサンプルでは、initメソッドの中で「5分でタイムアウト」という設定をハード・コードをしているが、実際には、「外だし」にするのが普通だろう。
また、テンプレート・メソッドパターンを使った規定クラスなので、可視性修飾子などを変更して、形式も整えてみた。

package test;

import java.io.IOException;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

public abstract class BaseAction extends Action {

    public ActionForward execute(
    	ActionMapping mapping,
        ActionForm form,
        HttpServletRequest request,
        HttpServletResponse response) throws IOException
    {
    	init(mapping, form, request, response);
    	ActionForward ac = exec(mapping, form, request, response);
    	end(mapping, form, request, response);
    	return ac;
    }

    protected void init(
    		ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response) throws IOException{
    	
    	// Access ID Cookieの生存時間を設定する。
    	Cookie cookie[] = request.getCookies();
    	if (cookie != null){
    	   for (int i = 0 ; i < cookie.length ; i++){
    	      if (cookie[i].getName().equalsIgnoreCase("ACSID")){
    	    	  // 秒単位で指定
    	    	  cookie[i].setMaxAge(60 * 5);
    	    	  // cookieの上書き
    	    	  response.addCookie(cookie[i]);
    	      }
    	   }
    	}

    	UserService userService = UserServiceFactory.getUserService();
    	String thisURL = request.getRequestURI();
    	
    	if (request.getUserPrincipal() != null) {
    	} else {
    		response.sendRedirect(userService.createLoginURL(thisURL));
    	}
    }
    
    abstract protected ActionForward exec(
        	ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response);

	protected void end(
		ActionMapping mapping,
        ActionForm form,
        HttpServletRequest request,
        HttpServletResponse response) throws IOException{
	}
}

実行結果

上記のように基底クラスを直してGAE/Jに再デプロイし、ブラウザーからアクセスしてみる。
Cookieを確認すると(下の図)のように有効期限が(5分後に)設定され、この期限が切れると再認証を聞いてくるようになる。

注記

こんな「持って回ったやり方」をせずに、セッションオブジェクトの状況によって再認証をかけるのが楽だとおもう。この場合、BaseAction.javaのinitメソッドは以下のようになる。

    public void init(
    		ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response) throws IOException{
    		
    	UserService userService = UserServiceFactory.getUserService();
    	String thisURL = request.getRequestURI();

        HttpSession session = request.getSession(true);

        // セッション・オブジェクトが新しければ(作成・再作成されたら)ログイン画面にredirect。
        if (!session.isNew()) {
    		response.sendRedirect(userService.createLoginURL(thisURL));
    	}
    }