9

認証と承認を実装するための最良のソリューションを考え出すために、私は一日中グーグルで検索し、ここでさまざまな質問を調べてきました。私は今、解決策の一部を考え出しましたが、誰かがギャップを埋めることができることを望んでいます. 以下にたくさんのテキストがあることは承知していますが、ご容赦ください:O)

バックグラウンド

私は、現在 JSF 2.0、JavaEE 6、JPA、および PostgreSQL データベースを使用している部分的に完成した CRM アプリケーションを継承しました。残念ながら、元々この Web アプリを無限の知恵で構築し始めた人たちは、認証/承認を最後まで残すのが最善であると判断しました。

アプリケーションは基本的に、ビュー、マネージド Bean、および DAO の 3 つのレイヤーに分割されます。これは、ビジネス ロジック、検証、およびナビゲーション ロジックのすべてが含まれているマネージド Bean が特に「太い」ことを意味します。

認証/承認の要件

  1. PostgreSQL データベースに保存されている資格情報に対して検証するフォーム ベースの認証。
  2. 一般に (匿名ユーザーが) アクセスできる唯一のページは、ログイン ページです。
  3. ユーザーの役割に基づいて、アプリケーションの特定の領域へのアクセスを防止する必要があります。たとえば、「管理者」ロールを持つユーザーのみが、ユーザーの作成/編集ページにアクセスできる必要があります。
  4. また、ページの特定の領域へのアクセスを制限できるようにする必要もあります。たとえば、「営業担当者」の役割を持つユーザーは顧客の詳細を表示できる必要がありますが、保存/編集ボタンはユーザーが「顧客サービス」の役割を持っている場合にのみ表示する必要があります。

私がいるところ

私が最初に計画していることは、 JAAS とサーブレット 3.0 ログインの例を使用したこのユーザー認証と承認に従うことです。これで、最初の 3 つの要件を満たすことができると思います。

ページの保存ボタンなどを表示/非表示にするために、この SO answerで説明されている手法を使用できます。これにより、要件 4 は部分的に解決されますが、アクション メソッドやマネージド Bean 自体を保護する必要があると思います。たとえば、カスタマー Bean の save() メソッドに注釈または何かを追加して、「カスタマー サービス」ロールを持つユーザーのみが呼び出しできるようにしたいと考えています。ここで問題が発生し始めます。 .

ビューで提案していることと同様のことを行い、facesContextを使用して現在のユーザーが「役割を果たしている」かどうかを確認するのが1つのオプションだと思います。コードが乱雑になり、代わりに注釈を使用したいので、私はこれに熱心ではありません。ただし、このルートをたどった場合、http 403 ステータスを返すにはどうすればよいでしょうか?

javax.annotation.security.* アノテーションは、アプリケーションの領域へのアクセスを宣言的に定義するのに適しているようですが、私が理解している限り、それらは EJB にのみ追加できます。これは、すべてのビジネス ロジックを、現在存在するマネージド Bean から新しい EJB に移動する必要があることを意味します。これには、ビジネスロジックを独自のクラスセット (デリゲート、サービス、またはそれらを呼び出すために選択したもの) に分離するという追加の利点があると思います。これは非常に大規模なリファクタリングになりますが、単体テストや統合テストの欠如によって支援されることはありません。アクセス制御の責任がこの新しいサービス レベルにあるのかどうかもわかりません。管理対象の Bean にあるはずだと思います。

その他の代替手段

調査中に、多くの人が Spring や Seam などのフレームワークについて言及しているのを見つけました。Seam の経験は限られています。Seam はこのプロジェクトにぴったりだったと思います。思い出す限りでは、私が抱えている承認の問題は解決すると思いますが、今すぐ導入するには遅すぎると思います。 .

いろいろなところでシロが言及されているのも見ました。10 分間のチュートリアルを見たところ、これは特にDeluan Quintao の taglibと組み合わせるとぴったりのように思えましたが、JSF Web アプリと統合する方法のチュートリアルや例を見つけることができませんでした。

私が驚くほど頻繁に遭遇するもう 1 つの選択肢は、カスタム ソリューションを実装することです。

概要

要約すると、認証と承認の実装に関して正しい道を進んでいるかどうか、および個々のメソッドやマネージド Bean (または少なくとも委任先のコード) および/または HTTP ステータス 403 を手動で返す方法。

