Yale CAS SSO + Alfresco 2.9.0b + Tomcat 5.5 的整合步骤

来源:互联网 发布:linux 网络唤醒 编辑:程序博客网 时间:2024/06/05 06:15
Alfresco,这个强大的开源内容管理项目,功能不用说了,关于其配置可参考官方的wiki资源和论坛,很详细!!!这里主要是结合Yale CAS做一个总结整理。

1.Alfresco与SSO:

     官方说明,Alfresco针对SSO具有良好的扩展性,主要体现在使用NTLM SSO认证,这个设置我做了一个星期都没有设置成功。与CAS SSO集成还算顺利,这里主要参考官方一个追踪单元:http://issues.alfresco.com/browse/AWC-952,其中工程师Andy做了解释:
Andy Hind - 28-Jun-07 08:14 PM A configurable filter (HTTPRequestAuthenticationFilter) has been added that should support this and other cases.
 
BTW

at org.alfresco.repo.security.authentication.AuthenticationComponentImpl.getUserDetails(AuthenticationComponentImpl.java:98)

Shows the alfresco authentication component is still configured.
You should use the example deny configuration in the config using SimpleAcceptOrRejectAllAuthenticationComponentImpl.
This supports setting the authenticated user without invoking the alfresco specific DAO for users which will not know about your users - but will check they exist. The filter is not doing this check in a user transaction. In previous versions there would have been auto transaction wrapping.

Thanks for providing the config settings for CAS

通过这个用例,我做了测试,在Alfresco中结合SSO,必须使用SimpleAcceptOrRejectAllAuthenticationComponentImpl作为AuthenticationComponent(认证组件),于是就可以实现了。

2.安装Yale CAS Server端

这里不用总结了,专门的Yale CAS Server总结另外我再写。

3.下载AlfrescoCommunity 2.9.0b

根据Use this defintion for Novell IChain integration,在authentication-services-context.xml中,使用Simple Authentication component如下:
<bean id="authenticationComponent" class="org.alfresco.repo.security.authentication.SimpleAcceptOrRejectAllAuthenticationComponentImpl">
        
<property name="accept">
            
<value>true</value>
        
</property>
    
</bean>

注释掉如下:

<!--
    <bean id="authenticationComponent" class="org.alfresco.repo.security.authentication.AuthenticationComponentImpl">
        <property name="authenticationDao">
            <ref bean="authenticationDao" />
        </property>
        <property name="authenticationManager">
            <ref bean="authenticationManager" />
        </property>
        <property name="allowGuestLogin">
            <value>true</value>
        </property>
    </bean>
    
-->

4.参照webclient中NovellIChainsHTTPRequestAuthenticationFilter.java

写一个CasAuthenticationFilter如下:
/*
 * Copyright (C) 2005-2007 Alfresco Software Limited.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

 * As a special exception to the terms and conditions of version 2.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * and Open Source Software ("FLOSS") applications as described in Alfresco's
 * FLOSS exception.  You should have recieved a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * 
http://www.alfresco.com/legal/licensing
 
*/

package org.alfresco.web.app.servlet;

import java.io.IOException;
import java.util.List;
import java.util.Locale;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.transaction.UserTransaction;

import org.alfresco.config.ConfigService;
import org.alfresco.i18n.I18NUtil;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.LoginBean;
import org.alfresco.web.bean.repository.User;
import org.alfresco.web.config.LanguagesConfigElement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.alfresco.web.bean.repository.Repository;

import edu.yale.its.tp.cas.client.filter.CASFilter;

/**
 * Sample authentication for CAS.
 *
 * 
@author Andy Hind
 * 
@author Laurent Meunier <l.meunier@atolcd.com>
 
*/

public class CasAuthenticationFilter extends AbstractAuthenticationFilter
        
