SSO单点登录之二:Yale CAS实现单点登录(服务器端)

耶鲁大学开发的CAS单点登录系统在SSO中应该说是比较有名的啦,既然也是开源的,我们为何不拿过来学习学习呢

很早之前的耶鲁的CAS开源包地址是在:http://www.yale.edu/tp/auth/ 

目前的CAS开源包都已经转移到新网址了,并且开源包里面的类名都没有以yelu开头了而是jasing

CAS客户端:http://downloads.jasig.org/cas-clients/ 

CAS服务器端:http://www.jasig.org/cas/download 

我下载的都是目前最新版的cas-server-3.4.5服务器端和cas-client-3.2.0客户端

 

 CAS服务器端基础应用

1)将cas-server-3.4.zip解压到一个目录,将cas-server-3.4.5\modules\cas-server-webapp-3.4.5.war拷贝到tomcat的webapp中去,并且改名为CasServer.war.启动tomcat

2)现在可以访问CAS应用了,当然要使用HTTPS加密协议访问,例如本问用到地址:https://javacrazyer.sso.com:8443/CasServer/login,现在打开了CAS服务器的页面输入admin/admin点击登录(CAS默认的验证规则只要用户名和密码相同就通过)


SSO单点登录之二:Yale CAS实现单点登录(服务器端)_第1张图片

 

所以如果你看到下面的这张图片你就成功了  


SSO单点登录之二:Yale CAS实现单点登录(服务器端)_第2张图片
 

CAS服务器端深入应用,与数据库配置登录

 

首先打开tomcat/webapp/cas/WEB-INF/deployerConfigContext.xml文件,配置的地方如下:

 1)找到第92行处,注释掉:SimpleTestUsernamePasswordAuthenticationHandler这个验证Handler,这个是比较简单的,只是判断用户名和密码相同即可通过,这个肯定不能在实际应用中使用,弃用!  

 2)注释掉92行后在下面添加下面的代码:

 

<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
        <property name="dataSource" ref="dataSource" />
        <property name="sql" value="select password from t_admin_user where login_name=?" />
        <property name="passwordEncoder" ref="MD5PasswordEncoder"/>
</bean>

在文件的末尾结束之前加上如下

 

<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:///wsriademo</value></property>
   <property name="username"><value>root</value></property>
   <property name="password"><value>root</value></property>
</bean>

<bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">  
        <constructor-arg index="0">
                <value>MD5</value>
        </constructor-arg>
</bean>
 

完整的deployerConfigContext.xml如下

 

<?xml version="1.0" encoding="UTF-8"?>
<!--
	| deployerConfigContext.xml centralizes into one file some of the declarative configuration that
	| all CAS deployers will need to modify.
	|
	| This file declares some of the Spring-managed JavaBeans that make up a CAS deployment.  
	| The beans declared in this file are instantiated at context initialization time by the Spring 
	| ContextLoaderListener declared in web.xml.  It finds this file because this
	| file is among those declared in the context parameter "contextConfigLocation".
	|
	| By far the most common change you will need to make in this file is to change the last bean
	| declaration to replace the default SimpleTestUsernamePasswordAuthenticationHandler with
	| one implementing your approach for authenticating usernames and passwords.
	+-->