4

2 に答える 2

3

多くの調査を行った結果、Tomcat のようなサーブレット コンテナーではなく、Java EE 仕様を完全に実装するアプリケーション サーバーにデプロイすることで、まずアプリケーションの要件が改善されるという結論に達しました。私が取り組んでいるプロジェクトはMavenを使用しているため、ここで重要なことは依存関係を正しく設定することでした.これは簡単ではなく、かなりのグーグル検索と試行錯誤が必要でした.もっと科学的なアプローチがあると確信しています.取ることができました。

次に、アプリケーションがデータベースと適切に通信できるように mysql モジュールを作成し、DAO を作成するために実装されていたファクトリを削除して、代わりにそれらを EJB に変換する必要がありました。また、hibernate.cfg.xml を更新して追加したデータソースを参照し、persistence.xml を更新してトランザクション タイプを JTA に設定し、JTA データ ソースも参照する必要がありました。他の唯一の問題は、Open Session In View パターンが使用されていたため、ビューでエンティティにアクセスしたときに休止状態の遅延初期化エラーが発生したことです。これを回避するために、この回答の下部に示されているようにフィルターを再実装しました。これは、うまくいけばこの領域をリファクタリングしてフィルターの必要性を取り除く前に、物事を再び機能させるための一時的な手段と考えています.

JBoss への移行には 1 日強しかかかりませんでした。Java EE と Maven の経験があれば、もっと迅速に移行できたはずです。その時点で、本質的に私が取ろうとしていた方向であるソリューションを一緒にハックしようとするのではなく、Seam 3 セキュリティをプロジェクトにドロップしてそれを利用できる良い位置にいます。Seam 3 の優れた点は、(Seam 2 のように) フレームワーク全体を追加する必要がなく、使用するモジュールをある程度選択できることです。他の多くのモジュールも同様に役立つと思いますが、とりわけビューパターンで開いているセッションを取り除くのに役立ちます.

Seam の使用に関して私が懸念したことの 1 つは、DeltaSpikeについて知らされたことです。これは、おそらく seam を置き換えるように思われ、これ以上のバージョンの seam の計画はありません。seam はまだサポートされており、DeltaSpike が seam 3 と同じくらいの時間がかかる場合は、seam 3 を使用するのがかなり安全であると判断しました。

この移行について適切な詳細を説明する適切なブログ記事を書きたいと思います。

public class OSVRequestFilter implements Filter {

    private static final String UserTransaction = "java:comp/UserTransaction";

    private static Logger logger = LoggerFactory.getLogger(EntityManagerRequestFilter.class);

    public void init(FilterConfig config) throws ServletException {
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            doFilter(request, response, chain, getUserTransaction());
        }
    }

    private UserTransaction getUserTransaction() throws ServletException {
        try {
            Context ctx = new InitialContext();
            return (UserTransaction)PortableRemoteObject.narrow(ctx.lookup(UserTransaction), UserTransaction.class);
        }
        catch (NamingException ex) {
            logger.error("Failed to get " + UserTransaction, ex);
            throw new ServletException(ex);
        }
    }

    private void doFilter(ServletRequest request, ServletResponse response, FilterChain chain, UserTransaction utx) throws IOException, ServletException {
        try {
            utx.begin();

            chain.doFilter(request, response);

            if (utx.getStatus() == Status.STATUS_ACTIVE)
                utx.commit();
            else 
                utx.rollback();
        }
        catch (ServletException ex) {
            onError(utx);
            throw ex;
        }
        catch (IOException ex) {
            onError(utx);
            throw ex;
        }
        catch (RuntimeException ex) {
            onError(utx);
            throw ex;
        }
        catch (Throwable ex){
            onError(utx);
            throw new ServletException(ex);
        }
    }

    private void onError(UserTransaction utx) throws IOException, ServletException {
        try {
            if ((utx != null) && (utx.getStatus() == Status.STATUS_ACTIVE))
                utx.rollback();
        }
        catch (Throwable e1) {
            logger.error("Cannot rollback transaction", e1);
        }
    }

    public void destroy() {
    }
}
于 2012-05-16T16:48:15.747 に答える