2015年9月12日土曜日

Glassfish+Derby+JDBCレルムについて(ソースコード解説)

JDBCレルムについてソースコード内容について概要を記述。
下記GitHubのコードの解説である。
https://github.com/Fufuhu/SimpleAuthentication

NetBeans上でMavenのWebプロジェクトで作成している。


Servletとjspだけで構成されたシンプルな構成になっている。
(Webページディレクトリ直下のindex.jspは無視してもらって構いません。)

■画面遷移について

以下のような形で画面遷移を構成しています。


index.jspがログインページLoginServletにてJDBCレルム認証を実行後、
認証を通過すればtoppage.jsp, 失敗すればerror.jspに遷移する。


■index.jsp
ログインページ。ユーザ名とパスワードの入力したのちに、
ログインボタンクリックで、LoginServletを呼び出し、JDBCレルム
認証を実行することができる。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>ログイン</title>
    </head>
    <body>
        ログイン情報<br/>
        <form method="POST" action="../LoginServlet">
            ユーザ名:<input type="text" name="username"/> <br/>
            パスワード:<input type="password" name="password"/><br/>
            <input type="submit" method="GET" value="ログイン"/>
        </form>
    </body>
</html>



■LoginServlet(一部抜粋)
42行目のrequest.login(username, password);でJDBCレルム認証を実行している。
index.jspで入力されたユーザ名、パスワードの内容に応じてログイン可否を判断している。
loginメソッドで認証ができれば、そのままtoppage.jspへの遷移を続行。(ユーザ認証情報がセッションに含まれるようになる。)できなければServletExceptionがスローされるので、error.jspに遷移する。

package authentication;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author USER
 */
@WebServlet(name = "LoginServlet", urlPatterns = {"/LoginServlet"})
public class LoginServlet extends HttpServlet {

   
    private final String topPage = "/login/toppage.jsp";
    private final String errorPage = "/login/error.jsp";
    /**
     *
     * @param request servlet request
     * @param response servlet response
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        response.setContentType("text/html;charset=UTF-8");
            String username = request.getParameter("username");
            String password = request.getParameter("password");
           
            ServletContext sc = getServletContext();
           
            try {
                //JDBCレルム認証部分(認証に失敗すると例外をスロー)
                request.login(username, password);
            } catch (ServletException ex) {
                Logger.getLogger(LoginServlet.class.getName()).log(Level.SEVERE, null, ex);
                try {
                    sc.getRequestDispatcher(errorPage).forward(request, response);
                } catch (ServletException ex1) {
                    Logger.getLogger(LoginServlet.class.getName()).log(Level.SEVERE, null, ex1);
                }
            }
            try {
                sc.getRequestDispatcher(topPage).forward(request, response);
            } catch (ServletException ex) {
                Logger.getLogger(LoginServlet.class.getName()).log(Level.SEVERE, null, ex);
            }
    } 
    /**
     * Handles the HTTP <code>POST</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }
}

■toppage.jsp
仮のトップページ。ウェルカムメッセージと、
ログアウトのための処理呼び出しが可能なだけ。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>トップページ</title>
    </head>
    <body>
        <h1>ログインに成功しました。</h1><br/>
        <form action="./LogoutServlet">
            ${param["username"]}様、いらっしゃいませ。<br/>
            <input type="submit" value="ログアウト"/>
        </form>
    </body>
</html>

■LogoutServlet(一部抜粋)
request.getSession().invalidate();でセッション情報を明示的に破棄するとともに
request.logout();でログアウト処理を実行している。

package authentication;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author USER
 */
@WebServlet(name = "LogoutServlet", urlPatterns = {"/LogoutServlet"})
public class LogoutServlet extends HttpServlet {

