基于CAS SHIRO LDAP 的SSO单点登录

本文章主要讲解的是如何配置CAS SHIRO LDAP域验证数据的SSO,

首先给出以下sso的验证流程图:

基于CAS SHIRO LDAP 的SSO单点登录


1:首先需要从网上下载CAS.war登录包,找到对应目录WEB-INFO下的deployerConfigContext.xml

基于CAS SHIRO LDAP 的SSO单点登录

2:修改配置文件中id="authenticationManager"

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:sec="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
        <constructor-arg>
            <map>
          
                <entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
                <entry key-ref="ldapAuthHandler" value-ref="proxyPrincipalResolver"/>
            </map>
        </constructor-arg>

        <property name="authenticationPolicy">
            <bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" />
        </property>
    </bean>
    
    <bean id="ldapAuthHandler"
         class="org.jasig.cas.authentication.LdapAuthenticationHandler"
         p:principalIdAttribute="sAMAccountName"
         c:authenticator-ref="authenticator">
       <property name="principalAttributeMap">
           <map>
                <entry key="sAMAccountName" value="loginName" />
                <entry key="department" value="department" />
                <entry key="mobile" value="mobile" />
                <entry key="name" value="name" />
                <entry key="company" value="company" />
                <entry key="title" value="title" />
                <entry key="userAccountControl" value="userAccountControl" />
                <entry key="userPrincipalName" value="userPrincipalName" />
                <entry key="email" value="email" />
                <entry key="memberOf" value="memberOf" />
            </map>
       </property>
   </bean>
             
   <bean id="authenticator" class="org.ldaptive.auth.Authenticator"
        c:resolver-ref="dnResolver" c:handler-ref="authHandler"
        p:entryResolver-ref="activeDirectoryEntryResolver">
        <property name="authenticationResponseHandlers">
            <util:list>
                <bean
                    class="org.ldaptive.auth.ext.ActiveDirectoryAuthenticationResponseHandler" />
            </util:list>
        </property>
    </bean>

    <bean id="activeDirectoryEntryResolver" class="org.ldaptive.auth.SearchEntryResolver"
            p:baseDn="dc=ZJHT,dc=com" p:userFilter="sAMAccountName={user}"
            p:connectionFactory-ref="pooledLdapConnectionFactory" p:subtreeSearch="true" />

        <bean id="dnResolver"
              class="org.ldaptive.auth.FormatDnResolver"
              c:format="%[email protected]" />   <!--根据自己的LDAP内容来配置-->


        <bean id="authHandler" class="org.ldaptive.auth.PooledBindAuthenticationHandler"
              p:connectionFactory-ref="pooledLdapConnectionFactory" />


        <bean id="pooledLdapConnectionFactory"
              class="org.ldaptive.pool.PooledConnectionFactory"
              p:connectionPool-ref="connectionPool" />


        <bean id="connectionPool"
              class="org.ldaptive.pool.BlockingConnectionPool"
              init-method="initialize"
              p:poolConfig-ref="ldapPoolConfig"
              p:blockWaitTime="3000"
              p:validator-ref="searchValidator"
              p:pruneStrategy-ref="pruneStrategy"
              p:connectionFactory-ref="connectionFactory" />


        <bean id="ldapPoolConfig" class="org.ldaptive.pool.PoolConfig"
              p:minPoolSize="3"
              p:maxPoolSize="10"
              p:validateOnCheckOut="false"
              p:validatePeriodically="true"
              p:validatePeriod="300" />


        <bean id="connectionFactory" class="org.ldaptive.DefaultConnectionFactory"
              p:connectionConfig-ref="connectionConfig" />


        <bean id="connectionConfig" class="org.ldaptive.ConnectionConfig"
              p:ldapUrl="ldap://172.16.111.2:3268"  
              p:connectTimeout="3000"    
              p:useStartTLS="false"
              p:connectionInitializer-ref="bindConnectionInitializer"   /><!--上面内容根据自己的LDAP内容来配置-->
              
        <bean id="bindConnectionInitializer" class="org.ldaptive.BindConnectionInitializer"
            p:bindDn="[email protected]">
            <property name="bindCredential">
                <bean class="org.ldaptive.Credential" c:password="888888" />
            </property>
        </bean>
        
        <bean id="pruneStrategy" class="org.ldaptive.pool.IdlePruneStrategy"
              p:prunePeriod="300"
              p:idleTime="600" />


        <bean id="searchValidator" class="org.ldaptive.pool.SearchValidator" />
  
    <bean id="proxyAuthenticationHandler"
          class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
          p:httpClient-ref="httpClient" />

    <bean id="primaryAuthenticationHandler"
          class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
        <property name="users">
            <map>
                <entry key="casuser" value="Mellon"/>
            </map>
        </property>
    </bean>
    <!-- Required for proxy ticket mechanism -->
    <bean id="proxyPrincipalResolver"
          class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver" >
          <property name="attributeRepository" ref="attributeRepository" />
    </bean>


    <bean id="primaryPrincipalResolver"
          class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver" >
        <property name="attributeRepository" ref="attributeRepository" />
    </bean>


    
    <!-- 查询控制 -->
    <bean class="javax.naming.directory.SearchControls" id="searchControls">
        <property name="countLimit" value="100" />
        <property name="searchScope">
            <util:constant static-field="javax.naming.directory.SearchControls.SUBTREE_SCOPE" />
        </property>
        <property name="timeLimit" value="5000" />
        <property name="returningObjFlag" value="true" />
    </bean>
    
    <bean id="attributeRepository"
        class="org.jasig.cas.persondir.LdapPersonAttributeDao"
        p:connectionFactory-ref="pooledLdapConnectionFactory"
        p:searchControls-ref="searchControls" p:baseDN="dc=ZJHT,dc=com"
        p:searchFilter="sAMAccountName={0}">
        <property name="requireAllQueryAttributes" value="true" />
        <property name="queryAttributeMapping">
            <map>
                <entry key="username" value="sAMAccountName" />
            </map>
        </property>
        <property name="resultAttributeMapping">
            <map>
                <entry key="sAMAccountName" value="loginName" />
                <entry key="department" value="department" />
                <entry key="mobile" value="mobile" />
                <entry key="name" value="name" />
                <entry key="company" value="company" />
                <entry key="title" value="title" />
                <entry key="userAccountControl" value="userAccountControl" />
                <entry key="userPrincipalName" value="userPrincipalName" />
                <entry key="email" value="email" />
                <entry key="memberOf" value="memberOf" />
            </map>
        </property>
    </bean>

    <bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl"
            p:registeredServices-ref="registeredServicesList" />

    <util:list id="registeredServicesList">
        <bean class="org.jasig.cas.services.RegexRegisteredService"
              p:id="0" p:name="HTTP and IMAP" p:description="Allows HTTP(S) and IMAP(S) protocols"
              p:serviceId="^(https?|imaps?)://.*" p:evaluationOrder="10000001" />
    </util:list>
    
    <bean id="auditTrailManager" class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager" />
    
    <bean id="healthCheckMonitor" class="org.jasig.cas.monitor.HealthCheckMonitor" p:monitors-ref="monitorsList" />
 
    <util:list id="monitorsList">
      <bean class="org.jasig.cas.monitor.MemoryMonitor" p:freeMemoryWarnThreshold="10" />
 
      <bean class="org.jasig.cas.monitor.SessionMonitor"
          p:ticketRegistry-ref="ticketRegistry"
          p:serviceTicketCountWarnThreshold="5000"
          p:sessionCountWarnThreshold="100000" />
    </util:list>
