JBoss使用JBoss SX框架来确保应用程序安全。它建立在Java身份验证和授权服务的顶层(JAAS,Java Authentication and Authorization Service)。
当JBoss接收到请求时,目标应用程序不需要知道基本安全数据库的位置或访问方式;
请求被传递到名为“安全域”的JBoss SX组件中,这是一种用来保护所有对组件的球球的抽象;
安全域执行所有必要的安全检查并告知组件用户可否继续进行访问,安全域知道如何使用一个或多个登录模块从数据源加载安全数据;
可以在server/x/conf/login-config.xml中添加或更改现有安全域定义。
<application-policy>定义一个安全域,JBoss SX根据name生成JNDI上下文,并使用该上下文将安全域绑定到JNDI中;
<login-module>定义登录模块,登录模块可以从属性文件、或数据库中加载安全信息(密码、角色)。
<!--application-policy:定义安全域--> <application-policy name="authnservice"> <authentication> <!--login-module:定义登录模块--> <login-module code="com.alpha.security.authentication.TokenLoginModule" flag="required"/> <login-module code="com.alpha.security.authentication.LockedAccountLoginModule" flag="required"> <module-option name="dsJndiName">java:/OracleDS</module-option> <module-option name="lockingQuery"> SELECT 。。。 </module-option> <module-option name="updateLocked"> UPDATE USER 。。。 </module-option> <module-option name="updateFailLoginCount"> UPDATE USER 。。。 </module-option> </login-module> <login-module code="com.alpha.security.authentication.TokenServiceLoginModule" flag="required"> <module-option name="unauthenticatedIdentity">guest</module-option> <module-option name="dsJndiName">java:/OracleDS</module-option> <module-option name="hashAlgorithm">SHA-1</module-option> <module-option name="hashEncoding">hex</module-option> <module-option name="principalsQuery"> select PASSWORD from 。。。 </module-option> <module-option name="rolesQuery"> SELECT Roles FROM ROLE r,。。。 </module-option> <module-option name="password-stacking">useFirstPass</module-option> <module-option name="rolePrefix">alpha/</module-option> <module-option name="hashCharset">UTF-8</module-option> </login-module> <login-module code="com.alpha.security.authentication.PasswordExpirationLoginModule" flag="required"> <module-option name="dsJndiName">java:/OracleDS</module-option> <module-option name="passwordQuery"> select date_of_creation, date_of_expiration from (select pw.* from sec_password pw, sec_user u where u.login_name = UPPER(?) and pw.user_id = u.id order by pw.date_of_creation desc) where rownum=1 </module-option> <module-option name="checkEnabledQuery"> select v.boolean_value from config_key k, config_value v where k.key = 'Options.system.settings.pwExpireEnabled' and v.key = k.id </module-option> <module-option name="checkFirstLoginQuery"> select v.boolean_value from config_key k, config_value v where k.key = 'Options.system.settings.enableFirstLogin' and v.key = k.id </module-option> <module-option name="passAgeQuery"> select v.integer_value from config_key k, config_value v where k.key = 'Options.system.settings.pwExpireAge' and v.key = k.id </module-option> <module-option name="notifyPeriodQuery"> select v.integer_value from config_key k, config_value v where k.key = 'Options.system.settings.pwNotifyPeriod' and v.key = k.id </module-option> </login-module> <login-module code="com.alpha.security.authentication.RoleMappingLoginModule" flag="required"> <module-option name="mapping"> 。。。 </module-option> </login-module> <login-module code="com.alpha.security.authentication.TokenCreatingLoginModule" flag="required"/> </authentication> </application-policy>
jboss.security - service=XMLLoginConfig
displayAppConfig( "authnservice" )
安全域信息:
登录模块可以从属性文件、或数据库中加载安全信息(密码、角色)。
前面的例子中,登录模块是自定义的。
JBoss SX提供了多个登录模块,其中UsersRolesLoginModule用于在属性文件中存储用户名和角色信息。
<application-policy name="JBossWS"> <authentication> <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required"> <module-option name="usersProperties">props/jbossws-users.properties</module-option> <module-option name="rolesProperties">props/jbossws-roles.properties</module-option> <module-option name="unauthenticatedIdentity">anonymous</module-option> </login-module> </authentication> </application-policy>
DatabaseServerLoginModule用于在数据库中存储用户名和角色信息:
<login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required"> <module-option name="password-stacking">useFirstPass</module-option> <module-option name="dsJndiName">java:/pacsDS</module-option> <module-option name="principalsQuery">select passwd from users where user_id=?</module-option> <module-option name="rolesQuery">select roles from roles where user_id=?</module-option> <module-option name="hashEncoding">base64</module-option> <module-option name="hashCharset">UTF-8</module-option> <module-option name="hashAlgorithm">SHA-1</module-option> </login-module><dsJndiName>定义了数据源的JNDI名称。
继承javax.security.auth.spi.LoginModule:
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.AccountExpiredException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import org.apache.log4j.Logger; import org.jboss.security.SecurityContextAssociation; public class PasswordExpirationLoginModule implements LoginModule { private static final Logger log = Logger.getLogger(PasswordExpirationLoginModule.class); private final class PasswordDao extends LoginModuleDAO { private final String userName; Timestamp expireDate; Timestamp createDate; private PasswordDao(String userName) { super(dsJndiName); this.userName = userName; } @Override public void innerRun(Connection conn, PreparedStatement ps, ResultSet rs) throws SQLException, LoginException { ps = conn.prepareStatement(passwordQuery); ps.setString(1, userName); rs = ps.executeQuery(); if (rs.next()) { expireDate = rs.getTimestamp("date_of_expiration"); createDate = rs.getTimestamp("date_of_creation"); } } } private final class SettingDAO<T> extends LoginModuleDAO { private final String query; private T data; private final Class<T> type; private SettingDAO(String query, Class<T> type) { super(dsJndiName); this.query = query; this.type = type; } @Override public void innerRun(Connection conn, PreparedStatement ps, ResultSet rs) throws SQLException, LoginException { ps = conn.prepareStatement(query); rs = ps.executeQuery(); if (rs.next()) { if(type==Integer.class) { data = (T) Integer.valueOf(rs.getInt(1)); }else if(type==Boolean.class) { data = (T) Boolean.valueOf(rs.getBoolean(1)); }else if(type==Timestamp.class) { data = (T) rs.getTimestamp(1); }else { data = (T) rs.getObject(1); } } } public T getResult() { return data; } } /** * Flag to allow ignore "about to expire" checking */ public static final String IGNORE_EXPIRED = "ignore_expired"; //mili seconds in a day private static final long timeADay = 86400000L; private String dsJndiName; //get pw creation date and expiration date private String passwordQuery; private String checkEnabledQuery; private String checkFirstLoginQuery; private String notifyPeriodQuery; private String passAgeQuery; private CallbackHandler callbackHandler; private CallbackHelper helper = new CallbackHelper(); @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { this.callbackHandler = callbackHandler; dsJndiName = (String) options.get("dsJndiName"); passwordQuery = (String) options.get("passwordQuery"); checkEnabledQuery = (String) options.get("checkEnabledQuery"); checkFirstLoginQuery = (String) options.get("checkFirstLoginQuery"); notifyPeriodQuery = (String) options.get("notifyPeriodQuery"); passAgeQuery = (String) options.get("passAgeQuery"); } @Override public boolean login() throws LoginException { if(log.isDebugEnabled()) { log.debug("do expire check"); } final String userName = helper.getUsername(callbackHandler); PasswordDao dao = new PasswordDao(userName); dao.run(); if(isCheckFirstLoginEnabled()) { if(dao.expireDate!=null && dao.expireDate.equals(dao.createDate)) { throw new FirstLoginException(); } } if(isCheckEnabled()) { long passAge = getPassAge() * timeADay; SettingDAO<Timestamp> timeDao = new SettingDAO<Timestamp>("select systimestamp from dual", Timestamp.class); timeDao.run(); Timestamp now = timeDao.getResult(); if(dao.expireDate!=null) { log.warn("expiration date has been set for current password."); long time = dao.expireDate.getTime() - dao.createDate.getTime(); if(time>0) { passAge = Math.min(passAge, time); } } long currentAge = now.getTime() - dao.createDate.getTime(); if(currentAge >= passAge) { throw new AccountExpiredException(); } //ignore about to expire Boolean isIgnore = (Boolean)(SecurityContextAssociation .getSecurityContext().getData().get(IGNORE_EXPIRED)); if(isIgnore!=null && isIgnore) { return false; } long notifiPeriod = getNotifyPeriod() * timeADay; if(currentAge >= passAge - notifiPeriod) { long time = passAge -currentAge; int day = (int)(time / timeADay + (time % timeADay==0 ? 0 : 1)); throw new AccountToExpireException(day); } } return false; } private boolean isCheckFirstLoginEnabled() throws LoginException { SettingDAO<Boolean> dao = new SettingDAO(checkFirstLoginQuery, Boolean.class); dao.run(); Boolean result = dao.getResult(); return result; } private int getNotifyPeriod() throws LoginException{ SettingDAO<Integer> dao = new SettingDAO(notifyPeriodQuery, Integer.class); dao.run(); Integer data = dao.getResult(); if(data==null) { log.warn("setting for notify period is not found, use hardcode value 14"); return 14; } return data; } private int getPassAge() throws LoginException { SettingDAO<Integer> dao = new SettingDAO(passAgeQuery, Integer.class); dao.run(); Integer data = dao.getResult(); if(data==null) { log.warn("setting for password expired age is not found"); return 42; } return data; } private boolean isCheckEnabled() throws LoginException{ SettingDAO<Boolean> dao = new SettingDAO(checkEnabledQuery, Boolean.class); dao.run(); Boolean result = dao.getResult(); return result; } @Override public boolean commit() throws LoginException { return false; } @Override public boolean abort() throws LoginException { return false; } @Override public boolean logout() throws LoginException { return false; } }