SAFluteのログイン周り

SAFluteの特徴の一つです。

概要

組み込みのログインロジック

ログインのロジックが組み込まれています。

これを必ず使わなければならないわけではないですが、カスタマイズも可能なのでオススメです。

ログインチェックも組み込み

TypicalBaseAction の中でログインチェックが組み込まれています。 組み込みのログインロジックを使って自動的にログインチェックがかかるようになっています。

ログインのAction

ログインのActionは、以下のような実装となります。

e.g. ログインのActionの実装 @Java
@Execute(validator = false)
public String index() { // ログイン画面を開く
    DocksideUserBean userBean = getUserBean(); // セッションのユーザー情報を取得
    if (userBean.isLogin()) { // すでにログイン済みなら
        return redirect(MypageIndexAction.class); // マイページへ
    }
    return path_Login_LoginJsp; // ログイン画面を描画
}

@DocksidePerformLogin // ログイン処理に付けるアノテーション
@Execute(validator=true, input = path_Login_LoginJsp)
public String doLogin() { // ログインボタン押した
    // DB検索して、セッションにユーザー情報を入れて、
    // 必要に応じてクッキーにも入れて、ログイン履歴を残して、が一行でおしまい
    memberLoginLogic.login(loginForm.email, loginForm.password, loginForm.isAutoLoginTrue());
    return redirect(MypageIndexAction.class); // デフォルトはマイページへ
}

ログインの処理は、LoginLogic に集約されています。

ログイン必須画面にログインしていない状態でアクセスしたときのログインであれば、ログイン後はアクセスしようとしたURLに飛びます。 ログインのActionで MypageIndexAction にリダイレクトしているのは、通常のログインであれば遷移する先(デフォルト)と捉えるとよいでしょう。

ログインチェック

ログイン必須画面にログインしていない状態でのアクセスを防ぐ仕組みは、ActionCallback の GodHand で行われています。

e.g. ログインのActionの実装 @Java
@Override
public String godHandActionPrologue(final ActionExecuteMeta executeMeta) { // fixed process
    arrangePreparedAccessContext(executeMeta);

    // should be after access-context (using access context's info)
    arrangeCallbackContext(executeMeta);

    // should be after access-context (may have update)
    final String redirectTo = handleLoginRequiredCheck(executeMeta);
    if (redirectTo != null) {
        return redirectTo;
    }
    return null;
}

...

/**
 * Handle the login required check for the requested action.
 * @param executeMeta The meta of action execute to determine required action. (NotNull)
 * @return The forward path, basically for login-redirect. (NullAllowed)
 */
protected String handleLoginRequiredCheck(ActionExecuteMeta executeMeta) {
    final LoginHandlingResource resource = createLogingHandlingResource(executeMeta);
    final String forwardTo = loginManager.checkLoginRequired(resource);
    if (forwardTo != null && executeMeta.isApiAction()) {
        return dispatchApiLoginRequiredFailure(executeMeta);
    }
    return forwardTo;
}

すべての Action にて、loginManager の checkLoginRequired() が呼ばれます。

ここでの loginManager は、LoginLogic のことです。LoginLogic は LoginManager インターフェースを implements しているので、フレームワークの中では、LoginManagerとしてDIして利用しています。

ログインのLogic

ログインのAction や ActionCallback でのログインチェックから呼び出される、ログインの Logic が存在します。こちらに、認証処理やセッションの設定、クッリーの設定、ログイン履歴など、ログインに必要な処理が詰め込まれています。

Logicクラスの階層構造

それぞれのWebプロジェクトに LoginLogic があり、例えば Maihama プロジェクトの Dockside Webアプリであれば、DocksideLoginLogic となります。

e.g. LoginLogic の階層構造、Maihamaプロジェクトなら @Structure
LoginManager // フレームワーク用インターフェース
 |-TypicalLoginBaseLogic // フレームワーク提供の典型的なベースクラス
    |-MaihamaLoginBaseLogic // プロジェクト共通のベースクラス
       |-DocksideLoginLogic // Webプロジェクトのロジッククラス (スマートデプロイ対象)

SAFluteが提供する TypicalLoginBaseLogic に、おおよそのロジックが組み込まれていて、それぞれのプロジェクトにフィットさせる部分を、 サブクラスで実装していくような形です。

