【JBoss】3. JBoss SX安全框架

JBoss SX

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>

JMX查看安全域

jboss.security - service=XMLLoginConfig


displayAppConfig( "authnservice" )


安全域信息:


登录模块UsersRolesLoginModule

登录模块可以从属性文件、或数据库中加载安全信息(密码、角色)。

前面的例子中,登录模块是自定义的。

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

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;
	}

}













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值