implements Filter {
    
private static final String LOCALE = "locale";
    
public static final String MESSAGE_BUNDLE = "alfresco.messages.webclient";
    
private static Log logger = LogFactory
            .getLog(CasAuthenticationFilter.
class);
    
private ServletContext context;
    
private String loginPage;
    
private AuthenticationComponent authComponent;
    
private AuthenticationService authService;
    
private TransactionService transactionService;
    
private PersonService personService;
    
private NodeService nodeService;
    
private List<String> m_languages;

    
public CasAuthenticationFilter() {
        
super();
    }


    
public void destroy() {
        
// Nothing to do
    }


    
/**
     * Run the filter
     *
     * 
@param sreq
     *            ServletRequest
     * 
@param sresp
     *            ServletResponse
     * 
@param chain
     *            FilterChain
     * 
@exception IOException
     * 
@exception ServletException
     
*/

    
public void doFilter(ServletRequest sreq, ServletResponse sresp,
            FilterChain chain) 
throws IOException, ServletException {
        
// Get the HTTP request/response/session
        HttpServletRequest req = (HttpServletRequest) sreq;
        HttpServletResponse resp 
= (HttpServletResponse) sresp;

        HttpSession httpSess 
= req.getSession(true);

        String userName 
= null;
        Object o 
= httpSess.getAttribute(CASFilter.CAS_FILTER_USER);
        
if (o == null{
            logger.error(
"CAS : CASFilter.CAS_FILTER_USER == NULL");
        }
 else {
            userName 
= o.toString();
        }


        
if (logger.isDebugEnabled()) {
            logger.debug(
"CAS : User = " + userName);
        }


        
// See if there is a user in the session and test if it matches
        User user = (User) httpSess
                .getAttribute(AuthenticationHelper.AUTHENTICATION_USER);

        
if (user != null{
            
try {
                
// Debug
                if (logger.isDebugEnabled())
                    logger.debug(
"CAS : User " + user.getUserName()
                            
+ " validate ticket");

                
if (user.getUserName().equals(userName)) {
                    authComponent.setCurrentUser(user.getUserName());
                    I18NUtil.setLocale(Application.getLanguage(httpSess));
                    chain.doFilter(sreq, sresp);
                    
return;
                }
 else {
                    
// No match
                    setAuthenticatedUser(req, httpSess, userName);
                }

            }
 catch (AuthenticationException ex) {
                
if (logger.isErrorEnabled())
                    logger.error(
"Failed to validate user "
                            
+ user.getUserName(), ex);
            }

        }


        setAuthenticatedUser(req, httpSess, userName);

        
// Redirect the login page as it is never seen as we always login by
        
// name
        if (req.getRequestURI().endsWith(getLoginPage()) == true{
            
if (logger.isDebugEnabled())
                logger.debug(
"Login page requested, chaining ...");

            resp.sendRedirect(req.getContextPath()
                    
+ "/faces/jsp/browse/browse.jsp");
            
return;
        }
 else {
            chain.doFilter(sreq, sresp);
            
return;
        }

    }


    
/**
     * Set the authenticated user.
     *
     * It does not check that the user exists at the moment.
     *
     * 
@param req
     * 
@param httpSess
     * 
@param userName
     
*/

    
private void setAuthenticatedUser(HttpServletRequest req,
            HttpSession httpSess, String userName) 
{
        
// Set the authentication
        authComponent.setCurrentUser(userName);

        
// Set up the user information
        UserTransaction tx = transactionService.getUserTransaction();
        NodeRef homeSpaceRef 
= null;
        User user;
        
try {
            tx.begin();
            user 
= new User(userName, authService.getCurrentTicket(),
                    personService.getPerson(userName));
            homeSpaceRef 
= (NodeRef) nodeService.getProperty(personService
                    .getPerson(userName), ContentModel.PROP_HOMEFOLDER);
            
if (homeSpaceRef == null{
                logger.warn(
"Home Folder is null for user '" + userName
                        
+ "', using company_home.");
                homeSpaceRef 
= (NodeRef) nodeService.getRootNode(Repository
                        .getStoreRef());
            }

            user.setHomeSpaceId(homeSpaceRef.getId());
            tx.commit();
        }
 catch (Throwable ex) {
            logger.error(ex);

            
try {
                tx.rollback();
            }
 catch (Exception ex2) {
                logger.error(
"Failed to rollback transaction", ex2);
            }


            
if (ex instanceof RuntimeException) {
                
throw (RuntimeException) ex;
            }
 else {
                
throw new RuntimeException("Failed to set authenticated user",
                        ex);
            }

        }


        
// Store the user
        httpSess.setAttribute(AuthenticationHelper.AUTHENTICATION_USER, user);
        httpSess.setAttribute(LoginBean.LOGIN_EXTERNAL_AUTH, Boolean.TRUE);

        
// Set the current locale from the Accept-Lanaguage header if available
        Locale userLocale = parseAcceptLanguageHeader(req, m_languages);

        
if (userLocale != null{
            httpSess.setAttribute(LOCALE, userLocale);
            httpSess.removeAttribute(MESSAGE_BUNDLE);
        }


        
// Set the locale using the session
        I18NUtil.setLocale(Application.getLanguage(httpSess));

    }


    
public void init(FilterConfig config) throws ServletException {
        
this.context = config.getServletContext();
        WebApplicationContext ctx 
= WebApplicationContextUtils
                .getRequiredWebApplicationContext(context);
        ServiceRegistry serviceRegistry 
= (ServiceRegistry) ctx
                .getBean(ServiceRegistry.SERVICE_REGISTRY);
        transactionService 
= serviceRegistry.getTransactionService();
        nodeService 
= serviceRegistry.getNodeService();

        authComponent 
= (AuthenticationComponent) ctx
                .getBean(
"authenticationComponent");
        authService 
= (AuthenticationService) ctx
                .getBean(
"authenticationService");
        personService 
= (PersonService) ctx.getBean("personService");

        
// Get a list of the available locales
        ConfigService configServiceService = (ConfigService) ctx
                .getBean(
"webClientConfigService");
        LanguagesConfigElement configElement 
= (LanguagesConfigElement) configServiceService
                .getConfig(
"Languages").getConfigElement(
                        LanguagesConfigElement.CONFIG_ELEMENT_ID);

        m_languages 
= configElement.getLanguages();
    }


    
/**
     * Return the login page address
     *
     * 
@return String
     
*/

    
private String getLoginPage() {
        
if (loginPage == null{
            loginPage 
= Application.getLoginPage(context);
        }


        
return loginPage;
    }

}


5.设置web.xml,加入cas filter如下:

<!-- CAS SSO integration -->
  
<filter>
    
<filter-name>CAS Required</filter-name>
    
<filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>
<init-param>
<param-name>logout_url</param-name>
<param-value>https://localhost:8883/cas-server/logout&lt;/param-value>
</init-param>
<!--init-param>
<param-name>edu.yale.its.tp.cas.client.filter.serviceUrl</param-name>
<param-value>http://localhost:8082/...&lt;/param-value>
</init-param
-->
    
<init-param>
        
<param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
        
<param-value>https://localhost:8883/cas-server/login&lt;/param-value>
    
</init-param>
    
<init-param>
        
<param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
        
<param-value>https://localhost:8883/cas-server/serviceValidate&lt;/param-value>
    
</init-param>
    
<init-param>
        
<param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
        
<param-value>localhost:8082</param-value>
    
</init-param>
  
</filter>

<filter>
      
<filter-name>Authentication Filter</filter-name>
      
<!--filter-class>org.alfresco.web.app.servlet.AuthenticationFilter</filter-class-->
      
<filter-class>org.alfresco.web.app.servlet.CASAuthenticationFilter</filter-class>
   
</filter>
   
<filter>
      
<filter-name>WebDAV Authentication Filter</filter-name>
      
<filter-class>org.alfresco.repo.webdav.auth.AuthenticationFilter</filter-class>
   
</filter>
   
<filter>
      
<filter-name>Admin Authentication Filter</filter-name>
      
<filter-class>org.alfresco.web.app.servlet.CASAuthenticationFilter</filter-class>
      
<!--filter-class>org.alfresco.web.app.servlet.AdminAuthenticationFilter</filter-class-->
   
</filter>

<filter-mapping>
      
<filter-name>CAS Required</filter-name>
      
<url-pattern>/faces/*</url-pattern>
   
</filter-mapping>
   
<filter-mapping>
                  
<filter-name>CAS Required</filter-name>
                  
<url-pattern>/template/*</url-pattern>
    
</filter-mapping>
     
<filter-mapping>
                  
<filter-name>CAS Required</filter-name>
                  
<url-pattern>/download/*</url-pattern>
      
</filter-mapping>

<filter-mapping>
      
<filter-name>Authentication Filter</filter-name>
      
<url-pattern>/faces/*</url-pattern>
   
</filter-mapping>

这里,如果不进行第三步,系统测试会出现如下异常:
org.alfresco.error.AlfrescoRuntimeException: Transaction must be active and synchronization is required
at org.alfresco.repo.transaction.AlfrescoTransactionSupport.registerSynchronizations(AlfrescoTransactionSupport.java:371)
at org.alfresco.repo.transaction.AlfrescoTransactionSupport.getSynchronization(AlfrescoTransactionSupport.java:356)
at org.alfresco.repo.transaction.AlfrescoTransactionSupport.bindDaoService(AlfrescoTransactionSupport.java:210)
at org.alfresco.repo.transaction.TransactionalDaoInterceptor.invoke(TransactionalDaoInterceptor.java:66)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:170)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:176)
at $Proxy1.getNode(Unknown Source)
.........
这里主要是利用org.alfresco.web.app.servlet.CASAuthenticationFilter替换org.alfresco.web.app.servlet.AdminAuthenticationFilter,这是关键处理!

6.使用了SSO,建议最好将CIFS和FTP服务关闭,因为在Afresco与SSO集成后,Alfresco作为客户应用端就无需做密码认证了,我在设置CIFS时失败,FTP时可以成功,但这也是危险的,当然可以在CIFS和FTP另外也强制进行密码认证处理。