`

使用CAS实现单点登录

 
阅读更多

单点登录(Single Sign On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

 

CAS(Central Authentication Service)是一款不错的针对 Web 应用的单点登录框架。

CAS的下载地址:http://www.jasig.org/cas/download 下载服务端cas-server-3.5.2-release.zip和客户端cas-client-3.2.1-release.zip

 

我们用3个web工程为例子演示CAS的使用。CASServer是CAS服务端,提供统一的登录页面。AA和BB分别是两个业务工程,在没登录之前,我们访问AA或BB都会跳到CASServer的登录页面。登录之后可以在AA和BB之间任意切换。退出之后必须得重新登录。

 

  • 服务端配置

解压cas-server-3.5.2-release.zip后进入目录,可以看到cas-server-webapp,我们在这个工程的基础上新建我们的工程CASServer。这是个maven工程,配置文件和源码里面都有,jar包可以从cas-server-3.5.2\modules\cas-server-webapp-3.5.2.war里面拷过去。

 

取消https配置,这个工程默认是支持https,必须手工取消,否则logout不能通过http方式生效。取消的话改动两个地方

WEB-INF/deployerConfigContext.xml增加p:requireSecure="false"

<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
 p:httpClient-ref="httpClient"  p:requireSecure="false"/>

WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml修改p:cookieSecure="false"

<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
		p:cookieSecure="false"

 

整个工程的配置文件很多,其实我们只要关注/WEB-INF/deployerConfigContext.xml这一个主要配置文件

里面定义了一个id为authenticationManager的bean,负责基于提供的凭证信息进行用户认证。

里面有两个属性credentialsToPrincipalResolvers和authenticationHandlers

credentialsToPrincipalResolvers用来将传递进来的安全实体信息转换成完整的org.jasig.cas.authentication.principal.Principal

authenticationHandlers用于用户认证。

 

我们可以实现自己的authenticationManager和authenticationHandlers,并配置到deployerConfigContext.xml

编写MyAuthenticationHandler,提供了一个很简单的验证功能,只要输入用户名aa密码123就算验证通过

public class MyAuthenticationHandler extends
		AbstractUsernamePasswordAuthenticationHandler {
	@Override
	protected boolean authenticateUsernamePasswordInternal(
			UsernamePasswordCredentials credentials) throws AuthenticationException {
		String username = credentials.getUsername();  
	    String password = credentials.getPassword();  
	    
	    if(username.equals("aa")&&password.equals("123")){
	    	return true;
	    }
		return false;
	}
}

 编写MyAuthenticationManagerImpl,核心是SimplePrincipal principalTas = new SimplePrincipal("aaaaaaaaaaaaaa");这一行。封装一个SimplePrincipal 对象给客户端。这里是一个字符串,实际过程中可以是一个代表权限的json字符串。

public class MyAuthenticationManagerImpl extends AbstractAuthenticationManager {

	/** An array of authentication handlers. */
	@NotNull
	@Size(min = 1)
	private List<AuthenticationHandler> authenticationHandlers;

	/** An array of CredentialsToPrincipalResolvers. */
	@NotNull
	@Size(min = 1)
	private List<CredentialsToPrincipalResolver> credentialsToPrincipalResolvers;

	
	@Override
	protected Pair<AuthenticationHandler, Principal> authenticateAndObtainPrincipal(
			Credentials credentials) throws AuthenticationException {

		boolean foundSupported = false;
		boolean authenticated = false;
		AuthenticationHandler authenticatedClass = null;

		for (final AuthenticationHandler authenticationHandler : this.authenticationHandlers) {
			if (authenticationHandler.supports(credentials)) {
				foundSupported = true;
				if (!authenticationHandler.authenticate(credentials)) {
					if (log.isInfoEnabled()) {
						log.info("AuthenticationHandler: "
								+ authenticationHandler.getClass().getName()
								+ " failed to authenticate the user which provided the following credentials: "
								+ credentials.toString());
					}
				} else {
					if (log.isInfoEnabled()) {
						log.info("AuthenticationHandler: "
								+ authenticationHandler.getClass().getName()
								+ " successfully authenticated the user which provided the following credentials: "
								+ credentials.toString());
					}
					authenticatedClass = authenticationHandler;
					authenticated = true;
					break;
				}
			}
		}

		if (!authenticated) {
			if (foundSupported) {
				throw BadCredentialsAuthenticationException.ERROR;
			}

			throw UnsupportedCredentialsException.ERROR;
		}

		foundSupported = false;

		for (final CredentialsToPrincipalResolver credentialsToPrincipalResolver : this.credentialsToPrincipalResolvers) {
			if (credentialsToPrincipalResolver.supports(credentials)) {
				final Principal principal = credentialsToPrincipalResolver
						.resolvePrincipal(credentials);
				foundSupported = true;
				if (principal != null) {
					SimplePrincipal principalTas = new SimplePrincipal("aaaaaaaaaaaaaa");
					return new Pair<AuthenticationHandler, Principal>(
							authenticatedClass, principalTas);
				}
			}
		}

		if (foundSupported) {
			if (log.isDebugEnabled()) {
				log.debug("CredentialsToPrincipalResolver found but no principal returned.");
			}

			throw BadCredentialsAuthenticationException.ERROR;
		}

		log.error("CredentialsToPrincipalResolver not found for "
				+ credentials.getClass().getName());
		throw UnsupportedCredentialsException.ERROR;
	
	}


	public List<AuthenticationHandler> getAuthenticationHandlers() {
		return authenticationHandlers;
	}


	public void setAuthenticationHandlers(
			List<AuthenticationHandler> authenticationHandlers) {
		this.authenticationHandlers = authenticationHandlers;
	}


	public List<CredentialsToPrincipalResolver> getCredentialsToPrincipalResolvers() {
		return credentialsToPrincipalResolvers;
	}


	public void setCredentialsToPrincipalResolvers(
			List<CredentialsToPrincipalResolver> credentialsToPrincipalResolvers) {
		this.credentialsToPrincipalResolvers = credentialsToPrincipalResolvers;
	}

}

 自此服务端配置完成,可以访问http://127.0.0.1/CASServer/login,用用户名aa密码123登陆成功说明一切正常。

 

  • 客户端配置

新建web工程AA,可以引入上个工程里面所有的jar包。

修改web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>AA</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
            classpath:applicationContext*.xml
		</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- CAS config begin -->
	<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>CAS Authentication Filter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<init-param>
			<param-name>targetBeanName</param-name>
			<param-value>casAuthenticationFilter</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>CAS Authentication Filter</filter-name>
		<url-pattern>*.jsp</url-pattern>
	</filter-mapping>
	<filter>
		<filter-name>CAS Validation Filter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<init-param>
			<param-name>targetBeanName</param-name>
			<param-value>casValidationFilter</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>CAS Validation Filter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<filter>
		<filter-name>CAS HttpServletRequestWrapperFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<init-param>
			<param-name>targetBeanName</param-name>
			<param-value>casHttpServletRequestWrapperFilter</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>CAS HttpServletRequestWrapperFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<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>
</web-app>

里面引入了spring的支持,spring的配置文件applicationContext-cas.xml。还有cas客户端相关的一些filter。

这些filter有org.jasig.cas.client.session.SingleSignOutFilter,org.jasig.cas.client.authentication.AuthenticationFilter,org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter,org.jasig.cas.client.util.HttpServletRequestWrapperFilter,org.jasig.cas.client.util.AssertionThreadLocalFilter。

关于各个filter的具体作用可以参考这篇文章http://blog.csdn.net/yuwenruli/article/details/6600032

 

applicationContext-cas.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

	<bean id="casAuthenticationFilter"
		class="org.jasig.cas.client.authentication.AuthenticationFilter">
		<property name="casServerLoginUrl" value="http://127.0.0.1/CASServer/login" />
		<property name="serverName" value="127.0.0.1" />
	</bean>

	<bean id="casValidationFilter"
		class="org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter">
		<property name="ticketValidator">
			<ref bean="Cas20ServiceTicketValidator" />
		</property>
		<property name="useSession" value="true" />
		<property name="serverName" value="127.0.0.1" />
		<property name="redirectAfterValidation" value="true" />
	</bean>
	<bean id="Cas20ServiceTicketValidator"
		class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
		<constructor-arg index="0" value="http://127.0.0.1/CASServer" />
	</bean>

	<bean id="casHttpServletRequestWrapperFilter"
		class="org.jasig.cas.client.util.HttpServletRequestWrapperFilter" />
</beans>

主要将casServerLoginUrl改成cas服务端的地址,serverName改成当前业务应用发布的地址。

 

新建测试主页面index.jsp,加入

<%=request.getUserPrincipal().getName() %>
<a href="http://127.0.0.1/CASServer/logout">logout</a>

 

用同样的方法创建web工程BB。

 

  • 测试

发布3个工程到本地tocmat。

第一次访问http://127.0.0.1/AAhttp://127.0.0.1/BB 都会跳到CASServer的登陆页面

登陆成功后跳到相应工程的index页面,以后可以在AA和BB两个工程间自由切换。logout后必须重新登陆。

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics