基于CAS SHIRO LDAP 的SSO单点登录
本文章主要讲解的是如何配置CAS SHIRO LDAP域验证数据的SSO,
首先给出以下sso的验证流程图:
1:首先需要从网上下载CAS.war登录包,找到对应目录WEB-INFO下的deployerConfigContext.xml
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>