サーブレットの認証を使わずに@Executeのroles属性を有効に使う方法

SAStrutsで認証を実装したいのですが、お勧めはサーブレットの認証機能を使って認証する方法みたいです。


認証 - cypher256's blog


今回はアプリケーションサーバTomcatで、ユーザ情報はDBで管理したいのでJDBCRealmを使ってやろうかなと思っていました。

ところが、ログインの認証機能が複雑な仕様になってしまって、JDBCRealmでは実現できないことがわかりました。

何とか、@Executeのroles属性でメソッド単位に認証をかけたいと思って、調べました。

調査

そもそも、どのようにrolse属性に設定したロールで認証がかかっているのか?

また、会社にあったStruts in アクションに書いてあるんじゃないかと思って、ActionMappingのrolse属性を見てみました。

以下、抜粋。

セキュリティチェックは、RequestProcessorのprocessRoles()メソッドで処理されます。RequestProcessorをサブクラス化することにより、アプリケーションベースのセキュリティでもrolesプロパティを使用できます。

何かできそうだ!!

さらに、RequestProcessor#processRoles()を見てみると…。

String roles[] = mapping.getRoleNames();
if ((roles == null) || (roles.length < 1)) {
    return (true);
}

// Check the current user against the list of required roles
for (int i = 0; i < roles.length; i++) {
    if (request.isUserInRole(roles[i])) {
        if (log.isDebugEnabled()) {
            log.debug(" User '" + request.getRemoteUser() +
                "' has role '" + roles[i] + "', granting access");
        }
        return (true);
    }
}

mapping.getRoleNames()でロール属性を取得して、request.isUserInRole()で検証している。


ということは…。


isUserInRole()メソッドをオーバーライドすればできる!?

実装

サーブレット仕様の認証を使わずにgetRemoteUserやisUserInRoleを使う。 - NullPointer's


まるっきり、実装方法が紹介されてました。

前提条件として、ユーザー情報*1はログイン処理後、セッションに保持する。


そして仕組みは


1.HttpServletRequestWrapperを継承して、isUserInRole()メソッドでセッションに保存してあるユーザー情報のロールを戻り値にする
2.フィルターで独自実装のHttpServletRequestを呼び出す。


という感じ。

AuthHttpServletRequest.java
package jp.co.suusuke.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;

public class AuthHttpServletRequest extends HttpServletRequestWrapper {

	/**
	 * @param request
	 */
	public AuthHttpServletRequest(HttpServletRequest request) {
		super(request);
	}

	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServletRequestWrapper#getRemoteUser()
	 */
	public String getRemoteUser() {
		HttpSession session = this.getSession(false);
		if (session == null) {
			return null;
		}
		return (String) session.getAttribute("auth.user");
	}

	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServletRequestWrapper#isUserInRole(java.lang.String)
	 */
	public boolean isUserInRole(String str) {
		if (str == null) {
			return false;
		}
		HttpSession session = this.getSession(false);
		if (session == null) {
			return false;
		}

		Object role = session.getAttribute("auth.role");
		return (str.equals(role));
	}

}
AuthFilter.java
package jp.co.suusuke.servlet.filter;

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 jp.co.suusuke.servlet.AuthHttpServletRequest;

public class AuthFilter implements Filter {

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.Filter#destroy()
	 */
	@Override
	public void destroy() {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
	 * javax.servlet.ServletResponse, javax.servlet.FilterChain)
	 */
	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		chain.doFilter(
				new AuthHttpServletRequest((HttpServletRequest) request),
				response);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	@Override
	public void init(FilterConfig arg0) throws ServletException {

	}

}
web.xml
<filter>
    <filter-name>authFilter</filter-name>
    <filter-class>jp.co.suusuke.servlet.filter.AuthFilter</filter-class>
</filter>        

...


<filter-mapping>
    <filter-name>authFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

フィルターの順番は先頭*2に設定したんだけど、いいのかな?

後で、デバッグしてみよう。


とりあえず、JDBCRealm使わないでroles属性を有効に使うことができた。

*1:idやロールetc...

*2:encodingfilterの前