サーブレットの認証を使わずに@Executeのroles属性を有効に使う方法
SAStrutsで認証を実装したいのですが、お勧めはサーブレットの認証機能を使って認証する方法みたいです。
今回はアプリケーションサーバが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属性を有効に使うことができた。