2010年1月25日 星期一

[JAAS] JBoss下動態網站使用資料庫設定認證授權

序言

雖說我以前一直很不想再Try Java Tool弄很久~因為開發速度還是微軟順手些,但看來我還是逃脫不了。
總之~這篇是最近我實作時的紀錄,主要是要用Java預設的安全機制,完成網站的認證授權(也就是登入登出、 Login and Logout)。
通常書到用時方恨少,這句話這時就應驗了,腦中有的資訊太少,所以這個東西花了我不少功夫。
如果有任何不標準的用法,請見諒,很多都是我從網路找來的資訊組合而成的,我很懶得去查官方的英文文件(官方文件有很多其實也沒寫的很詳細...)。
歡迎有更標準的用法的話,請留下文章、連結等資料指正我。

開發環境

JBoss設定

  1. 資料庫連接器:將資料庫連接器【mysql-connector-java-5.1.10-bin.jar】放在Jboss的伺服器函式庫資料夾,如【jboss\server\default\lib】
  2. 資料庫連線設定:連線設定檔是用xml定義的,可直接複製JBoss提供的範本(位於【jboss\docs\examples\jca\mysql-ds.xml】)到【jboss\server\default\deploy】,並將資料庫設為你的MySQL連線設定。(如果用其他資料庫的話,在範本資料夾裡大都有範例)
    這邊比較需要注意的是等下要用到的名稱
    • jndi-name:等下設定驗證方式時會用到的名稱,例如下面範例的【MySqlDS】
    <?xml version="1.0" encoding="UTF-8"?>

    <!-- See http://www.jboss.org/community/wiki/Multiple1PC for information about local-tx-datasource -->
    <!-- $Id: mysql-ds.xml 88948 2009-05-15 14:09:08Z jesper.pedersen $ -->
    <!-- Datasource config for MySQL using 3.0.9 available from:
    http://www.mysql.com/downloads/api-jdbc-stable.html
    -->

    <datasources>
    <local-tx-datasource>
    <jndi-name>MySqlDS</jndi-name>
    <connection-url>jdbc:mysql://mysql-hostname:3306/jbossdb</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>x</user-name>
    <password>y</password>
    <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
    <!-- should only be used on drivers after 3.22.1 with "ping" support
    <valid-connection-checker-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLValidConnectionChecker</valid-connection-checker-class-name>
    -->
    <!-- sql to call when connection is created
    <new-connection-sql>some arbitrary sql</new-connection-sql>
    -->
    <!-- sql to call on an existing pooled connection when it is obtained from pool - MySQLValidConnectionChecker is preferred for newer drivers
    <check-valid-connection-sql>some arbitrary sql</check-valid-connection-sql>
    -->

    <!-- corresponding type-mapping in the standardjbosscmp-jdbc.xml (optional) -->
    <metadata>
    <type-mapping>mySQL</type-mapping>
    </metadata>
    </local-tx-datasource>
    </datasources>


    另外,如果覺得連線密碼這樣寫明碼太危險,可參考JBoss community的文件加密儲存
  3. 驗證方式設定:在JBoss的設定目錄下【jboss\server\default\conf】,有個【login-config.xml】,這個就是設定帳號驗證的方式(如果沒有這個檔也可以自己新增)
    裡面的參數
    • UserAA: 我設定的驗證來源名稱,這個在專案中會做對應的設定。
    • dsJndiName: 這邊就會對應到上一個設定的資料庫來源名稱MySqlDS
    • principalsQuery:這邊要寫查詢帳號密碼的SQL
    • rolesQuery:這邊要寫使用者角色的查詢SQL,角色會關係到後面網站權限的設定,第一個資料【roleName】所查詢出來的值我們這邊假設可能會有admin與guest,第二個資料是群組,我目前也不清楚是做什麼的,先依據官方範例給固定文字
    <?xml version='1.0'?>
    <policy>
    <application-policy name="UserAA">
    <authentication>
    <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag = "required">
    <module-option name = "dsJndiName">java:/MySqlDS</module-option>
    <module-option name = "principalsQuery">SELECT passwd FROM userdb.usersTab where userID=?</module-option>
    <module-option name = "rolesQuery">SELECT roleName,'Roles' FROM userdb.userRolesTab where userID=?</module-option>
    </login-module>
    </authentication>
    </application-policy>
    </policy>

MySQL

以下使用SQL script建立所需的資料庫與資料表,我將同時產生兩個預設的帳號分別屬於不同的權限角色,以下比較需要注意的部份是
  • 主要用到的資料來源是UsersTab資料表中的兩個欄位userID、passwd,還有UserRolesTab的兩個欄位userID、roleName,這部份不知道是不是有型態限制或是長度限制的問題,我用別的規格下去可能就無法認證。
  • 往下的Trigger是可有可無的,是我建立還方便管理資料庫用的
  • 最下面的insert是預設帳號與角色資料的建立
    • boss/0000:角色為admin
    • test/0000:角色為guest
CREATE DATABASE  IF NOT EXISTS `userdb` /*!40100 DEFAULT CHARACTER SET utf8 */;


