论坛首页 Java企业应用论坛

myfaces1.x,seam1.2.1和spring security 2.0(acegi)的集成

浏览 2726 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-12-06   最后修改:2009-01-08
acegi升级为spring security2.0后配置有很大不同,按照老的配置方式需要更改很多包路径,以及属性名,这需要在源代码中查找,如果配置好是没有问题的。新的配置是基于老的方式并进行了简化,就直接给出我的配置作为参考:
新的配置方式:
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:security="http://www.springframework.org/schema/security"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
              http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">  

	<security:http auto-config="true" access-denied-page="/login.faces">
		<security:intercept-url pattern="/jsp/*/yourpath" access="ROLE_COMMISSAR, ROLE_DRAFTOFFICE, ROLE_TRANSACT, ROLE_DRAFTSUPER, ROLE_URGER"/>
		<security:intercept-url pattern="/jsp/*/yourpath" access="ROLE_LETTERPUBLIC,ROLE_LETTERTRANSACT,ROLE_LETTERTASTER,ROLE_LETTERPROCESS"/>
		<security:intercept-url pattern="/jsp/*/yourpath" access="ROLE_POPULARSUBMIT,ROLE_POPULARPROCESS"/>
		<security:intercept-url pattern="/**/*.gif" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<security:intercept-url pattern="/**/*.gif" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<security:intercept-url pattern="/**/*.jpg" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<security:intercept-url pattern="/**/*.css" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<security:intercept-url pattern="/login.faces" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
		<security:form-login 
			default-target-url="/"
			always-use-default-target="true"
			login-page="/login.faces"
			login-processing-url="/j_spring_security_check"
			authentication-failure-url="/login.faces" 
			/>
		<security:anonymous key="doesNotMatter" username="anonymousUser"/>
		 <security:concurrent-session-control 
			max-sessions="1" 
			exception-if-maximum-exceeded="false"
			expired-url="/login.faces"/>
		<security:logout logout-url="/j_spring_security_logout" logout-success-url="/login.faces" />
	</security:http>
	
	<security:authentication-provider>
		<security:jdbc-user-service 
			data-source-ref="dataSource" 
			users-by-username-query="SELECT USER_, PASSWORD_, SIGN_ FROM ORM_USER WHERE USER_= ?"
			authorities-by-username-query="select user0_.USER_ as col_0_0_, role2_.VALUE_ as col_1_0_ from ORM_USER user0_  inner join ORM_USER_ROLE roles1_ on user0_.ID_=roles1_.USERID_ inner join ORM_ROLE role2_ on roles1_.ROLEID_=role2_.ID_ where user0_.USER_=?"
			cache-ref="userCache"	
			/>
	</security:authentication-provider>
	
	<bean id="userCache" class="org.springframework.security.providers.dao.cache.EhCacheBasedUserCache">
		<property name="cache"><ref local="userCacheBackend"/></property>
	</bean>	
	
	<bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
		<property name="cacheManager"> <ref local="cacheManager"/> </property>
		<property name="cacheName"> <value>userCache</value> </property>
	</bean>
	
	<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
	
	<!-- This bean is optional; it isn't used by any other bean as it only listens and logs -->
	<bean id="loggerListener" class="org.springframework.security.event.authentication.LoggerListener" />
	
	<!-- <bean id="customAuthenticationFilter" class="**.authorization.CustomAuthenticationFilter">
		<security:custom-filter position="AUTHENTICATION_PROCESSING_FILTER"/>
	</bean>  使用自定义的过滤器,参考spring security中不同的自定义位置名称--> 
	
</beans>


jsf集成:

jsf跳转机制使用的是redirect,所以登录的信息就不能很好的传递给acegi,根据myfaces wiki提供的方式:创建登录使用的loginBean,注意不要使用seam的@Name注册为seam组件,直接使用jsf的managed-bean机制注册成backingbean
	public String login() throws IOException, ServletException
    {
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
        
        RequestDispatcher dispatcher = ((ServletRequest) context.getRequest())
                 .getRequestDispatcher("/j_spring_security_check");
 
        dispatcher.forward((ServletRequest) context.getRequest(),
                (ServletResponse) context.getResponse());
        FacesContext.getCurrentInstance().responseComplete();
        // It's OK to return null here because Faces is just going to exit.
        logger.debug("=================================================" + 
        		((HttpServletRequest)context.getRequest()).getSession().getId());
        HttpSession session = ((HttpServletRequest)context.getRequest()).getSession();
//        session.setAttribute(LOGIN_PROCESS, new Boolean(true));
       //这是调整seam的集成部分
		ContextControl.instance().begin(session);
		//createAuthData(session, user);
        return null;
    }
	

	
	public String logout() throws IOException, ServletException
    {
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
 
        RequestDispatcher dispatcher = ((ServletRequest) context.getRequest())
                 .getRequestDispatcher("/j_spring_security_logout");
 
        dispatcher.forward((ServletRequest) context.getRequest(),
                (ServletResponse) context.getResponse());

        FacesContext.getCurrentInstance().responseComplete();
        // It's OK to return null here because Faces is just going to exit.
        logger.debug("=================================================" + 
        		((HttpServletRequest)context.getRequest()).getSession().getId());       
        HttpSession session = ((HttpServletRequest)context.getRequest()).getSession();
     //这是调整seam的集成部分
    	ContextControl.instance().begin(session);
        return null;
    }

jsf页面的做法
<t:inputText id="j_username" forceId="true" styleClass="form1" size="20" value="#{loginBean.userName}"></t:inputText>
<t:inputSecret id="j_password" forceId="true" styleClass="form1" size="20" value="#{loginBean.password}"
<h:commandButton value="登录" action="#{loginBean.login}"></h:commandButton>


seam的集成:
因为在登录过程中,acegi注销掉了上一个session,创建了一个新的session,那么SeamListener这个session监听器就会执行sessionDestroyed和sessionCreated,但是seam并不认为你是真正销毁了所有的上下文包括request和application,我认为这是seam为了在页面过期的环境中使用,因为它的原则是由seam的Seam.invalidateSession()来处理session的销毁(事实上根本就没有销毁session而是给出了销毁的标记,也许这样就可以由它的内部解决所有上下文的管理)。那么就会产生异常,同样在这一次请求中seam的阶段监听也会产生异常,因为Conversation绑定的session仍然是已经过期的session。我的做法很傻,hack了!

一:自定义SeamListener
	@Override
	public void sessionCreated(HttpSessionEvent event) {
		// TODO Auto-generated method stub
		super.sessionCreated(event);
	}
	
	@Override
	public void sessionDestroyed(HttpSessionEvent event) {
		// TODO Auto-generated method stub
//		event.getSession().getAttribute("org.jboss.seam.sessionInvalid");
		try {			
			super.sessionDestroyed(event);
		} catch (IllegalStateException e) {
			// TODO: handle exception
			Seam.invalidateSession();
			HttpSession session = event.getSession();
			ContextControl.instance().end(session.getServletContext(), new ServletSessionImpl(session));
		}
	}


二 创建自定义的seam上下文控制类,直接操作各个上下文,对于session销毁和创建手动去清理或者创建其他各上下文实例。因为各个上下文的操作都是protect,所以只有将类建在它的包下了!
package org.jboss.seam.contexts;

import java.util.Set;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.core.ConversationEntries;
import org.jboss.seam.servlet.ServletSessionImpl;

public class ContextControl {
	private static final Log log = LogFactory.getLog(ContextControl.class);
	
	private static final ContextControl contextControl = new ContextControl();
	
	public static ContextControl instance() {
		return contextControl;
	}
	
