1、acegi内置了对CAS的支持。这里的CAS是3.2。建立CAS server是一个比较简单的事情。CAS server就是一个标准的war文件,把它发布就可以运行。需要做的仅仅是调整登陆和其他一些页面。
2、先了解一下CAS如何实现SSO。
例子:原有系统A和系统B,现在在它们之间做SSO。
很显然,系统A和B都是CAS client。首先是访问系统A,干掉A的登陆页面,在A的入口判断有没有Ticket(票据),如果没有则重定向到CAS server,在CAS server提供Credential(大多数情况就是用户名和密码)。CAS server的作用非常简单:就是来验证用户密码。正确,则发送Ticket。CAS有5种Ticket,分别是TGC(通过cookie发送的 ticket),ST(Service Ticket),PGT,PGTIOU,PT。其中PGT,PGTIOU,PT属于代理ticket,这里不作讨论。
TGC和ST的关系可以打个比方:
我去中央电视塔去玩,结果发现地下还有个海底世界。SSO前我是这么玩的:先去电视塔买张门票,玩完了;再去海底世界买张门票,玩完了。发现真累,两个景点这么近还要买两次门票,就不能搞个通票吗?于是就SSO。于是这样:我先去电视塔,门卫告诉我你不能进去要买票,于是把我送到通票售票处(CAS server)买票(登录),买吧,于是给了我两张票,注意,是两张,一张发到我手里,上面写着仅限电视塔使用(ST);靠,不是通票吗,咋仅限电视塔使用?别急,还有一张票(TGC)通过cookie发送你看不见。人家说了保证没问题,我咋办,这是人家的规矩,那就先去玩吧。出了电视塔我直扑海底世界,
门卫说要海底世界票,不会吧,我买的通票啊,门卫说不着急,又把我送回通票售票处(CAS server),通票售票处(CAS server)一看,发现我有TGC,嘿嘿,这家伙买过票了不用再买(不用再登录),于是换我一张票(ST)上面写着仅限海底世界使用,于是我就拿着这张票又去海底世界了。于是我明白了啥是SSO了,不就是把买票改成换票了吗?
比方完了,最开始的例子也就不往下继续了。需要注意的是系统A和B整合SSO需要把A、B的用户密码集中管理,你说A中我的用户名是张三,B中是李四,SSO能不能帮我自动识别,回答是不行的。
二、acegi配置
以一个典型的web访问来说明整个过程
1、用户访问一个受acegi安全保护的页面或业务方法;
2、用户没有登陆的话显然会抛出AuthenticationException
3、配置exceptionTranslationFilter捕获这个异常重定向到CAS server登陆页面
修改applicationContext-acegi-security.xml文件
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint"><ref local="casProcessingFilterEntryPoint"/></property>
</bean>
<bean id="casProcessingFilterEntryPoint" class="org.acegisecurity.ui.cas.CasProcessingFilterEntryPoint">
<property name="loginUrl"><value>https://my.company.com/cas/login</value></property>
<property name="serviceProperties"><ref local="serviceProperties"/></property>
</bean>
<bean id="serviceProperties" class="org.acegisecurity.ui.cas.ServiceProperties">
<property name="service"><value>https://server.company.com/myapp/j_acegi_cas_security_check</value></property>
<property name="sendRenew"><value>false</value></property>
</bean>
配置casProcessingFilter来处理返回ST(和以前的authenticationProcessingFilter比较类似)
<bean id="casProcessingFilter" class="org.acegisecurity.ui.cas.CasProcessingFilter">
<property name="authenticationManager"><ref local="authenticationManager"/></property>
<property name="authenticationFailureUrl"><value>/casfailed.jsp</value></property>
<property name="defaultTargetUrl"><value>/</value></property>
<property name="filterProcessesUrl"><value>/j_acegi_cas_security_check</value></property>
</bean>
配置authenticationManager
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="casAuthenticationProvider"/>
</list>
</property>
</bean>
<bean id="casAuthenticationProvider" class="org.acegisecurity.providers.cas.CasAuthenticationProvider">
<property name="casAuthoritiesPopulator"><ref local="casAuthoritiesPopulator"/></property>
<property name="casProxyDecider"><ref local="casProxyDecider"/></property>
<property name="ticketValidator"><ref local="casProxyTicketValidator"/></property>
<property name="statelessTicketCache"><ref local="statelessTicketCache"/></property>
<property name="key"><value>my_password_for_this_auth_provider_only</value></property>
</bean>
具体作用的是casAuthenticationProvider,casAuthenticationProvider通过 casProxyTicketValidator来校验ST
<bean id="casProxyTicketValidator" class="org.acegisecurity.providers.cas.ticketvalidator.CasProxyTicketValidator">
<property name="casValidate"><value>https://my.company.com/cas/proxyValidate</value></property>
<property name="serviceProperties"><ref local="serviceProperties"/></property>
</bean>
其他
需要拷贝casclient-3.2和ehcache-1.3.0.jar加入应用的lib中
以下是整个applicationContext-acegi-security.xml
Code
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!--
- A simple "base bones" Acegi Security configuration.
-
- The sample includes the "popular" features that people tend to use.
- Specifically, form authentication, remember-me, and anonymous processing.
- Other features aren't setup, as these can be added later by inserting
- the relevant XML fragments as specified in the Reference Guide.
-
- To assist new users, the filters specified in the FilterChainProxy are
- declared in the application context in the same order. Collaborators
- required by those filters are placed at the end of the file.
-
- $Id: applicationContext-acegi-security.xml,v 1.3 2008/01/09 01:41:14 fdmeng Exp $
-->
<beans>
<bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value><![CDATA[
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/js/**=#NONE#
/css/**=#NONE#
/images/**=#NONE#
/**=httpSessionContextIntegrationFilter,casProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
]]></value>
</property>
</bean>
<bean id="httpSessionContextIntegrationFilter"
class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" />
<bean id="logoutFilter"
class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/index.jsp" />
<!-- URL redirected to after logout -->
<constructor-arg>
<list>
<ref bean="rememberMeServices" />
<bean
class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />
</list>
</constructor-arg>
</bean>
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager"
ref="authenticationManager" />
<property name="authenticationFailureUrl"
value="/acegilogin.jsp?login_error=1" />
<property name="defaultTargetUrl" value="/" />
<property name="filterProcessesUrl"
value="/j_acegi_security_check" />
<property name="rememberMeServices" ref="rememberMeServices" />
</bean>
<bean id="securityContextHolderAwareRequestFilter"
class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter" />
<bean id="rememberMeProcessingFilter"
class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="authenticationManager"
ref="authenticationManager" />
<property name="rememberMeServices" ref="rememberMeServices" />
</bean>
<bean id="anonymousProcessingFilter"
class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
<property name="key" value="changeThis" />
<property name="userAttribute"
value="anonymousUser,ROLE_ANONYMOUS" />
</bean>
<!-- replace to cas login -->
<!-- bean id="exceptionTranslationFilter"
class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl"
value="/acegilogin.jsp" />
<property name="forceHttps" value="false" />
</bean>
</property>
<property name="accessDeniedHandler">
<bean
class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/accessDenied.jsp" />
</bean>
</property>
</bean>
-->
<bean id="exceptionTranslationFilter"
class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<ref local="casProcessingFilterEntryPoint" />
</property>
</bean>
<bean id="casProcessingFilterEntryPoint"
class="org.acegisecurity.ui.cas.CasProcessingFilterEntryPoint">
<property name="loginUrl">
<value>https://EafyYe:8443/cas-server-webapp-3.2.1.1/login</value>
</property>
<property name="serviceProperties">
<ref local="serviceProperties" />
</property>
</bean>
<bean id="serviceProperties"
class="org.acegisecurity.ui.cas.ServiceProperties">
<property name="service">
<value>http://www.163.com</value>
</property>
<property name="sendRenew">
<value>false</value>
</property>
</bean>
<bean id="casProcessingFilter"
class="org.acegisecurity.ui.cas.CasProcessingFilter">
<property name="authenticationManager">
<ref local="authenticationManager" />
</property>
<property name="authenticationFailureUrl">
<value>/casfailed.jsp</value>
</property>
<property name="defaultTargetUrl">
<value>/</value>
</property>
<property name="filterProcessesUrl">
<value>/j_acegi_cas_security_check</value>
</property>
</bean>
<bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="casAuthenticationProvider" />
</list>
</property>
</bean>
<bean id="casAuthenticationProvider"
class="org.acegisecurity.providers.cas.CasAuthenticationProvider">
<property name="casAuthoritiesPopulator">
<ref local="casAuthoritiesPopulator" />
</property>
<property name="casProxyDecider">
<ref local="rejectProxyTickets" />
</property>
<property name="ticketValidator">
<ref local="casProxyTicketValidator" />
</property>
<property name="statelessTicketCache">
<ref local="statelessTicketCache" />
</property>
<property name="key">
<value>my_password_for_this_auth_provider_only</value>
</property>
</bean>
<bean id="casAuthoritiesPopulator"
class="org.acegisecurity.providers.cas.populator.DaoCasAuthoritiesPopulator">
<property name="userDetailsService" ref="inMemDaoImpl" />
</bean>
<bean id="inMemDaoImpl"
class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
admin=password,ROLE_ADMIN
marissa=koala,ROLE_ADMIN,ROLE_MARISSA,enabled
</value>
</property>
</bean>
<bean id="rejectProxyTickets"
class="org.acegisecurity.providers.cas.proxy.RejectProxyTickets" />
<bean id="casProxyTicketValidator"
class="org.acegisecurity.providers.cas.ticketvalidator.CasProxyTicketValidator">
<property name="casValidate">
<value>https://eafyye:8443/cas-server-webapp-3.2.1.1/serviceValidate</value>
<!-- https://my.company.com/cas/proxyValidate -->
</property>
<property name="serviceProperties">
<ref local="serviceProperties" />
</property>
</bean>
<bean id="statelessTicketCache"
class="org.acegisecurity.providers.cas.cache.EhCacheBasedTicketCache">
<property name="cache" ref="ticketCacheBackend" />
</bean>
<bean id="ticketCacheBackend"
class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="cacheManager" />
<property name="cacheName" value="ticketCache" />
</bean>
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
<bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"
ref="authenticationManager" />
<property name="accessDecisionManager">
<bean class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions"
value="false" />
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter" />
<bean
class="org.acegisecurity.vote.AuthenticatedVoter" />
</list>
</property>
</bean>
</property>
<property name="objectDefinitionSource">
<ref bean="rolePermissionService" />
</property>
<!--
<property name="objectDefinitionSource">
<value><![CDATA[
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/secure/extreme/**=ROLE_SUPERVISOR
/secure/**=IS_AUTHENTICATED_REMEMBERED
/project/**=IS_AUTHENTICATED_REMEMBERED
/secretary/**=ROLE_TELLER
/**=IS_AUTHENTICATED_ANONYMOUSLY
]]></value>
</property>
-->
</bean>
<bean id="rememberMeServices"
class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="userDetailsService" />
<property name="key" value="changeThis" />
</bean>
<!--
<bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider" />
<bean
class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
<property name="key" value="changeThis" />
</bean>
<bean
class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key" value="changeThis" />
</bean>
</list>
</property>
<property name="sessionController">
<ref bean="concurrentSessionController" />
</property>
</bean>
-->
<bean id="concurrentSessionController"
class="org.acegisecurity.concurrent.ConcurrentSessionControllerImpl">
<property name="maximumSessions">
<value>1</value>
</property>
<property name="sessionRegistry">
<ref local="sessionRegistry" />
</property>
</bean>
<bean id="sessionRegistry"
class="org.acegisecurity.concurrent.SessionRegistryImpl" />
<bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService" />
</bean>
<!-- UserDetailsService is the most commonly frequently Acegi Security interface implemented by end users -->
<!--
<bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userProperties">
<bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:users.properties"/>
</bean>
</property>
</bean>
-->
<bean id="userDetailsService"
class="org.security.acegi.AcegiJdbcDaoImpl">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="usersByUsernameQuery">
<value>
SELECT u.user_id, u.company_id, u.email, u.password,
u.enabled from role r, user_role ur, user u where
r.role_id = ur.role_id and ur.user_id = u.user_id and
email = ? limit 1
</value>
</property>
<property name="authoritiesByUsernameQuery">
<value>
SELECT u.email, r.role_name FROM user_role ur, user u,
role r WHERE ur.user_id = u.user_id and ur.role_id =
r.role_id and u.email = ?
</value>
</property>
</bean>
<bean id="rolePermissionService"
class="org.security.acegi.AcegiJdbcDefinitionSourceImpl">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="permissionsQuery">
<value>
SELECT resource_name, role_name FROM resource_role rr,
resource re, role ro WHERE rr.role_id = ro.role_id and
rr.resource_id = re.resource_id
</value>
</property>
<property name="convertUrlToLowercaseBeforeComparison"
value="false">
</property>
<property name="resourceExpression"
value="PATTERN_TYPE_APACHE_ANT">
</property>
</bean>
<!-- This bean is optional; it isn't used by any other bean as it only listens and logs -->
<bean id="loggerListener"
class="org.acegisecurity.event.authentication.LoggerListener" />
</beans>
参考 集成ACEGI 进行权限控制
JA-SIG(CAS)学习笔记3