<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:sec="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
	<!--
		| This bean declares our AuthenticationManager.  The CentralAuthenticationService service bean
		| declared in applicationContext.xml picks up this AuthenticationManager by reference to its id, 
		| "authenticationManager".  Most deployers will be able to use the default AuthenticationManager
		| implementation and so do not need to change the class of this bean.  We include the whole
		| AuthenticationManager here in the userConfigContext.xml so that you can see the things you will
		| need to change in context.
		+-->
	<bean id="authenticationManager"
		class="org.jasig.cas.authentication.AuthenticationManagerImpl">
		<!--
			| This is the List of CredentialToPrincipalResolvers that identify what Principal is trying to authenticate.
			| The AuthenticationManagerImpl considers them in order, finding a CredentialToPrincipalResolver which 
			| supports the presented credentials.
			|
			| AuthenticationManagerImpl uses these resolvers for two purposes.  First, it uses them to identify the Principal
			| attempting to authenticate to CAS /login .  In the default configuration, it is the DefaultCredentialsToPrincipalResolver
			| that fills this role.  If you are using some other kind of credentials than UsernamePasswordCredentials, you will need to replace
			| DefaultCredentialsToPrincipalResolver with a CredentialsToPrincipalResolver that supports the credentials you are
			| using.
			|
			| Second, AuthenticationManagerImpl uses these resolvers to identify a service requesting a proxy granting ticket. 
			| In the default configuration, it is the HttpBasedServiceCredentialsToPrincipalResolver that serves this purpose. 
			| You will need to change this list if you are identifying services by something more or other than their callback URL.
			+-->
		<property name="credentialsToPrincipalResolvers">
			<list>
				<!--
					| UsernamePasswordCredentialsToPrincipalResolver supports the UsernamePasswordCredentials that we use for /login 
					| by default and produces SimplePrincipal instances conveying the username from the credentials.
					| 
					| If you've changed your LoginFormAction to use credentials other than UsernamePasswordCredentials then you will also
					| need to change this bean declaration (or add additional declarations) to declare a CredentialsToPrincipalResolver that supports the
					| Credentials you are using.
					+-->
				<bean
					class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />
				<!--
					| HttpBasedServiceCredentialsToPrincipalResolver supports HttpBasedCredentials.  It supports the CAS 2.0 approach of
					| authenticating services by SSL callback, extracting the callback URL from the Credentials and representing it as a
					| SimpleService identified by that callback URL.
					|
					| If you are representing services by something more or other than an HTTPS URL whereat they are able to
					| receive a proxy callback, you will need to change this bean declaration (or add additional declarations).
					+-->
				<bean
					class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />
			</list>
		</property>

		<!--
			| Whereas CredentialsToPrincipalResolvers identify who it is some Credentials might authenticate, 
			| AuthenticationHandlers actually authenticate credentials.  Here we declare the AuthenticationHandlers that
			| authenticate the Principals that the CredentialsToPrincipalResolvers identified.  CAS will try these handlers in turn
			| until it finds one that both supports the Credentials presented and succeeds in authenticating.
			+-->
		<property name="authenticationHandlers">
			<list>
				<!--
					| This is the authentication handler that authenticates services by means of callback via SSL, thereby validating
					| a server side SSL certificate.
					+-->
				<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
					p:httpClient-ref="httpClient" />
				<!--
					| This is the authentication handler declaration that every CAS deployer will need to change before deploying CAS 
					| into production.  The default SimpleTestUsernamePasswordAuthenticationHandler authenticates UsernamePasswordCredentials
					| where the username equals the password.  You will need to replace this with an AuthenticationHandler that implements your
					| local authentication strategy.  You might accomplish this by coding a new such handler and declaring
					| edu.someschool.its.cas.MySpecialHandler here, or you might use one of the handlers provided in the adaptors modules.
					+-->
				<!--<bean
					class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />
			-->
	<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
        <property name="dataSource" ref="dataSource" />
        <property name="sql" value="select password from t_admin_user where login_name=?" />
        <property name="passwordEncoder" ref="myPasswordEncoder"/>
    </bean>
			</list>
		</property>
	</bean>


	<!--
	This bean defines the security roles for the Services Management application.  Simple deployments can use the in-memory version.
	More robust deployments will want to use another option, such as the Jdbc version.
	
	The name of this should remain "userDetailsService" in order for Spring Security to find it.
	 -->
    <!-- <sec:user name="@@THIS SHOULD BE REPLACED@@" password="notused" authorities="ROLE_ADMIN" />-->

    <sec:user-service id="userDetailsService">
        <sec:user name="@@THIS SHOULD BE REPLACED@@" password="notused" authorities="ROLE_ADMIN" />
    </sec:user-service>
	
	<!-- 
	Bean that defines the attributes that a service may return.  This example uses the Stub/Mock version.  A real implementation
	may go against a database or LDAP server.  The id should remain "attributeRepository" though.
	 -->
	<bean id="attributeRepository"
		class="org.jasig.services.persondir.support.StubPersonAttributeDao">
		<property name="backingMap">
			<map>
				<entry key="uid" value="uid" />
				<entry key="eduPersonAffiliation" value="eduPersonAffiliation" /> 
				<entry key="groupMembership" value="groupMembership" />
			</map>
		</property>
	</bean>
	
	<!-- 
	Sample, in-memory data store for the ServiceRegistry. A real implementation
	would probably want to replace this with the JPA-backed ServiceRegistry DAO
	The name of this bean should remain "serviceRegistryDao".
	 -->
	<bean
		id="serviceRegistryDao"
        class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl">
            <property name="registeredServices">
                <list>
                    <bean class="org.jasig.cas.services.RegisteredServiceImpl">
                        <property name="id" value="0" />
                        <property name="name" value="HTTP" />
                        <property name="description" value="Only Allows HTTP Urls" />
                        <property name="serviceId" value="http://**" />
                    </bean>

                    <bean class="org.jasig.cas.services.RegisteredServiceImpl">
                        <property name="id" value="1" />
                        <property name="name" value="HTTPS" />
                        <property name="description" value="Only Allows HTTPS Urls" />
                        <property name="serviceId" value="https://**" />
                    </bean>

                    <bean class="org.jasig.cas.services.RegisteredServiceImpl">
                        <property name="id" value="2" />
                        <property name="name" value="IMAPS" />
                        <property name="description" value="Only Allows HTTPS Urls" />
                        <property name="serviceId" value="imaps://**" />
                    </bean>

                    <bean class="org.jasig.cas.services.RegisteredServiceImpl">
                        <property name="id" value="3" />
                        <property name="name" value="IMAP" />
                        <property name="description" value="Only Allows IMAP Urls" />
                        <property name="serviceId" value="imap://**" />
                    </bean>
                </list>
            </property>
        </bean>
        
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource">
   <property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
   <property name="url"><value>jdbc:mysql:///javacrazyer</value></property>
   <property name="username"><value>root</value></property>
   <property name="password"><value>root</value></property>