    private final String indexPage = "./login/index.jsp";
    private final String errorPage = "./login/error.jsp";
    /**
     * Processes requests for both HTTP <code>GET</code> and <code>POST</code>
     * methods.
     *
     * @param request servlet request
     * @param response servlet response
     */
    /**
     * @param request servlet request
     * @param response servlet response
     * @throws java.io.IOException
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws IOException
    {
        //セッションを明示的に破棄する。
        request.getSession().invalidate();
        ServletContext sc = getServletContext();
       
        try {
           //認証からのログアウトを行う処理。
            request.logout();
        } catch (ServletException ex) {
            Logger.getLogger(LogoutServlet.class.getName()).log(Level.SEVERE, null, ex);
            try {
                sc.getRequestDispatcher(errorPage).forward(request, response);
            } catch (ServletException ex1) {
                Logger.getLogger(LogoutServlet.class.getName()).log(Level.SEVERE, null, ex1);
            }
        }
        response.sendRedirect(indexPage);      
    }
    /**
     * Handles the HTTP <code>POST</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }
}


■error.jsp
ログイン処理時にrequest.login()に失敗するとこちらのページに遷移する。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>

        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>ログイン失敗</h1><br/>
        <a href="./index.jsp">ログインページへ</a>
    </body>
</html>


次の記事では、コードではなく、アクセス制御(web.xml)の記入について説明します。
※生のxmlを書くわけではなく、NetBeansにおんぶにだっこで設定します。

Glassfish+Derby+JDBCレルムについて(Glassfishの設定)

Glassfish側の設定。

以下の項目を作成していきます。

  1. コネクションプール
  2. JNDIリソース
1.についてはもともとあるものを流用してもよいのですが、
個人的に気持ち悪さを感じてしまうのであえて新しく作成することにします。

1. コネクションプールの作成
Glassfishの管理画面にログインします。
ローカルで実行している場合は、
http://localhost:4848にアクセスしてもらえれば管理画面に
アクセスできるはずです。
初期状態では特にパスワードなどは設定していないので
そのまま管理画面にアクセスできるはずです。

アクセス後は、管理画面の「JDBC」→「JDBC Connection Pools」と
選択をしていきます。作成済みのコネクションプールの一覧が
表形式で表示されるので、「New」ボタンをクリックしたのち、下記画面の内容を入力します。


これで以前の記事で作成したデータベースに対する
JDBCのコネクションプールは作成完了です。

一枚目の画像の「Ping」ボタンをクリックして

コネクションプールの名前にAuthenticationPoolと名付けたことを覚えておいて
ください。

2. JNDIリソースの作成

次はJNDIリソースを作成します。
管理画面の「JDBC」→「JDBC Resources」を選択します。
作成済みのJDBCプールのJNDIリソースの一覧が表示されるので、
「New」ボタンをクリックしたのち、下記の内容を入力します。


3. JDBCレルム設定の作成

「Conifgurations」→「Conifgurations」→「Security」→「Realms」を選択したのち、
「New」ボタンをクリックします。

下記画像の設定を投入します。


設定内容は以前の記事にて作成したDerbyのデータベースの設定に
準拠しています。

これでGlassfish側のJDBCレルムの設定は完了です。




2015年9月10日木曜日

Glassfish+Derby+JDBCレルムについて(Derby(JavaDB)の設定)

JDBCレルム認証を実現するためにはまずデータベースの作成が必要です。
ここでは、NetBeansをつかってローカルのJavaDB上にJDBCレルム認証に利用するデータベースを構築する方法を説明します。


NetBeansのサービスタグを選択、JavaDBを右クリックしてデータベースの
作成を選択します。


ここでは、データベース名をAuthenticationSampleとして作成しています。
ユーザ名はAPP, パスワードもユーザ名と同じにしています。
(当然ですが、本番環境での利用の際にはユーザ名、パスワードともに配慮が必要です。)

これで、JavaDB上にAuthenticationSampleデータベースが作成されました。



次は新規の接続先として先ほど作成したAuthenticationSampleに接続します。
下記のような設定を行ってください。


これで、接続先の設定が完了しました。
次にテーブルの作成に移ります。

テーブルの作成には先ほど作成した接続先を右クリックし、
コマンドの実行を選択します。新しい画面が開くので、下記のSQLを入力し、
usertableとgrouptableを作成します。


create table usertable (
username varchar(20) NOT NULL CONSTRAINT USER_PK PRIMARY KEY ,
mailaddress varchar(100) NOT NULL,
password varchar(128) NOT NULL
);


create table grouptable(
username varchar(20) NOT NULL,
groupid varchar(20) NOT NULL,
CONSTRAINT GROUP_PK PRIMARY KEY(username, groupid),
CONSTRAINT USER_FK FOREIGN KEY(username)
REFERENCES usertable(username) ON DELETE
);

これで、JDBCレルム認証用のデータベースが作成できました。
次以降の設定で必要となる内容は以下の通りです。

usertableのパスワード列には生パスワードではなく、SHA-256でハッシュ化した
値を入れないといけません。こちらについてはサンプルコードを
寺田佳央さんが提供してくれています。
https://github.com/yoshioterada/JDBC-Realm-Sample/tree/master/src/main/java/jp/co/oracle/jdbcrealm/SHADigestUtil


以下の情報は次回以降に利用するため、記載しておきます。

JDBC URL: jdbc:derby//localhost:1527/AuthenticationSample
データベース名: AuthenticationSample
データベースのユーザ名/パスワード: APP/APP
ユーザテーブル: usertable
ユーザ名: username
パスワード: password

グループテーブル: grouptable
ユーザ名: username
グループ名: groupid




Glassfish + Derby + JDBCレルムについて

JDBCレルム認証を説明した可能な限りシンプルなコードを作成しました。


https://github.com/Fufuhu/SimpleAuthentication


具体的な説明は後日。

2015年9月7日月曜日

Glassfish + Derby + JDBCレルムについて(導入編)

私の妄想が7割くらいなので、正しい理解なのかは保証しません。
図にしないと理解しづらい低能なので、JDBC、JNDI、JDBCレルムの依存関係はこんな感じ。
(コネクションプールを利用する場合)



  • JDBC Connection Pool ... どこにあるDBに接続するか
  • JDBC Resource(JNDI) ... JNDIとしてJDBC Connection Poolを取り扱えるようにする。
  • JDBCレルム ... JNDIからJDBC Connection Poolへ接続し、特定のテーブルからユーザ名・パスワードとロール名を読み取る。
JDBCレルムでパスワードを取り扱う際には、生パスワードをDBに書き込むことができないのでうっかりさんも安心。寺田さんの記事の例ではSHA-256でハッシュ化してあります。

一応私の方でも実装編を解説しようかと思います。以下の寺田さんの例だとJSFを使っているので、前提知識多めな感じになってしまっているので...私の方ではもっとシンプルにServletとJSPを使って実装しようかと思います。

2015年9月5日土曜日

JSFのfaceletsきめぇな方へ

JSFってエンジニアにはうれしいけど、デザイナにとってはつらいよね。

漢らしい業務臭い画面ってそれはそれで味があっていいけれども、
かわいらしいデザインって欲しいじゃない?

ってことでHTMLっぽくJSFを記述する方法をちょいと調べてみた。

JSF 2.2 でさらに便利になったMarkupを使ってみよう


公式に近い部分だとこれかな??
HTML(5) Friendly Markup in JSF 2.2


これをつかえばcssとかJavaScriptも一緒に使えるかな??

CDIメモ(自分向け)

完全に自分向けメモ。
ぼやっと本読んでたら何やら理解できて来た気がするので……

内容が正しいかはまったくもって保証しません。

■ CDI(Context and Dependency Injection)


    @Named(value = "injectSample")
    public class InjectSample {
        @Inject InjectedSample injected;
        public void sample() {
            injected.execute();
        }
    }


通常であればinjectedはどこかでインスタンス化する
必要がある(new InjectedSample();)が、CDIを利用することで
これをしなくてよくなる。

これによって、
InjectSampleとInjectedSampleの間の依存関係を疎にすることができる。
(その代わりInjectedSampleは引数を受け付けないコンストラクタによって
インスタンス化される)

■ コンテキスト

上記の内容を理解するにはコンテキスト(Context)にるいて理解する必要がある。

CDIにおけるコンテキストとは
スコープアノテーションによって指定されたオブジェクトの存続設定

■スコープアノテーション

CDIビーンに対して設定されるオブジェクトの存続期間


  • @RequestScoped
  • @SessionScoped
  • @ApplicationScoped
  • @ConversationScoped
  • @Dependent

■CDIビーン

管理ビーンの一種。CDIサービスによって管理される。
管理ビーンはコンテナ内で管理されており、
スコープに応じて適切なオブジェクトが自動で選択され、
変数に代入される。

2015年9月1日火曜日

WebSocketのサンプルコード(クライアント編)


WebSocketのクライアント(ブラウザ)側の実装サンプル。

テキストフィールドにメッセージを入れてSubmitボタンをクリックすると
他のクライアントにもメッセージが配信される。そんだけ。


Bootstrapを使ってみたけど、説明するって意味では、
明らかにコード可読性の低下要因でしかないな~......


以下の解説の用語とかは信用しないでください。
jQueryとBootstrap使ってるけれども正しい使い方ではないと思う。
基本的には、

  1. Websocketプロトコルでのアクセス先(wsUri)を指定
  2. Websocketをnewする(websocket)
  3. websocketに対して必要なメソッドを実装(ここではonerror, onopen, onmessage)
  4. クライアント上の操作内容に応じてjQueryでぐりぐりする
(ここでは、submitボタンをクリックするとテキストフィールドに入力したメッセージが 送信される。同時に他のクライアントにもメッセージが配信される。)
<html>
    <head>
        <title>Chatter</title>
        
        
        <link href="css/bootstrap.min.css" rel="stylesheet"></link>
        <script src="./js/jquery-2.1.1.js" type="text/javascript"></script>
        <script type="text/javascript">
            $(function(){
                var wsUri = "ws://" + document.location.host + "/WebSocketChatter/MessageReceiptor";
                var websocket = new WebSocket(wsUri);
                websocket.onerror = function(evt) {
                    
                };
                websocket.onopen = function(evt) {
                    
                };
                websocket.onmessage = function(message) {
                    console.log('Received Text:'+message.data);
                    $('<div class="well row" style="margin-bottom: 2px;">
'+message.data+'</div>
').appendTo('#container_message');
                };
                
                $('#btn_submit').on('click', function(){
                    var text = $('#text_message').val();
                    if(text !== '') {
                        websocket.send(text);                        
                        console.log('Send Text:'+text);
                    }
                });
                
                $('#btn_clear').on('click', function(){
                    $('#text_message').val('');
                });
            });
        </script>
    </head>
    <body>
        <div class="row">
            <div class="col-lg-3">
</div>
<div class="col-lg-3">
                <h1 class="label-primary" style="border-radius: 3px 3px;">
WebSocketChatter</h1>
</div>
</div>
<div class="row">
            <div class="col-lg-3">
</div>
<div class="col-lg-3">
                <h2 class="label-info">
入力項目</h2>
</div>
</div>
<div class="row">
            <div class="col-lg-3">
</div>
<div class="col-lg-3">
                <label for="text_message">Message</label>
                <input class="form-control" id="text_message" placeholder="Text Message" type="text" />
            </div>
</div>
<div class="row">
            <div class="col-lg-3">
</div>
<div class="col-lg-4 btn-toolbar">
                <div class="btn-group">
                    <button class="btn btn-success" id="btn_submit" type="button">Submit    <span class="glyphicon glyphicon-check"></span></button>
                    <button class="btn btn-danger" id="btn_clear" type="button">Clear   <span class="glyphicon glyphicon-remove"></span></button>
                </div>
</div>
</div>
<div class="row">
            <div class="col-lg-3">
</div>
<div class="col-lg-3">
                <h2 class="label-info">
メッセージコンテナ</h2>
<div class="col-lg-12" id="container_message">
                </div>
</div>
</div>
</body>
</html>