DROP TABLE IF EXISTS `userdb`.`UsersTab`;
CREATE TABLE `userdb`.`UsersTab` (
`userID` varchar(64) NOT NULL,
`passwd` varchar(64) NOT NULL,
`createtime` datetime DEFAULT NULL,
`updatetime` datetime DEFAULT NULL,
PRIMARY KEY (`userID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `userdb`.`UserRolesTab`;
CREATE TABLE `userdb`.`UserRolesTab` (
`userID` varchar(64) NOT NULL,
`roleName` varchar(32) NOT NULL DEFAULT 'guest',
`createtime` datetime DEFAULT NULL,
PRIMARY KEY (`userID`,`roleName`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


DELIMITER $$
drop TRIGGER IF EXISTS `userdb`.UsersTab_Insert$$
CREATE TRIGGER `userdb`.UsersTab_Insert BEFORE INSERT ON `userdb`.UsersTab FOR EACH ROW
BEGIN
if new.createtime is null then
Set new.createtime = now();
end if;
if new.updatetime is null then
Set new.updatetime = now();
end if;
END$$

drop TRIGGER IF EXISTS `userdb`.UsersTab_Update$$
CREATE TRIGGER `userdb`.UsersTab_Update BEFORE UPDATE ON `userdb`.UsersTab FOR EACH ROW
BEGIN
if new.updatetime is null or old.updatetime=new.updatetime then
Set new.updatetime = now();
end if;
END$$

drop TRIGGER IF EXISTS `userdb`.UsersTab_Delete$$
CREATE TRIGGER `userdb`.UsersTab_Delete AFTER DELETE ON `userdb`.UsersTab FOR EACH ROW
BEGIN
delete from `userdb`.UserRolesTab where userID=old.userID;
END$$

drop TRIGGER IF EXISTS `userdb`.UserRolesTab_Insert$$
CREATE TRIGGER `userdb`.UserRolesTab_Insert BEFORE INSERT ON `userdb`.UserRolesTab FOR EACH ROW
BEGIN
if new.createtime is null then
Set new.createtime = now();
end if;
END$$
DELIMITER ;

insert into userdb.UsersTab (userID,passwd) values('boss','0000');
insert into userdb.UserRolesTab (userID,roleName) values('boss','admin');

insert into userdb.UsersTab (userID,passwd) values('test','0000');
insert into userdb.UserRolesTab (userID,roleName) values('test','guest');

Dynmic Web Project

動態網站的設定主要是在【WEB-INF】資料夾中新增一個【jboss-web.xml】,內容如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 2.3V2//EN" "http://www.jboss.org/j2ee/dtd/jboss-web_3_2.dtd">

<jboss-web>
<security-domain>java:/jaas/UserAA</security-domain>
</jboss-web>
然後修改【web.xml】。這邊我採用Form認證的方式,也就是用網頁提供Login的介面。
  • security-constraint:這一段是設定哪些路徑可由哪些Role來存取,也就是權限設定。這部份有用到的角色一定要在下面的【security-role】有定義到。另外如果想設定成只有有登入的人才能存取,可將【role-name】設定成【*】。
  • login-config:這一段就是定義Login的方式與參照頁面(另依種方式設定為BASIC)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
...
<security-constraint>
<web-resource-collection>
<web-resource-name></web-resource-name>
<url-pattern>/test/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name></web-resource-name>
<url-pattern>/Login</url-pattern>
<url-pattern>/Logout</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<realm-name></realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login.jsp?msg=Incorrect</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>guest</role-name>
</security-role>
...
</web-app>

JSP & Servlet

再來我針對登入部份的網頁進行開發,主要會建立JSP:login.jsp與Servlet:Login、Logout。
實際上真正的登入網頁會是login.jsp,但因為如果使用者直接輸入login.jsp的網址後,進行登入成功後系統會不知該導到哪一頁(似乎也沒辦法設定預設導頁,網路上是有另一種作法如果有需要可自行參考)。
我這邊是自己想了個比較簡單的邏輯性解法,就是做一個Login的Servlet裡面直接會跳到首頁作為預設網頁,
然後將這個Login servlet設定必須登入才能存取(也就是上一步的web.xml中設定的),讓登入的連結以這個Servlet為主就會自動導自login.jsp。
login.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Login</title>
</head>
<body>

<h2 align="center">Login</h2>
<table border="0" align="center">
<tr>
<td>
<form method="POST" action="j_security_check">
<div>User ID :
<input type="text"
name="j_username">
</div>
<div>Password :
<input type="password"
name="j_password">
</div>
<div><input type="submit" value="Log In"></div>

<div style="color: red;"><%=(request.getParameter("msg")!=null)?request.getParameter("msg"):"" %></div>

</form>
</td>
</tr>
</table>

</body>
</html>
Login servlet
package view;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* Servlet implementation class Login
*/
public class Login extends HttpServlet {
private static final long serialVersionUID = 1L;

/**
* @see HttpServlet#HttpServlet()
*/
public Login() {
super();
// TODO Auto-generated constructor stub
}

/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.sendRedirect(request.getContextPath() + "/index.jsp");
}
}

Logout servlet
package view;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class Logout extends HttpServlet {
private static final long serialVersionUID = 1L;

/**
* @see HttpServlet#HttpServlet()
*/
public Logout() {
super();
}

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().invalidate();
response.sendRedirect(request.getContextPath() + "/");
}
}

總結

以上設定完成後,就可以進到你的應用程式,然後存取Login servlet看看能不能正常登入登出了。
在這邊補充一些資訊,如果要在程式中取得登入者後的帳號,可以用【request.getUserPrincipal()】來取得Principal物件,如果不是null就可以呼叫該物件的getName()方法。
不過我個人比較喜歡用另外一個物件【org.jboss.security.SecurityAssociation】中的【getPrincipal()】也可以。
如果是要判斷使用者的角色,可使用【request.isUserInRole("admin")】方法來知道該使用者是否屬於admin這個角色。

參考資料

沒有留言:

張貼留言