前のセクションで説明したデフォルトの JRun セキュリティメカニズムは、サイトによっては適用できない場合があります。たとえば、JDBC、LDAP、および Windows ログインモジュールの入力パラメータは、既存のセキュリティシステムでは機能しない場合があります。
このような場合、1 つ以上のカスタマイズされたログインモジュールを作成することによって、JRun セキュリティを必要に応じてカスタマイズして拡張できます。
JRun セキュリティをカスタマイズする前に、ログインモジュールが JRun でどのように使用されるかについて理解しておくことが必要です。デフォルトの JAAS 実装では、ログインモジュールは認証のみに使用されます。しかし、JRun では次に示すように認証および承認の両方でログインモジュールを使用します。
実行する処理は、auth.config ファイルで指定されている mode 属性の USER または ROLE を渡して指定します。この属性の処理については、サンプルコードを参照してください。
次のサンプルコードは、ログインモジュールがユーザー名およびパスワードにアクセスする方法を示しています。
...
NameCallback n = new NameCallback("User Name - ", "Guest"); PasswordCallback p = new PasswordCallback("Password - ", false); Callback[] callbacks = {n, p}; try { cbHandler.handle(callbacks); username = n.getName().trim(); char[] tmpPassword = p.getPassword(); ...
次のサンプルコードは、ユーザー名および許可されるロールにアクセスします。
...
boolean userRoleFound = false;RolesCallback rcb = new RolesCallback();Callback[] callbacks = {rcb};try { // cbHandler は CallbackHandler オブジェクトです。 cbHandler.handle(callbacks); Principal p = rcb.getPrincipal(); username = p.getName().trim(); Collection rolesToCheck = rcb.getRoles(); ...
多くのサイトでは、デフォルトの JRun ログインモジュールを使用して認証および承認を行うことができます。ただし、ユーザーのサイトでデフォルトの JRun の JDBC ログインモジュールを使用できない場合は、カスタマイズされたログインモジュールを作成して、ユーザーのサイト固有のリレーショナルデータベースのユーザーおよびロールにアクセスできます。
デフォルトの JDBC ログインモジュールの詳細については、「JDBC ベースのセキュリティ実装の使用」を参照してください。
カスタマイズされた JDBC ログインモジュールを使用するには、次の手順を実行します。
次の例は、カスタマイズされた JDBC ログインモジュールを示しています。
...
// 必要なインポート import javax.security.auth.spi.LoginModule; import javax.security.auth.Subject; import javax.security.auth.login.LoginException; import javax.security.auth.login.FailedLoginException; import javax.security.auth.callback.*; import java.security.Principal; // ログインモジュール特有の機能に使用されるインポートです。 // ログインモジュール特有 (JDBC) の機能に使用されるインポートです。 import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import java.util.Map; import java.util.Collection; import java.util.Iterator; import java.util.ArrayList; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; // JRun 特有のインポート import jrun.security.SimplePrincipal; import jrun.security.RolesCallback; // リソースバンドル処理に使用されます。 import jrunx.util.RB; // ロギングに使用されます。 import jrunx.kernel.JRun; public class SampleJDBCLoginModule implements LoginModule { private Subject subject; private CallbackHandler cbHandler; private Map sharedState; private Map options; /** * mode = "USER" の * SQL ステートメントは、特定のユーザー名のパスワードを照会します。 * 例:"select PasswordColumn from UsersTable where UserName=?" * * mode = "ROLE" の * SQL ステートメントは、特定のユーザー名のロールを照会します。 * 例:"select RoleColumn from RolesTable where UserName=?" */ private String queryString=null; /** * ユーザーは、ユーザー名と、ユーザー名を入力として扱うパスワードを * 取り出す SQL ステートメントを指定するか、テーブル名、ユーザー名、 * 列、およびパスワード列を指定できます。 * auth.config でパラメータとして指定されます。 */ private String userColumn=null; private String passwordColumn=null; private String tableName=null; private String roleColumn=null; /** * ユーザーセキュリティ情報が含まれるデータベースにアクセスするユーザー ID とパスワードです。 * auth.config でパラメータとして指定されます。 */ private String dbUserId=null; private String dbPassword=null; private String username=null; private char[] password=null; /** * データベースにアクセスするために JNDI 内で検索する JDBC データソースです。 * これは、JRun データソースとなり得ます。 * auth.config でパラメータとして指定されます。 */ String dsJndiName=null; // 認証ステータス private boolean succeeded = false; private boolean commitSucceeded = false; //認証された principal オブジェクト private SimplePrincipal userPrincipal; // ログインモードを "USER" または "ROLE" にして、ユーザー証明を認証するか、 // ユーザーロールを承認するかを指定します。 protected String loginMode = "USER"; /** * <p>JRun セキュリティマネージャは、認証または * 承認リクエストの開始時に initialize メソッドを * 呼び出します。 */ public void initialize(Subject subj, CallbackHandler cbh, Map sharedState, Map options) { this.subject = subj; this.cbHandler = cbh; this.sharedState = sharedState; this.options = options; loginMode = (String) options.get("mode"); dbUserId = (String) options.get("databaseUserId"); dbPassword = (String) options.get("databasePassword"); dsJndiName = (String) options.get("dataSourceJNDIName"); queryString = (String) options.get("queryString"); tableName = (String) options.get("tableName"); userColumn = (String) options.get("userNameColumn"); if(loginMode.equals("USER") ) // ユーザー認証に特有のパラメータ passwordColumn = (String) options.get("passwordColumn"); else // ユーザーロール承認に特有のパラメータ roleColumn = (String) options.get("roleColumn"); JRun.getLogger().logInfo("*** SampleJDBCLoginModule:initialize"); } /** * <p>JRun セキュリティマネージャは、認証または承認リクエスト * に対して initialize メソッドを呼び出した後、login メソッドを * 呼び出します。 */ public boolean login() throws LoginException { if(cbHandler == null) { throw new LoginException(RB.getString(SampleJDBCLoginModule.class, "SampleJDBCLoginModule.noCallBackHandlerAvailable") ); } JRun.getLogger().logInfo("*** SampleJDBCLoginModule:ログイン。モード:" + loginMode); if (loginMode.equals("ROLE") ) { // 承認のためにこのメソッドを呼び出します。 return validateRole(); } else { // デフォルト // 認証のためにこのメソッドを呼び出します。 return loginUser(); } } /** * <p> mode=USER のときに呼び出されます。 */ protected boolean loginUser() throws LoginException { NameCallback n = new NameCallback("User Name - ", "Guest"); PasswordCallback p = new PasswordCallback("Password - ", false); Callback[] callbacks = {n, p}; try { cbHandler.handle(callbacks); // 渡されたユーザー名 NameCallback を取得します。 username = n.getName().trim(); // デバッグの補助 JRun.getLogger().logInfo("*** loginUser():ログインしているユーザー:" + username); // PasswordCallback から渡されたパスワードを取得します。 char[] tmpPassword = p.getPassword(); if( tmpPassword != null ) { password = new char[tmpPassword.length]; System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length); p.clearPassword(); } } catch(java.io.IOException e) { throw new LoginException(e.toString()); } catch(UnsupportedCallbackException uce) { // 新規 LoginException("サポートされていないコールバック:" + uce.getCallback()); throw new LoginException(RB.getString(SampleJDBCLoginModule.class, "SampleJDBCLoginModule.unsupportedCallBack")); } // コールバックからのパスワードとデータベースのパスワードを検証します。 String passwordFromDB = getUserPassword().trim(); JRun.getLogger().logInfo("*** loginUser():データベースのパスワード :" + passwordFromDB); succeeded = true; char[] pw = passwordFromDB.toCharArray() ; if(password.length == pw.length) { for(int i = 0; i < pw.length; i++) { if (password[i] != pw[i]) { succeeded = false; break; } // 内側 if を終了 } // for を終了 } else succeeded = false; // 同じ長さではない return succeeded; } /** * <p> mode=ROLE のときに呼び出されます。 */ protected boolean validateRole() throws LoginException { boolean userRoleFound = false; JRun.getLogger().logInfo("*** validateRole()"); RolesCallback rcb = new RolesCallback(); Callback[] callbacks = {rcb}; try { cbHandler.handle(callbacks); // RolesCallback からユーザー名を取得します。 Principal p = rcb.getPrincipal(); username = p.getName().trim(); // 承認されたロールを RolesCallback から取得します。 Collection rolesToCheck = rcb.getRoles(); // 割り当てられているロールをデータベースから取得します。 ArrayList rolesFromDatabase = getUserRoles(); //rolesToCheck を繰り返し、rolesFromDataBase からユーザー名を持つものを検索し ます。 Iterator i = rolesToCheck.iterator(); while(i.hasNext() && !userRoleFound) { String thisRoleName = (String) i.next(); int numberOfRolesFromDB = rolesFromDatabase.size(); for(int index = 0; index < numberOfRolesFromDB; index++) { String dbRoleName = (String) rolesFromDatabase.get(index); if(thisRoleName.equals(dbRoleName.trim()) ) { succeeded = true; userRoleFound = true; } // 内側 if を終了 } // for を終了 } // while を終了 } catch(java.io.IOException e) { throw new LoginException(e.toString()); } catch(UnsupportedCallbackException uce) { throw new LoginException(RB.getString(SampleJDBCLoginModule.class, "SampleJDBCLoginModule.unsupportedCallBack") ); } catch(Exception e) { JRun.getLogger().logError(RB.getString(SampleJDBCLoginModule.class, "SampleJDBCLoginModule.errorValidatingRole"), e); } return userRoleFound; } /* * <p>データベースを呼び出して、ユーザー名のパスワードを取り出します。 */ protected String getUserPassword() throws LoginException { Connection conn = null; PreparedStatement ps = null; String passwordFromResult=null; try { InitialContext ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup(dsJndiName); if(dbUserId != null) ds.getConnection(dbUserId, dbPassword); else conn = ds.getConnection(); // SQL クエリ文字列か、テーブル名、パスワード、およびユーザー名の列を提供できます。 if(queryString != null) { ps = conn.prepareStatement(queryString); ps.setString(1, username); } else { queryString = "select " + passwordColumn + " from " + tableName + " where " + userColumn + "=" + username; ps = conn.prepareStatement(queryString); } ResultSet rs = ps.executeQuery(); if( rs.next() == false ) throw new FailedLoginException(RB.getString(SampleJDBCLoginModule.class, "SampleJDBCLoginModule.userNameNotFound") ); passwordFromResult = rs.getString(1); rs.close(); } catch(NamingException ex) { throw new LoginException(ex.toString(true)); } catch(SQLException ex) { throw new LoginException(ex.toString()); } finally { connCleanup(ps, conn); } return passwordFromResult; } /** * ユーザー名に割り当てられてたロールを取り出すためにデータベースを呼び出します。 */ protected ArrayList getUserRoles() throws LoginException { Connection conn = null; PreparedStatement ps = null; ArrayList rolesFromDB= new ArrayList(); try { InitialContext ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup(dsJndiName); if(dbUserId != null) ds.getConnection(dbUserId, dbPassword); else conn = ds.getConnection(); // auth.config ファイルでは、SQL クエリ文字列、または // テーブル名、ロール名、およびユーザー名の列を提供できます。 if(queryString != null) { ps = conn.prepareStatement(queryString); ps.setString(1, username); } else { queryString = "select " + roleColumn + " from " + tableName + " where " + userColumn + "=" + username; ps = conn.prepareStatement(queryString); } ResultSet results = ps.executeQuery(); String dbRolename=null; if(results.getFetchSize() > 0) { while(results.next() ) { dbRolename = results.getString(1); rolesFromDB.add(dbRolename); } // while を終了 } // if を終了 else throw new FailedLoginException(RB.getString(SampleJDBCLoginModule.class, "SampleJDBCLoginModule.userNameNotFound") ); results.close(); } // try を終了 catch(NamingException ex) { throw new LoginException(ex.toString(true)); } catch(SQLException ex) { throw new LoginException(ex.toString()); } finally { connCleanup(ps, conn); } return rolesFromDB; } protected void connCleanup(PreparedStatement ps, Connection conn) { if( ps != null ) { try { ps.close(); } catch(SQLException e) {} } if( conn != null ) { try { conn.close(); } catch (SQLException ex) {} } } /** * <p>認証および承認が成功したかどうかを示します。 */ public boolean commit() throws LoginException { if (succeeded == false) { return false; } else { // Principal (認証済みの ID) を // Subject に追加します。 // 認証されるユーザーは SimplePrincipal とします。 userPrincipal = new SimplePrincipal(username); if (!subject.getPrincipals().contains(userPrincipal)) subject.getPrincipals().add(userPrincipal); // いずれの場合も、ステートをクリーンアップします。 username = null; // for (int i = 0; i < password.length; i++) //password[i] = ' '; password = null; commitSucceeded = true; return true; } } /** * <p> このメソッドは LoginContext の * すべての認証が失敗した場合に呼び出されます。 * (関連する REQUIRED、REQUISITE、SUFFICIENT、および OPTIONAL LoginModules * は成功していません。) * * <p> この LoginModule の認証が成功すると (<code>login</code> および * <code>commit</code> メソッドで保存されている プライベートステートを *取得することによって確認できます)、このメソッドははじめから * 保存されているすべてのステートをクリーンアップします。 * * <p> * * abort が失敗した場合は例外 LoginException です。 * * この LoginModule のログインまたはコミットが失敗した場合は false が返され、 * それ以外は true が返されます。 */ public boolean abort() throws LoginException { if (succeeded == false) { return false; } else if (succeeded == true && commitSucceeded == false) { // ログインには成功しましたが、全体の認証には失敗しました。 succeeded = false; username = null; if (password != null) { password = null; } userPrincipal = null; } else { // 全体の認証およびコミットは成功しましたが、 // 他のユーザーのコミットには失敗しました。 logout(); } return true; } /** * ユーザーをログアウトします。 * * <p> このメソッドは、<code>SamplePrincipal</code> を削除します。 * これは、<code>commit</code> メソッドによって追加されたものです。 * * <p> * * logout に失敗した場合は例外 LoginException です。 * * この <code>LoginModule</code> は無視できないので * すべてのケースで true が返されます。 */ public boolean logout() throws LoginException { subject.getPrincipals().remove(userPrincipal); username = null; if (password != null) { for (int i = 0; i < password.length; i++) password[i] = ' '; password = null; } userPrincipal = null; return true; } } // クラスを終了します。