CAS SSO 代码及依赖的jar下载: http://download.csdn.net/detail/nmsbq/9875610
1.导入项目 cas-server-webapp源码导入到idea或eclipse,pom.xml 加入依赖的jar
<!-- https://mvnrepository.com/artifact/org.jasig.cas/cas-server-support-jdbc -->
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>3.5.1</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.22</version>
</dependency>
2.CAS服务器深入配置
上面的初体验仅仅是简单的身份验证,实际应用中肯定是要读取数据库的数据,进一步配置CAS服务器怎么读取数据库的信息进行身份验证。 首先打开deployerConfigContext.xml
配置的地方如下:
找到:SimpleTestUsernamePasswordAuthenticationHandler这个验证Handler,这个是比较简单的,只是判断用户名和密码相同即可通过,这个肯定不能在实际应用中使用,弃用注释掉!
<bean
class=”org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler” />
注释掉后在下面添加下面的代码:
<!-- 变更为JDBC验证方式 -->
<bean id="primaryAuthenticationHandler" class="cn.com.yktour.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="dataSource"></property>
<property name="sql" value="SELECT login_pwd,id,login_name,nick_name from user where login_name = ?"></property>
<property name="passwordEncoder" ref="MD5PasswordEncoder"></property>
</bean>
新的验证实现类 cn.com.yktour.jdbc.QueryDatabaseAuthenticationHandler
根据用户查询用户的SQL SELECT login_pwd,id,login_name,nick_name from table where login_name = ?
注意 你的密码加密方式如果不是简单的MD5 加密,需要重新实现加密方式
在最外层的</bean> 后面添加 数据源配置和MD5加密配置
<!-- 数据源配置 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property> <property name="url"><value>jdbc:mysql://数据库连接地址:3306/db?characterEncoding=utf8</value></property> <property name="username"><value>账号</value></property> <property name="password"><value>密码</value></property> </bean> <!-- 添加MD5密码加密功能 --> <bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"> <constructor-arg index="0"> <value>MD5</value> </constructor-arg> </bean>
QueryDatabaseAuthenticationHandler实现代码
package cn.com.yktour.jdbc; import cn.com.yktour.enity.Member; import cn.com.yktour.util.DefaultPasswordEncoder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jasig.cas.authentication.handler.AuthenticationException; import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.jdbc.core.RowMapper; import org.springside.modules.security.utils.Digests; import org.springside.modules.utils.Encodes; import javax.validation.constraints.NotNull; import java.sql.ResultSet; import java.sql.SQLException; public class QueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler { private final static Log LOG = LogFactory.getLog(QueryDatabaseAuthenticationHandler.class); @NotNull private String sql; @NotNull private DefaultPasswordEncoder passwordEncoder; protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException { LOG.info("=====开始===="); final String username = getPrincipalNameTransformer().transform(credentials.getUsername()); final String password = credentials.getPassword(); try { LOG.info("==========username:"+username + ",password:"+password); MemberRowMapper rm = new MemberRowMapper(); final Member member = getJdbcTemplate().queryForObject(this.sql, rm, username);//查询用户对象 String dbPassword = member.getLoginPwd(); String encryptedPassword = passwordEncoder.encode(password); boolean bl = dbPassword.equals(encryptedPassword); return bl; } catch (final IncorrectResultSizeDataAccessException e) { LOG.error(e.getMessage(),e.fillInStackTrace()); return false; } } /** * @param sql The sql to set. */ public void setSql(final String sql) { this.sql = sql; } public void setPasswordEncoder(DefaultPasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } }
以上完成后把cas项目打成war 改名为 cas.war,复制到 HTTPS comcat 下运行即可
3.配置CAS客户端
在client 客户端 pom.xml添加 jra依赖
<!-- https://mvnrepository.com/artifact/org.jasig.cas.client/cas-client-core --> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.2.2</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
编辑web.xml添加cas相关配置
<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置 --> <listener> <listenerclass> org.jasig.cas.client.session.SingleSignOutHttpSessionListener </listenerclass> </listener> <!-- 该过滤器用于实现单点登出功能,可选配置。 --> <filter> <filtername>CAS Single Sign Out Filter</filtername> <filterclass>org.jasig.cas.client.session.SingleSignOutFilter</filterclass> </filter> <filtermapping> <filtername>CAS Single Sign Out Filter</filtername> <urlpattern>/*</urlpattern> </filtermapping> <!-- 该过滤器负责用户的认证工作,必须启用它 --> <filter> <filtername>CASFilter</filtername> <filterclass>org.jasig.cas.client.authentication.AuthenticationFilter</filterclass> <initparam> <paramname>casServerLoginUrl</paramname> <paramvalue>https://cas.test123.com:8443/cas</paramvalue> </initparam> <!-- 这里的serverName是服务端的IP --> <initparam> <paramname>serverName</paramname> <paramvalue>http://client1.test123:8080</paramvalue> </initparam> </filter> <filtermapping> <filtername>CASFilter</filtername> <urlpattern>/*</urlpattern> </filtermapping> <!-- 该过滤器负责对Ticket的校验工作,必须启用它 --> <filter> <filtername>CAS Validation Filter</filtername> <filterclass> org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter </filterclass> <initparam> <paramname>casServerUrlPrefix</paramname> <paramvalue>https://cas.test123.com:8443/cas</paramvalue> </initparam> <initparam> <paramname>serverName</paramname> <paramvalue>http://client1.test123:8080</paramvalue> </initparam> </filter> <filtermapping> <filtername>CAS Validation Filter</filtername> <urlpattern>/*</urlpattern> </filtermapping> <!-- 该过滤器负责实现HttpServletRequest请求的包裹, 比如允许开发者通过 HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。--> <filter> <filtername>CAS HttpServletRequest Wrapper Filter</filtername> <filterclass> org.jasig.cas.client.util.HttpServletRequestWrapperFilter </filterclass> </filter> <filtermapping> <filtername>CAS HttpServletRequest Wrapper Filter</filtername> <urlpattern>/*</urlpattern> </filtermapping> <!-- 该过滤器负责把ticket验证后产生的Assertion放入ThreadLocal中,以便 不能访问web层的资 源使用。该过滤器可以使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登 录名。比如AssertionHolder.getAssertion().getPrincipal().getName()。 --> <filter> <filtername>CAS Assertion Thread Local Filter</filtername> <filterclass>org.jasig.cas.client.util.AssertionThreadLocalFilter</filterclass> </filter> <filtermapping> <filtername>CAS Assertion Thread Local Filter</filtername> <urlpattern>/*</urlpattern> </filtermapping> <! ======================== 单点登录结束 ======================== >
4.cas client 获取用户信息代码
获取用户名
AttributePrincipal principal = (AttributePrincipal)request.getUserPrincipal(); String loginName = principal.getName();
配置属性attributeRepository
获取更多的用户信息,到WEB-INF目录找到 deployerConfigContext.xml文件,同时配置attributeRepository
如下:
<bean class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao" id="attributeRepository"> <constructor-arg index="0" ref="dataSource"/> <constructor-arg index="1" value="SELECT login_pwd,id,login_name,nick_name,real_name,mobile,salt from scm_c_member where {0}"/> <property name="queryAttributeMapping"> <map> <!--这里的key需写username,value对应数据库用户名字段--> <entry key="username" value="login_name"/> </map> </property> <property name="resultAttributeMapping"> <map> <!--key为对应的数据库字段名称,value为提供给客户端获取的属性名字,系统会自动填充值--> <entry key="id" value="id"/> <entry key="mobile" value="mobile"/> <entry key="login_pwd" value="loginPwd"/> </map> </property> </bean>
配置用户认证凭据转化的解析器
找到credentialsToPrincipalResolvers,为UsernamePasswordCredentialsToPrincipalResolver 注入 attributeRepository,那么attributeRepository 就会被触发并通过此类进行解析,红色为新添部分。
<property name="credentialsToPrincipalResolvers"> <list> <bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" > <property name="attributeRepository" ref="attributeRepository" /> </bean> <bean class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" /> </list> </property>
配置InMemoryServiceRegistryDaoImpl的属性 registeredServices
修改 deployerConfigContext.xml 中的 org.jasig.cas.services.InMemoryServiceRegistryDaoImpl的 属性 registeredServices。修改 registeredServices 的allowedAttributes属性值,将需要在客户端显示的列值加上。
<bean id="serviceRegistryDao" class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl"> <property name="registeredServices"> <list> <bean class="org.jasig.cas.services.RegexRegisteredService"> <property name="id" value="0" /> <property name="name" value="HTTP and IMAP" /> <property name="description" value="Allows HTTP(S) and IMAP(S) protocols" /> <property name="serviceId" value="^(https?|imaps?)://.*" /> <property name="evaluationOrder" value="10000001" /> <!--客户端需要使用的对象的属性名称--> <property name="allowedAttributes"> <list> <value>id</value> <value>mobile</value> <value>loginPwd</value> </list> </property> </bean> </list> </property> </bean>
【
提示】网上说此bean中的ignoreAttributes属性默认是不添加用户信息,查看了 CAS 3.5.1版本的 AbstractRegisteredService 源码后,发现其默认值就是false,即:添加属性后,客户端就可见了配置与客户端交互的xml信息
修改WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp。在server验证成功后,这个页面负责生成与客户端交互的xml信息,在默认的casServiceValidationSuccess.jsp中,只包括用户名,并不提供其他的属性信息,因此需要对页面进行扩展,如下,红色为新添加部分<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'> <cas:authenticationSuccess> <cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user> <c:if test="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes) > 0}"> <cas:attributes> <c:forEach var="attr" items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}"> <cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}> </c:forEach> </cas:attributes> </c:if> <c:if test="${not empty pgtIou}"> <cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket> </c:if> <c:if test="${fn:length(assertion.chainedAuthentications) > 1}"> <cas:proxies> <c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1"> <cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy> </c:forEach> </cas:proxies> </c:if> </cas:authenticationSuccess> </cas:serviceResponse>
通过完成上面四个步骤的配置后,server端的工作就完成了。cas client获取用户信息:
AttributePrincipal principal = (AttributePrincipal)request.getUserPrincipal(); Map attributes = principal.getAttributes(); Object moblie=attributes .get("moblie");