	public void begin(HttpSession session) {
	      boolean applicationContextActive = Contexts.isApplicationContextActive();
	      boolean eventContextActive = Contexts.isEventContextActive();
	      boolean conversationContextActive = Contexts.isConversationContextActive();
	      if ( !applicationContextActive )
	      {
	         Context tempApplicationContext = new WebApplicationContext(session.getServletContext());
	         Contexts.applicationContext.set(tempApplicationContext);
	      }
	      Context tempEventContext = null;
	      if ( !eventContextActive )
	      {
	         tempEventContext = new MapContext(ScopeType.EVENT);
	         Contexts.eventContext.set(tempEventContext);
	      }
	      Context tempConversationContext = null;
	      if ( !conversationContextActive )
	      {
	         tempConversationContext = new MapContext(ScopeType.CONVERSATION);
	         Contexts.conversationContext.set(tempConversationContext);
	      }

	      Context tempSessionContext = new WebSessionContext(new ServletSessionImpl(session));
	      Contexts.sessionContext.set(tempSessionContext);		
	      
	      //instantiate all session-scoped @Startup components
	      for ( String name : Contexts.getApplicationContext().getNames() )
	      {
	         Object object = Contexts.getApplicationContext().get(name);
	         if ( object!=null && (object instanceof Component) ) 
	         {
	            Component component = (Component) object;
	            if ( component.isStartup() && component.getScope() == ScopeType.SESSION )
	            {
	               startup(component);
	            }
	         }
	      }	      
	}
	
	public void end(ServletContext servletContext, ContextAdaptor session) {
	      
	      Context tempApplicationContext = new WebApplicationContext(servletContext);
	      Contexts.applicationContext.set(tempApplicationContext);

	      //this is used just as a place to stick the ConversationManager
	      Context tempEventContext = new MapContext(ScopeType.EVENT);
	      Contexts.eventContext.set(tempEventContext);

	      //this is used (a) for destroying session-scoped components
	      //and is also used (b) by the ConversationManager
	      Context tempSessionContext = new WebSessionContext(session);
	      Contexts.sessionContext.set(tempSessionContext);

	      Set<String> conversationIds = ConversationEntries.instance().getConversationIds();
	      log.debug("destroying conversation contexts: " + conversationIds);
	      for (String conversationId: conversationIds)
	      {
	         Lifecycle.destroyConversationContext(session, conversationId);
	      }
	      
	      //we need some conversation-scope components for destroying
	      //the session context...
	      Context tempConversationContext = new MapContext(ScopeType.CONVERSATION);
	      Contexts.conversationContext.set(tempConversationContext);

	      log.debug("destroying session context");
	      Contexts.destroy(tempSessionContext);
	      Contexts.sessionContext.set(null);
	      
	      Contexts.destroy(tempConversationContext);
	      Contexts.conversationContext.set(null);

	      Contexts.destroy(tempEventContext);
	      Contexts.eventContext.set(null);

	      Contexts.applicationContext.set(null);		
	}
	
	   private static void startup(Component component)
	   {
	      if ( component.isStartup() )
	      {
	         for ( String dependency: component.getDependencies() )
	         {
	            Component dependentComponent = Component.forName(dependency);
	            if (dependentComponent!=null)
	            {
	               startup( dependentComponent );
	            }
	         }
	      }

	      if ( !component.getScope().getContext().isSet( component.getName() ) ) 
	      {
	         log.info("starting up: " + component.getName());
	         component.newInstance();
	      }
	   }
}


这些只是我的做法!
注意的地方:
websphere 6.1.0.3以上版本默认对于找不到资源的url路径直接过滤到错误页面,这样spring的/j_spring_security_check是访问不到的,需要参考http://topic.csdn.net/u/20080620/14/9c05c7d7-a6a0-4646-95b2-4bdadcb1002c.html
   发表时间:2009-01-08  
最近正在整理以前的安全系统, 没有用过Spring Security, 但是想研究一下; 请教lz一个问题: Spring Security非得绑定Springframework吗?
0 请登录后投票
   发表时间:2009-01-08  
回复ls,一定的
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics