Dalam membangun sebuah aplikasi enterprise, salah satu fitur penting yang harus diperhatikan adalah tentang Authentication & Authorization. JavaEE sendiri juga telah memiliki fitur yang dikenal dengan Java Authentication & Authorization Service (JAAS) yang mana akan ditangani oleh Application Server.

Untuk kali ini saya ingin berbagi pengalaman dalam mengkonfigurasi JAAS pada Wildfly versi 10.1.0.Final. Konfigurasi JAAS yang saya lakukan adalah berbasis FORM yang mana berarti kita akan membuat halaman login dengan JSF. Kemudian data authentication & authorization akan menggunakan database. Untuk itu diperlukan konfigurasi Datasource sebelumnya.

Create Table Authentication and Authorization

Langkah pertama adalah pastikan kita memiliki table pada database untuk menyimpan informasi user beserta role yang dimiliki. sebagai contoh berikut adalah table yang saya gunakan dengan menggunakan database mariadb.

DROP TABLE IF EXISTS user_account;
CREATE TABLE user_account(
  id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(100) NOT NULL,
  email VARCHAR(200),
  password VARCHAR(255) NOT NULL,
  enabled INT(1) -- active = 1, non-active = 0
) ENGINE=InnoDB;

DROP TABLE IF EXISTS role;
CREATE TABLE role(
  id INT(2) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  role_name VARCHAR(100) NOT NULL,
  description VARCHAR(255)
) ENGINE=InnoDB;

