GAE/Jの認証をカスタマイズする
前々回までのサンプルコードを使って、GAE/Jの認証をカスタマイズする(最終回)。
GAE/Jは(今のところ?)認証時に設定できるロール(役割)が
- 一般ユーザー
- Admin(管理)ユーザー
のいずれかとなっている。コンシューマー向けのサイトを少人数で開発・運用するのであれば、これで行けると思うのだが、B2Bのサイトであるとか、大人数でメンテナンスをするサイトになると、もう少しきめ細かい認証方式が欲しくなる。GAE上にワークフロー(業務フロー)みたいなものを作る場合にも、必要になることが多いだろう。ディレクトリー・サービスみたいなものが提供されると、こういうことも可能になるのだろうが、「普通のデータとしてユーザー・マスタを持って、それでコントロールする」という「昔ながらの方法」も敷居が低くて捨てがたい。
今回は、前々回までのサンプルをつかって、以下のようなサンプルを作ってみる。
- GAE/Jに標準で用意されている(簡易な)認証機構をつかう。
- セッション・タイムアウトで再認証をする。
- ユーザーマスタをもって、そこに登録されていないユーザーは認証を通過させない。
ユーザーマスタとしては、先のログ「GAE/JでYUI2.7.0+Struts1.3をつかって、データストアからエンティティを取得してDataTableで表示する」で作成した、AppUserエンティティー(AppUser Kind)を使う。
以下の作業は、GAE/JプラグインがインストールされたGanymede(Eclipse3.4)で行った。
プロジェクトの作成
前々回作成したプロジェクトをコピーして新規のプロジェクトを作成する。
AppUserDao.javaの変更
今回のサンプルでは、AppUser KindにGAE/Jの認証を通過してきた、ユーザー(Emailアドレス)が存在するか確認をする。そのためのメソッドgetAppUserByEmail(String email)を追加する。コードの全文を以下に示しておく。
package test; import java.util.ArrayList; import java.util.List; import javax.jdo.PersistenceManager; import javax.jdo.Query; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyFactory; import test.entities.AppUser; import test.entities.RootTO; /** * AppUserエンティティーのData Access Object * */ public class AppUserDao{ private static final Log log = LogFactory.getLog(AppUserDao.class); /** * コンストラクタ */ public AppUserDao() { } @SuppressWarnings("unchecked") private List<AppUser> loadAppUser(){ List<AppUser> users = new ArrayList<AppUser>(); PersistenceManager pm = PMF.get().getPersistenceManager(); /** * AppUserエンティティーに対するクエリの実行 */ Query query = pm.newQuery(AppUser.class); query.setOrdering("email asc"); try{ List<AppUser> list = (List<AppUser>)query.execute(); for(AppUser usr : list){ users.add(usr); } }finally{ query.closeAll(); pm.close(); } return users; } /* * AppUserのリストを返却する。 */ public List<AppUser> getAll() { return this.loadAppUser(); } /* * AppUserを登録、更新する。 */ public List<AppUser> setAppUser(AppUser usr) { log.debug("setAppUser id : " + usr.getNumber()); if(usr.getNumber().intValue() != -1){ this.updateAppUser(usr); }else{ this.registerAppUser(usr); } return this.loadAppUser(); } /* * AppUserを登録する。 */ private void registerAppUser(AppUser usr) { PersistenceManager pm = PMF.get().getPersistenceManager(); long count = 0; try{ pm.currentTransaction().begin(); RootTO root = pm.getObjectById(RootTO.class, "key"); count = root.getCount()+1; root.setCount(count); pm.currentTransaction().commit(); }finally{ if(pm.currentTransaction().isActive()){ // ロールバック pm.currentTransaction().rollback(); } pm.close(); } if(count > 0){ AppUser storePer = new AppUser(usr.getEmail(), usr.getName(), usr.getAddress(), usr.getRole()); KeyFactory.Builder kb = new KeyFactory.Builder(RootTO.class.getSimpleName(), "key"); kb.addChild(AppUser.class.getSimpleName(), AppUser.class.getSimpleName()+ new Long(count).toString()); Key key = kb.getKey(); storePer.setKey(key); storePer.setNumber(count); pm = PMF.get().getPersistenceManager(); try{ pm.makePersistent(storePer); }finally{ pm.close(); } } return; } /* * AppUserを更新する。 */ private void updateAppUser(AppUser usr) { PersistenceManager pm = PMF.get().getPersistenceManager(); try{ // キーの生成 KeyFactory.Builder kb = new KeyFactory.Builder(RootTO.class.getSimpleName(), "key"); kb.addChild(AppUser.class.getSimpleName(), AppUser.class.getSimpleName()+usr.getNumber().toString()); Key key = kb.getKey(); // オブジェクトの取得 AppUser storePer = pm.getObjectById(AppUser.class, key); storePer.setEmail(usr.getEmail()); storePer.setName(usr.getName()); storePer.setAddress(usr.getAddress()); storePer.setRole(usr.getRole()); }finally{ pm.close(); } return; } /** * AppUserを削除する。 */ public List<AppUser> deletePerson(AppUser usr) { PersistenceManager pm = PMF.get().getPersistenceManager(); try{ // キーの生成 KeyFactory.Builder kb = new KeyFactory.Builder(RootTO.class.getSimpleName(), "key"); kb.addChild(AppUser.class.getSimpleName(), AppUser.class.getSimpleName()+usr.getNumber().toString()); Key key = kb.getKey(); // オブジェクトの取得 AppUser storePer = pm.getObjectById(AppUser.class, key); pm.deletePersistent(storePer); }finally{ pm.close(); } return this.loadAppUser(); } /** * Personを一括削除する。 */ @SuppressWarnings("unchecked") public void deleteAll() { PersistenceManager pm = PMF.get().getPersistenceManager(); Query query = pm.newQuery(AppUser.class); try{ pm.currentTransaction().begin(); List<AppUser> list = (List<AppUser>)query.execute(); pm.deletePersistentAll(list); pm.currentTransaction().commit(); }finally{ if(pm.currentTransaction().isActive()){ // ロールバック pm.currentTransaction().rollback(); } query.closeAll(); pm.close(); } return; } /** * emailでAppUserエンティティーを取得する */ @SuppressWarnings("unchecked") public List<AppUser> getAppUserByEmail(String email) { List<AppUser> users = new ArrayList<AppUser>(); PersistenceManager pm = PMF.get().getPersistenceManager(); /** * AppUserエンティティーに対するクエリの実行 */ Query query = pm.newQuery(AppUser.class); query.setFilter("email == requiredEmail"); query.setOrdering("email asc"); query.declareParameters("String requiredEmail"); try{ List<AppUser> list = (List<AppUser>)query.execute(email); for(AppUser usr : list){ users.add(usr); } }finally{ query.closeAll(); pm.close(); } return users; } }
BaseAction.javaの変更
Actionの基底クラスを以下のように変更する。前々回のサンプルでは、ACSID(アクセスID)というCookieを上書きして再認証をさせたが、今回はセッション・タイムアウトで再認証をさせるようにする。また、GAE/Jの認証を通過したら、AppUsersエンティティーを、Emailアドレスで検索して存在確認をする。この際、存在しなかったら、GAEで用意している認証画面に戻してしまう。
package test; import java.io.IOException; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; 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 test.entities.AppUser; import com.google.appengine.api.users.User; 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); return exec(mapping, form, request, response); } 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); if (session.isNew()) { response.sendRedirect(userService.createLoginURL(thisURL)); } else{ // ログインしたらここに戻る。 User attemptingUser = userService.getCurrentUser(); AppUserDao auo = new AppUserDao(); List<AppUser> users = auo.getAppUserByEmail(attemptingUser.getEmail()); if(users.size()==0) response.sendRedirect(userService.createLoginURL(thisURL)); } } abstract public ActionForward exec( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response); }
web.xml
以下を追加して、セッションタイムアウトを設定する。(以下の例では5分でタイムアウト)
<!-- session configuration --> <session-config> <session-timeout>5</session-timeout> </session-config>
テストとデプロイ
AppUserDao.javaに新しいクエリを作ったので、ローカル環境で実行してIndexを作成する。Indexはwar/WEB-INF/appengine-generated/datastore-indexes-auto.xmlに登録される。そうしたら、GAE/Jにデプロイする。
ブラウザーで、GAE/Jが発行した、JSESSENIDとACSIDの2つのCookieを削除して、ブラウザーを再起動する。URLにアクセスすると、認証を聞いてくるが、メイルの設定などで「ログイン状態を保持する」としていると、ユーザーIDが入った格好で、以下のような画面がでる。
ここで、「別のユーザーとしてログインする」を選ぶと、以下の画面に変わる。AppUserに登録したEmail、しないEmailアドレスでテストする。