cas sso 单点登陆 登陆及client获取用户信息(三)

  • Post author:
  • Post category:其他

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>
        <listener­class>
org.jasig.cas.client.session.SingleSignOutHttpSessionListener
</listener­class>
    </listener>
    <!­­-- 该过滤器用于实现单点登出功能,可选配置。 ­­-->
    <filter>
        <filter­name>CAS Single Sign Out Filter</filter­name>
        <filter­class>org.jasig.cas.client.session.SingleSignOutFilter</filter­class>
    </filter>
    <filter­mapping>
        <filter­name>CAS Single Sign Out Filter</filter­name>
        <url­pattern>/*</url­pattern>
    </filter­mapping>
    <!­­-- 该过滤器负责用户的认证工作,必须启用它 ­­-->
    <filter>
        <filter­name>CASFilter</filter­name>
        <filter­class>org.jasig.cas.client.authentication.AuthenticationFilter</filter­class>
        <init­param>
            <param­name>casServerLoginUrl</param­name>
            <param­value>https://cas.test123.com:8443/cas</param­value>
        </init­param>
        <!­­-- 这里的serverName是服务端的IP ­­-->
        <init­param>
            <param­name>serverName</param­name>
            <param­value>http://client1.test123:8080</param­value>
        </init­param>
    </filter>
    <filter­mapping>
        <filter­name>CASFilter</filter­name>
        <url­pattern>/*</url­pattern>
    </filter­mapping>
    <!­­-- 该过滤器负责对Ticket的校验工作,必须启用它 ­­-->
    <filter>
        <filter­name>CAS Validation Filter</filter­name>
        <filter­class>
            org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
        </filter­class>
        <init­param>
            <param­name>casServerUrlPrefix</param­name>
            <param­value>https://cas.test123.com:8443/cas</param­value>
        </init­param>
        <init­param>
            <param­name>serverName</param­name>
            <param­value>http://client1.test123:8080</param­value>
        </init­param>
    </filter>
    <filter­mapping>
        <filter­name>CAS Validation Filter</filter­name>
        <url­pattern>/*</url­pattern>
    </filter­mapping>
    <!­­-- 该过滤器负责实现HttpServletRequest请求的包裹, 比如允许开发者通过
HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。--­­>
    <filter>
        <filter­name>CAS HttpServletRequest Wrapper Filter</filter­name>
        <filter­class>
            org.jasig.cas.client.util.HttpServletRequestWrapperFilter
        </filter­class>
    </filter>
    <filter­mapping>
        <filter­name>CAS HttpServletRequest Wrapper Filter</filter­name>
        <url­pattern>/*</url­pattern>
    </filter­mapping>
    <!­­-- 该过滤器负责把ticket验证后产生的Assertion放入ThreadLocal中,以便 不能访问web层的资
源使用。该过滤器可以使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登
录名。比如AssertionHolder.getAssertion().getPrincipal().getName()。 ­­-->
    <filter>
        <filter­name>CAS Assertion Thread Local Filter</filter­name>
        <filter­class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter­class>
    </filter>
    <filter­mapping>
        <filter­name>CAS Assertion Thread Local Filter</filter­name>
        <url­pattern>/*</url­pattern>
    </filter­mapping>
    <!­­ ======================== 单点登录结束 ======================== ­­>

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");  


版权声明:本文为nmsbq原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。