cypher256's blog

Pleiades とか作った

JSP で Action に指定した roles を使う

SAStruts では Servlet コンテナの認証を使った場合、アクションの roles 属性を指定することにより、アクセス制御が可能です。これでセキュリティは守られます。でも実際のところ、その権限によって、画面にメニューやリンク、ボタンの表示/非表示を切り替える必要があるのがほとんどではないでしょうか? JSP でベタで権限判定をしても良いのですが、せっかく、アクションに roles 属性を指定しているなら、そこでロール定義は管理し、JSP ではそのアクション名とメソッド名で判定したいところです。こんな感じ。

<!-- show ファンクションで表示/非表示判定 -->
<c:if test='${x:show("hoge/index")}'>
    HogeAction の index 権限がある人のみ表示
</c:if>
// ファンクション・クラス
public class Functions {

    // 指定されたアクション/メソッドが現在のユーザに権限があるか判定
    public static boolean isShow(String actionMethod) {
        ActionRolesMap actionRolesMap = ActionRolesRegister.getActionRolesMap();
        List<String> roleList = actionRolesMap.get(actionMethod);
        if (roleList == null) {
            throw new IllegalArgumentException("指定されたアクションメソッド " + actionMethod
                + " は存在しません。" + actionRolesMap);
        }
        if (roleList.size() == 0) {
            Exception e = new IllegalArgumentException("指定されたアクションメソッド "
                + actionMethod + " は roles が指定されていません。" + actionRolesMap);
            return false;
        }
        User user = SingletonS2Container.getComponent(User.class);
        return roleList.contains(user.roleId);
    }
}

TLD は適当に作っておきます。あと、上記の ActionRolesMap をどこで作っておくかなのですが、最初は ActionCustomizer でやろうと思ったのですが、SAStruts の環境名が ct の場合、最初にすべてのアクションをロードしないため、別アクションの roles が参照できません。そこで結局また ComponentAutoRegister を拡張。こんな感じ。

// アクション・ロール・レジスター・クラス
public class ActionRolesRegister extends ComponentAutoRegister {

    private static String KEY = ActionRolesRegister.class.getName();

    // コンストラクタ
    public ActionRolesRegister(NamingConvention namingConvention) {
        for (String rootPackageName : namingConvention.getRootPackageNames()) {
            addClassPattern(rootPackageName + ".action", ".*Action");
        }
        addReferenceClass(getClass());
    }

    // @Execute の roles 属性からアクション・ロール・マップを作成
    @Override
    protected void register(String className) {
        Class<?> clazz = ClassLoaderUtil.loadClass(ResourceUtil.getClassLoader(), className);
        ServletContext application = ServletContextUtil.getServletContext();
        ActionRolesMap actionRolesMap = (ActionRolesMap) application.getAttribute(KEY);
        if (actionRolesMap == null) {
            actionRolesMap = new ActionRolesMap();
            application.setAttribute(KEY, actionRolesMap);
        }
        for (Method method : clazz.getMethods()) {
            Execute anno = method.getAnnotation(Execute.class);
            if (anno == null) {
                continue;
            }
            String[] roles = anno.roles().split("\\s*,\\s*");
            List<String> roleList = new LinkedList<String>();
            for (String role : roles) {
                if (StringUtils.isNotBlank(role)) {
                    roleList.add(role);
                }
            }
            String shortName = ClassUtil.getShortClassName(className);
            String actionName = shortName.replaceFirst("Action$", "");
            String actionMethod = actionName + "/" + method.getName();
            actionMethod = StringUtils.uncapitalize(actionMethod);
            actionRolesMap.put(actionMethod, roleList);
        }
    }

    // アクション・ロール・マップの取得
    public static ActionRolesMap getActionRolesMap() {
        ServletContext application = ServletContextUtil.getServletContext();
        return (ActionRolesMap) application.getAttribute(KEY);
    }

    // アクション・ロール・マップ
    public static class ActionRolesMap extends HashMap<String, List<String>> {
    }
}


あとは適当に app.dicon とかに。

<!-- アクション・ロール・マップのコンポーネント登録 -->
<component class="hoge.framework.internal.ActionRolesRegister"/>


・・・というか、書いてて気づきましたが、Functions.isShow で普通にアクション・クラスの Execute アノテーション取り出して、判定すればいいだけでした。それだと ActionRolesRegister なんてものは、いらないです。でも、先読みにより実行効率があがるかもしれないので良いということにしておきます。