Slim3、GAE/JでGoogleアカウントの認証 その2

前のエントリーで色々ご指摘いただいたので試してみました。
Googleアカウントを使ってGAE/Jアプリのログイン処理の実装 - ありの日記

独自の認証フィルターを作って認証をかける

debit-credit-monkeyさんからの提案。独自のサーブレットフィルターを作って認証させたくないやつ(index.jspとか)は認証させないようにして、それ以外は認証させるという方法。
まず、javax.servlet.Filterを実装したAuthFilterクラスを作成し、web.xmlに登録。init-paramで認証を除外したいURLを渡せるようにする。
まず、AuthFilterを作る。

package slim3.it;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

public class AuthFilter implements Filter {

    private String[] excludes;
    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        UserService userService = UserServiceFactory.getUserService();
        String thisURL = ((HttpServletRequest) request).getRequestURI();
        if (((HttpServletRequest) request).getUserPrincipal() == null) {
            if (!isExcludePath(thisURL)) {
                // × 除外したURLのアクセスの場合、ログイン画面にリダイレクトする[このコメントは間違い]
                // ○ 除外したURL以外へのアクセスの場合、ログイン画面にリダイレクトする
                ((HttpServletResponse) response).sendRedirect(userService
                  .createLoginURL(thisURL));
                return;
            }
        }
        chain.doFilter(request, response);
    }
    /**
     * リクエストされたURLが除外対象か判断する。
     * 除外対象の場合trueを返す
     * @param thisURL
     * @return
     */
    private boolean isExcludePath(String thisURL) {
        String[] excludes = this.excludes;
        for (String path : excludes) {
            // 除外対象パスの最後が「*」の場合、indexOfで含まれるか確認
            if (path.indexOf("*") == path.length() - 1) {
                if (thisURL.indexOf(path.substring(0, path.length() - 2)) >= 0) {
                    return true;
                }
            } else {
                // 上記以外は、完全一致
                if (thisURL.equals(path)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void init(FilterConfig config) throws ServletException {
        String exclude = config.getInitParameter("exclude");
        if (exclude == null) return;
        this.excludes = exclude.split(",");
    }
}

そして、web.xmlにこのフィルターを登録。init-paramに除外したいURLを指定。

	<filter>
		<filter-name>authFilter</filter-name>
		<filter-class>slim3.it.AuthFilter</filter-class>
		<init-param>
			<param-name>exclude</param-name>
			<param-value>/index.jsp,/css*,/_ah/login*</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>authFilter</filter-name>
		<url-pattern>/*</url-pattern>
		<dispatcher>REQUEST</dispatcher>
		<dispatcher>FORWARD</dispatcher>
		<dispatcher>INCLUDE</dispatcher>
		<dispatcher>ERROR</dispatcher>
	</filter-mapping>

ここでは、「index.jsp」と「/css*」で始まるパスと、「/_ah/login*」で始まるパスを認証除外対象としている。ちなみに「/_ah/login*」はローカル環境でシミュレートされるGoogleアカウントのログイン画面のURL。実際のサーバにデプロイしたらログイン画面は違うサイトに飛ばされるからその場合は関係ないかな。
とりあえずこのコードで動くけど、ちゃんとテストしてないしこんな実装でいいのかも不明なので自己責任でお願いします(’◇’)

/memberで始まるURLに対してのみ認証をかける。

web.xml側の設定はそのまま以下のようにすればよい。

<security-constraint>
	<web-resource-collection>
		<url-pattern>/member/*</url-pattern>
	</web-resource-collection>
	<auth-constraint>
		<role-name>*</role-name>
	</auth-constraint>
</security-constraint>

そして、ここからひがさんに教えてもらった方法。Slim3で使用するコントローラにプレフィックスをつけることができるらしいので、パッケージを以下のように変更。

slim3.it.controller.todo
↓
slim3.it.controller.member.todo

memberってのが新たに付け加えたところ。そして、その下のtodoパッケージ内にEditController.javaがあり、これにアクセスする時のURLはこうなる。

http://ホスト名/member/todo/edit

なので、認証をかけたいコントローラたちは「slim3.it.controller.member.〜」って感じで追加してゆけばよい。(この例では会員が使うであろう機能をmemberパッケージ以下に押し込むことを想定しています)
ちなみにJSPの場所もwar/member/todoとする必要がある。さらっと。

使い方としてはこんな感じにすることの方が多いかもね。
ユーザ側の画面や管理者側の画面があるアプリだとこの例のように、memberとか、adminとかってパッケージ分けて作ったりしそうだね。