</bean>
<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:///javacrazyer</value></property>
   <property name="username"><value>root</value></property>
   <property name="password"><value>root</value></property>
</bean>

<bean id="myPasswordEncoder" class="org.jasig.cas.authentication.handler.MyPasswordEncoder"/>
<bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">  
        <constructor-arg index="0">
                <value>MD5</value>
        </constructor-arg>
</bean>

</beans>

2.1)dataSource,就是使用JDBC查询时的数据源

这里呢关于dataSource的配置可以采用多种方式,正如我的配置文件中采用了spring支持的dataSource(这里需要添加cas-server-3.4.5\modules\cas-server-support-jdbc-3.4.5.jar),也采用了dbcp的方式(这里需要添加commons-dbcp.jar和commons-pool.jar),当然少不了添加MySQL驱动包了,当然我们可以配置N多种数据源在这里,真用到的其实只会有一个, <property name="dataSource" ref="dataSource" />这句话可以引用你想要的数据源。

这里关于数据源配置可以参考我早前写的关于spring数据源配置方式的文章

2.2)passwordEncoder:处理密码加密

细心的朋友从最后面的代码可以看到MD5加密的类有两个实MD5PasswordEncoder就是默认我们只需把摸认类添

加上无需写其他继承类,而如果你非要继承的话,那么就写你自己的类继承自PasswordEncoder,具体可以更改encode方法

我这里就选择使用了继承的方式,我写的类如下

 