</beans>

以上是配置了cas ldap的配置文件。


3:配置cas shiro

本次使用的是shiro权限认证,并附上代码和cas调用的shiro.xml

public class CasRealm extends org.apache.shiro.cas.CasRealm {

    private static final Logger logger = LoggerFactory.getLogger(CasRealm.class);

    @Autowired
    private IShiroService shiroService;
    
    /**
     * 认证回调函数,登录时调用.
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        CasToken token = (CasToken) authcToken;
        
        AuthenticationInfo info = super.doGetAuthenticationInfo(authcToken);
        String userName = info.getPrincipals().fromRealm(getName()).iterator().next().toString();
        logger.info("doGetAuthenticationInfo*************************" + userName + "#" + token.getCredentials());

        SysUser user = shiroService.getUser(userName);
        
        if (user == null) {
            logger.warn("未查询到相关用户[username:{}]", userName);
            throw new NotFoundUserException("未查询到相关用户");
        } else if(Status.ENABLED.getValue().byteValue() != user.getStatus().byteValue()){
            logger.warn("用户已禁用[username:{}]", userName);
            throw new DisabledAccountException();
        }
        
        ServletRequest request = ((WebSubject)SecurityUtils.getSubject()).getServletRequest();
        HttpServletRequest rq = (HttpServletRequest)request;
        String url = "http://"+rq.getServerName()+":"+rq.getServerPort()+rq.getContextPath()+"/logout";
        System.out.println(url);
        Serializable sessionId = SecurityUtils.getSubject().getSession().getId();
        System.out.println(sessionId);
        SysLoginControl sysLoginControl = new SysLoginControl();
        sysLoginControl.setLoginName(userName);
        sysLoginControl.setSessionId(sessionId.toString());
        sysLoginControl.setUrl(url);
        sysLoginControl.setDomainCode(rq.getContextPath());
        sysLoginControl.setTicket(info.getCredentials().toString());
        SecurityUtils.getSubject().getSession().setAttribute("ticket", info.getCredentials().toString());
        SecurityUtils.getSubject().getSession().setAttribute("userName", userName);
        shiroService.saveLoginControl(sysLoginControl);
        
        List<SysResource> roleList = shiroService.getroleList(user);
        System.out.println(roleList);
        SecurityUtils.getSubject().getSession().setAttribute("roleList", roleList);
        
        PrincipalCollection principalCollection = new SimplePrincipalCollection(user, getName());
        return new SimpleAuthenticationInfo(principalCollection, info.getCredentials());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SysUser user = (SysUser) principals.getPrimaryPrincipal();
        
        SimpleAuthorizationInfo authInfo = shiroService.getShiro(user);
        
        return authInfo;    
    }

}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="  
     http://www.springframework.org/schema/beans   
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
     http://www.springframework.org/schema/context   
     http://www.springframework.org/schema/context/spring-context-3.0.xsd  
     http://www.springframework.org/schema/util  
     http://www.springframework.org/schema/util/spring-util-3.0.xsd"
    default-lazy-init="true">
    
    <bean id="filterChainDefinitionService" class="com.vip.auth.security.impl.FilterChainDefinitionServiceImpl" depends-on="vpmSessionManager">
        <property name="domainCode" value="${PMS_PORTAL_DOMAIN_CODE}"></property>
        <property name="filterChainDefinitionsTpl">
            <value>
                /static/** = anon
                /shiro-cas = casFilter
                <!-- /login = anon -->
                /logout = logoutFilter
                /public/** = anon
                /error/** = anon
                /api/** = anon
                /_health_check.jsp = anon
                #auth#
                /** = authc
            </value>
        </property>
    </bean>

    
    <!-- <bean id="portalAuthFilter" class="com.vip.pms.admin.web.security.PortalFormAuthFilter"></bean> -->
    <!-- Shiro Filter -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" lazy-init="true">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="${cas.casServerUrlPrefix}/login?service=${PMS_PORTAL_WEB_CONTEXT}/shiro-cas" />
        <property name="successUrl" value="/index" />
        <property name="unauthorizedUrl" value="/unauthorized"></property>
        <property name="filters">
            <map>
                <!-- 添加casFilter到shiroFilter -->
                <entry key="casFilter" value-ref="casFilter" />
                <entry key="logoutFilter" value-ref="logoutFilter" />
            </map>
        </property>
        <property name="filterChainDefinitions" value="#{filterChainDefinitionService.loadFilterChainDefinitions()}"> </property>
        
    </bean>


    <!-- Shiro's main business-tier object for web-enabled applications -->
    <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:/conf/cache/ehcache-shiro.xml" />
    </bean>

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="shiroCacheManager" />
        <property name="realm" ref="shiroCasRealm" />
        <property name="subjectFactory" ref="casSubjectFactory" />
    </bean>

    <!-- =====================Cas认证====================== -->
    <bean id="casFilter" class="com.vip.auth.security.filter.PortalCasFilter">
        <!-- 配置验证错误时的失败页面 -->
        <property name="failureUrl" value="/error/error_login" />
    </bean>
    
    <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        <!-- 配置登出页面 -->
        <property name="redirectUrl" value="${cas.casServerUrlPrefix}/logout?service=${PMS_PORTAL_WEB_CONTEXT}/index" />
    </bean>
    
    
    
    <bean id="shiroCasRealm" class="com.vip.auth.security.realm.CasRealm">
        <property name="authenticationService" ref="authenticationService"></property>
        <property name="defaultRoles" value="ROLE_USER" />
        <property name="casServerUrlPrefix" value="${cas.casServerUrlPrefix}" />
        <!-- 客户端的回调地址设置,必须和下面的shiro-cas过滤器拦截的地址一致 -->
        <property name="casService" value="${PMS_PORTAL_WEB_CONTEXT}/shiro-cas" />
    </bean>
    <!-- 如果要实现cas的remember me的功能,需要用到下面这个bean,并设置到securityManager的subjectFactory中 -->
    <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />


    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
        <property name="arguments" ref="securityManager" />
    </bean>
</beans>