なので、DocksideLoginLogic にはあまりロジックはたくさんありません。 どのテーブルにアクセスするのか?どこに履歴を残すのか?など、ピンポイントで必要な情報をスーパークラスに渡してあげるだけになります。 もちろん、とある処理だけをオーバーライドして独自の挙動にすることもできます(権限チェックなどはそのように)。

Logicクラスのメソッド

大きくは以下の二つ:

login()
ログイン処理を行う (ログインのActionから呼ばれる想定)
checkLoginRequired()
ログイン必須チェックを行う (ActionCallbackから呼ばれる想定)

実際には、引数が違うだけのオーバーロードのメソッドや、多種多様なログイン処理を行うメソッド、 ログイン処理やチェック処理の一部だけを行う部品的なメソッドなどがあります。

メソッドの一覧が見たければ、LoginManager のソースを読むとよいでしょう。

詳しい処理に関しては、TypicalLoginBaseLogic を読んでみましょう。

厳密には、TypicalLoginBaseLogic を使わなければならないわけではありません。基本的に Typical と付いているものは、もし現場にフィットするなら使ってみてください、というニュアンスのものです。 ですが、多くの処理をオーバーライドなどで拡張できるようにしているので、よほど特殊でなければフィットさせることができるのではないかと想定しています。

権限チェックの実装

拡張ポイントが用意されている

権限チェックの仕組み自体は、かなり業務に特化する部分なので特に組み込みでの用意はありませんが、 ログインロジックのプロセスの一環として組み込める拡張ポイントが用意されています。

processAuthority()

ログインチェックの処理の中で、ログイン状態のときに必ず呼ばれるメソッドをあります。 そのメソッドの中で権限チェックをすることで、ログインしていてもその画面で必要な権限がないアクセスを弾くことができます。

LoginLogic が継承している TypicalLoginBaseLogic にて、processAuthority() というメソッドが定義されていて、デフォルトでは中身は空っぽです。

e.g. processAuthority()のそのままのコード @Java
/**
 * Process for the authority of the login user. (called in login status)
 * @param resource The resource of login handling to determine. (NotNull)
 * @param userBean The bean of the login user. (NotNull)
 * @return The forward path, basically for authority redirect. (NullAllowed: if null, authority check passed)
 */
protected String processAuthority(LoginHandlingResource resource, USER_BEAN userBean) {
    return null; // no check as default, you can override
}

processAuthority()は、既にログイン済み、もしくは、いま自動ログインしたばかり のときに呼び出されます。

オーバーライドして権限チェック

それぞれの LoginLogic にて、 この processAuthority() をオーバーライドして、権限チェックをするとよいでしょう。 戻り値は、Action の return で戻すものと同じです。

e.g. processAuthority()のそのままのコード @Java
/** {@inheritDoc} */
@Override
protected String processAuthority(LoginHandlingResource resource, USER_BEAN userBean) {
    if (この画面で、この人が権限もっていなければ) {
        sessionManager.saveErrors(DocksideMessages.ERRORS_AUTHORITY_VIOLATION);
        return DocksideJspPath.path_Error_ErrorMessageJsp;
    }
    return null; // 権限ある
}

どの画面がどんな権限?

ここは様々な方法がありますが、一例として、アノテーションに付けるやり方があります。

e.g. LoginRequiredのアノテーションにロールを指定 @Java
@DocksideLoginRequired({ CDef.OperationRole.Sea, CDef.OperationRole.Land })
@Execute(validator = false, urlPattern = "{ikspiari}")
public String index() {
    ...
}

ロールはテーブル区分値として定義しておきます。自動生成された CDef をアノテーションで指定できるようにして、 processAuthority()の中でそのアノテーションの値を取得して、ログインユーザーのロールと照らし合わせてアクセス可能かどうかを判定します。

必ずこのやり方である必要はありません。データベースに画面と必要なロールを紐づけるテーブルを作ってもよいでしょう。 ただ、画面が増えたときにそのテーブルのデータもメンテナンスしてあげないといけないので、アノテーションであればプログラム作成時に自然と権限が付与されるので、 定義し忘れのトラブルは少ないでしょう。