package org.jasig.cas.authentication.handler;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public final class MyPasswordEncoder implements PasswordEncoder {

	public MyPasswordEncoder() {
	};

	public String encode(String password) {
		if (password == null || password.length() == 0) {
			throw new IllegalArgumentException(
					"String to encript cannot be null or zero length");
		}

		StringBuffer hexString = new StringBuffer();

		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			md.update(password.getBytes());
			byte[] hash = md.digest();

			for (int i = 0; i < hash.length; i++) {
				if ((0xff & hash[i]) < 0x10) {
					hexString.append("0"
							+ Integer.toHexString((0xFF & hash[i])));
				} else {
					hexString.append(Integer.toHexString(0xFF & hash[i]));
				}
			}
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}

		return hexString.toString();
	}

}

 当然你也可以选择不继承,采用默认的,都由 <property name="passwordEncoder" ref="MD5PasswordEncoder"/>来决定选择哪个加密方式。

 

2.3)QueryDatabaseAuthenticationHandler是cas-server-support-jdbc提供的查询接口其中一个,QueryDatabaseAuthenticationHandler是通过配置一个 SQL 语句查出密码,与所给密码匹配 

 

2.4)sql:登录查询哪一张表

这里大家或许还有一句话可以看到<property name="sql" value="select password from t_admin_user where login_name=?" />

所以需要用到数据库脚本,数据库名称我这里起名为javacrazyer

/*
Navicat MySQL Data Transfer
Source Host     : localhost:3306
Source Database : javacrazyer
Target Host     : localhost:3306
Target Database : javacrazyer
Date: 2011-01-21 15:19:40
*/

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for area_info
-- ----------------------------
DROP TABLE IF EXISTS `area_info`;
CREATE TABLE `area_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '地区ID',
  `area_code` varchar(32) NOT NULL COMMENT '地区编码',
  `area_name` varchar(60) NOT NULL COMMENT '地区名称',
  `parent_area_id` bigint(20) NOT NULL COMMENT '上级ID',
  `area_number` char(6) DEFAULT NULL,
  `country_code` varchar(32) DEFAULT NULL,
  `area_level` int(11) NOT NULL,
  `enabled` bit(1) NOT NULL,
  `sort` int(11) DEFAULT NULL,
  `remark` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `bmc_bd_uqareainfo` (`area_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of area_info
-- ----------------------------

-- ----------------------------
-- Table structure for city
-- ----------------------------
DROP TABLE IF EXISTS `city`;
CREATE TABLE `city` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '城市ID',
  `CITY_NAME` varchar(20) NOT NULL COMMENT '城市名称',
  `SUPER_ID` bigint(20) NOT NULL COMMENT '上级城市(没有为-1)',
  `REMARK` varchar(255) DEFAULT NULL COMMENT '城市备注',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='城市';

-- ----------------------------
-- Records of city
-- ----------------------------

-- ----------------------------
-- Table structure for t_admin_user
-- ----------------------------
DROP TABLE IF EXISTS `t_admin_user`;
CREATE TABLE `t_admin_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `password` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `login_name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_admin_user
-- ----------------------------
INSERT INTO `t_admin_user` VALUES ('1', '88f6f4ddbd7160aa6a547589af036ee0', null, null, 'cheneywu');
INSERT INTO `t_admin_user` VALUES ('2', '098f6bcd4621d373cade4e832627b4f6', null, null, 'test');
 

 到这里你就认为完事了?看到我脚本中的密码的值是MD5加密过的,为什么加密?2.2中已经介绍过了,所以对于用户名/密码为:test/test的账户来说,在数据库中就应该为test/8f6bcd4621d373cade4e832627b4f6.这个密码值是怎么得到的,sql语言

中可以进行操作:select md5("test")

给力上图


SSO单点登录之二:Yale CAS实现单点登录(服务器端)_第3张图片

这时你再去访问地址:https://javacrazyer.sso.com:8443/CasServer/login,输入数据库中存在的相同值的用户名密码,就能成功登陆了

 

关于CAS客户端SSO登录下一篇文章将会介绍

你可能感兴趣的:(spring,应用服务器,mysql,bean,SSO)