DROP TABLE IF EXISTS user_role;
CREATE TABLE user_role(
  id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  user_id INT(4) UNSIGNED NOT NULL,
  role_id INT(2) UNSIGNED NOT NULL,
  FOREIGN KEY (user_id) REFERENCES user_account(id) ON DELETE CASCADE ON UPDATE CASCADE,
  FOREIGN KEY (role_id) REFERENCES role(id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;

Kemudian insert sebuah data sebagai awal agar kita bisa login kedalam aplikasi yang akan kita bangun.

INSERT INTO user_account(id, username, email, password, enabled) VALUES
(1, 'admin', 'admin@mail.com', 'x61Ey612Kl2gpFL56FT9weDnpSo4AV8j8+qx2AuTHdRyY036xxzTTrw10Wq3+4qQyB+XURPWx1ON
xp3Y3pB37A==', 1), -- password is admin with SHA-512
(2, 'sukma', 'sukma@mail.com', 'lPzi0dvSiqUEmrwbcFF+E5aRbdQEu5UWzgeiD5a7Vm0NjFmj9Bc4sgEgCTeq1cokWTRk42NfJ+Jl
2ZS5HvOFLw==', 1); --password is sukma with SHA-512

INSERT INTO role(id, role_name) VALUES
(1, 'ADMIN'),
(2, 'LIBRARIAN'),
(3, 'CUSTOMER');

INSERT INTO user_role(user_id, role_id) VALUES
(1, 1),
(2, 3);

Sebagai catatan Wildfly 10 menggukanan kombinasi PicketBox & JAAS sebagai client and server security mechanism dan picketbox pula yang akan melakukan pengecekan terhadap enkripsi sebuah password. Jika melihat contoh query yang saya cantumkan diatas untuk kolom password datanya merupakan hasil enkripsi dengan algoritma SHA-512.

Untuk dapat membuat hasil enkripsi tersebut kita dapat memanfaatkan picketbox yang terdapat pada Wildfly dengan perintah seperti berikut

--java -cp WILDFLY_HOME/modules/system/layers/base/org/picketbox/main/picketbox-4.9.6.Final.jar org.jboss.security.Base64Encoder <password> <message-digest>

java -cp WILDFLY_HOME/modules/system/layers/base/org/picketbox/main/picketbox-4.9.6.Final.jar org.jboss.security.Base64Encoder sukma SHA-512

Configure Security Domain in Wildfly

Sungguh aneh bagi saya bahwa konfigurasi Authentication & Authorization ditangani oleh sebuah Application Server karena saya memulai belajar JavaEE dengan menggunakan Spring Framework yang mana semua konfigurasi tentu dilakukan oleh Aplikasi itu sendiri (XML ataupun Java Class). Tapi justru hal ini menjadi pengalaman yang menarik bagi saya.

Pertama-tama masuk kedalam menu console pada halaman Wildfly, pilih menu configuration -> Subsytem -> Security Domain.

security_menu

Kemudian klik tombol add. Isi nama security domain, untuk cache type pilih default.

security_menu

Sayangnya tidak seperti mengkonfigurasi datasource saya belum menemukan tutorial konfigurasi security domain secara UI, sehingga kita perlu melakukan perubahan pada file standalone.xml yang terletak pada folder WILDFLY_HOME/standalone/configuration.

pada file standalone.xml cari subsytem yang memiliki value jboss:domain:security:x.y”(x.y) adalah nomoer versinya. Didalam subsytem tersebut kalian akan menemukan nama security yang telah kita buat seperti berikut

<security-domain name="library" cache-type="default" />

Rubah tag XML tersebut sehingga menjadi seperti berikut

<security-domain name="library" cache-type="default">
    <authentication>
        <login-module code="Database" flag="required">
            <module-option name="dsJndiName" value="java:jboss/datasource/libraryDS"/>
            <module-option name="principalsQuery" value="SELECT password from user_account WHERE username=?"/>
            <module-option name="rolesQuery" value="SELECT r.role_name, 'Roles' FROM user_role ur LEFT JOIN role r ON ur.role_id = r.id LEFT JOIN user_account u ON ur.user_id = u.id WHERE u.username=?"/>
            <module-option name="hashAlgorithm" value="SHA-512"/>
            <module-option name="hashEncoding" value="base64"/>
        </login-module>
    </authentication>
    <authorization>
        <policy-module code="Database" flag="required">
            <module-option name="dsJndiName" value="java:jboss/datasource/libraryDS"/>
            <module-option name="principalsQuery" value="SELECT password from user_account WHERE username=?"/>
            <module-option name="rolesQuery" value="SELECT r.role_name, 'Roles' FROM user_role ur LEFT JOIN role r ON ur.role_id = r.id LEFT JOIN user_account u ON ur.user_id = u.id WHERE u.username=?"/>
            <module-option name="hashAlgorithm" value="SHA-512"/>
            <module-option name="hashEncoding" value="base64"/>
        </policy-module>
    </authorization>
</security-domain>

Disini saya membuat dua module yakni authentication dan authorization dimana dalam tiap module tersebut memiliki module option yang sama. Hal ini saya lakukan karena disebutkan jika apabila hanya menggunakan module authentication maka akan dapat menimbulkan masalah dikedepannya.

  • dsJndiName: This is a module option which tells the system to use the datasource in order to connect to a database. We pass a datasource JNDI as a value in here.
  • principalsQuery: This is the key query which matches the passed username and value against the database entries and returns true or false based on the result. The general structure of query is: SELECT [password-column] FROM [table-name] WHERE [username-column]=?
  • rolesQuery: This is the query we are going to use in order to query the database for the role type of user. This is more of a authorization process. Here the general structure of query is: SELECT [role-column], 'Roles' FROM [table-name] WHERE [username-column]=?
  • hashAlgorithm: This tells the system to use a perticular hashAlgorithm to match against the password. This is an optional step but is recommended to ensure the security of password.
  • hashEncoding: This is also an optional step but is necessary if you are using hashAlgorithm, this tells the system to use a particular encoding for the hash code generated by the algorithm specified in hashAlgorithm.

Configure Your Application

Setelah selesai melakukan konfigurasi disisi Application Server selanjutnya adalah mengkonfigurasi dari sisi aplikasi kita, langkah yang pertama kali harus kita lakukan adalah membuat file dengan nama jboss-web.xml pada folder WEB-INF file tersebut merupakan mandatory karena kita menggunakan Application Server Wildfly yang notabene adalah suksesor JBoss, jika kita menggunakan Application Server lain maka akan lain pula konfigurasinya, berikut adalah isi dari file jboss-web.xml

<jboss-web>
    <!-- security domain name in wildfly 10.1.0 -->
    <security-domain>library</security-domain>
</jboss-web>

Langkah selanjutnya adalah menambahkan security constraint pada file web.xml

<!--Defining security constraint for type of roles available-->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>administrator</web-resource-name>
            <url-pattern>/admin/*</url-pattern>
            <http-method>POST</http-method>
            <http-method>GET</http-method>
            <http-method>PUT</http-method>
            <http-method>DELETE</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
        </auth-constraint>
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>customer</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>POST</http-method>
            <http-method>GET</http-method>
            <http-method>PUT</http-method>
            <http-method>DELETE</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
            <role-name>CUSTOMER</role-name>
        </auth-constraint>
    </security-constraint>
    <!--Defining security constraint for type of roles available-->

    <!--Defining type of authenitcation mechanism-->
    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>library</realm-name> <!-- realm name from wildfly appserver -->
        <form-login-config>
            <form-login-page>/login.xhtml</form-login-page>
            <form-error-page>/error.xhtml</form-error-page>
        </form-login-config>
    </login-config>
    <!--Defining type of authenitcation mechanism-->

    <!--Defining security role, the role name is must be the same like in database-->
    <security-role>
        <role-name>ADMIN</role-name>
    </security-role>
    <security-role>
        <role-name>LIBRARIAN</role-name>
    </security-role>
    <security-role>
        <role-name>CUSTOMER</role-name>
    </security-role>

NOTE: role-name merupakan nama role yang tersimpan pada database

Create Login & Logout

Karena kita menggunakan FORM JAAS maka langkah terakhir adalah membuat halaman Login serta fitur untuk melakukan logout. Untuk itu pertama kita perlu membuat file login.xhtml yang isinya adalah seperti berikut

<form action="j_security_check" method="post" class="form-horizontal">
	<div class="form-group">
		<label class="control-label col-sm-2">Username:</label>
		<div class="col-sm-10">
			<input type="text" class="" name="j_username" placeholder="Username" />
		</div>
	</div>
	<div class="form-group">
		<label class="control-label col-sm-2">Password:</label>
		<div class="col-sm-10">
			<input type="password" class="" name="j_password" placeholder="Password" />
		</div>
	</div>
	<div class="form-group">
		<div class="col-sm-offset-2 col-sm-10">
			<button type="submit" class="btn btn-primary">Login</button>
		</div>
	</div>
</form>

Kemudian Untuk dapat melakukan fitur logout, kita dapat memanfaatkan Servlet. Kita perlu membuat sebuah Class Java seperti berikut:

@WebServlet(name = "logout", urlPatterns = {"/logout"})
public class Logout extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        HttpSession session = request.getSession(false);

        if(session != null) {
            session.invalidate();
        }

        response.sendRedirect(request.getContextPath());
    }
}

Demikian tutorial untuk menerapkan JAAS dengan Wildfly 10.1.0.Final

References