所谓单点登录,SSO(Single Sign On),就是把N个应用的登录系统整合在一起,这样一来无论用户登录了任何一个应用,都可以直接以登录过的身份访问其他应用,不用每次访问其他系统再去登陆一遍了。
Spring Security没有实现自己的SSO,而是整合了耶鲁大学单点登陆(JA-SIG),这是当前使用很广泛的一种SSO实现,它是基于中央认证服务CAS(Center Authentication Service)的结构实现的,可以访问它们的官方网站获得更详细的信息http://www.jasig.org/cas。
在了解过这些基础知识之后,我们可以开始研究如何使用Spring Security实现单点登录了。
从JA-SIG的官方网站下载cas-server,本文写作时的最新稳定版为3.3.2。http://www.ja-sig.org/downloads/cas/cas-server-3.3.2-release.zip。
将下载得到的cas-server-3.3.2-release.zip文件解压后,可以得到一大堆的目录和文件,我们这里需要的是modules目录下的cas-server-webapp-3.3.2.war。
把cas-server-webapp-3.3.2.war放到ch09\server目录下,然后执行run.bat就可启动CAS中央认证服务器。
我们已在pom.xml中配置好了启用SSL所需的配置,包括使用的server.jks和对应密码,之后我们可以通过https://localhost:9443/cas/login访问CAS中央认证服务器。
默认情况下,只要输入相同的用户名和密码就可以登陆系统,比如我们使用user/user进行登陆。
这就证明中央认证服务器已经跑起来了。下一步我们来配置Spring Security,让它通过中央认证服务器进行登录。
首先要添加对cas的插件和cas客户端的依赖库。因为我们使用了maven2,所以只需要在pom.xml中添加一个依赖即可。
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas-client</artifactId> <version>2.0.4</version> </dependency>
如果有人很不幸的没有使用maven2,那么就需要手工把去下面这些依赖库了。
spring-security-cas-client-2.0.4.jar spring-dao-2.0.8.jar aopalliance-1.0.jar cas-client-core-3.1.3.jar
大家可以去spring和ja-sig的网站去寻找这些依赖库。
首先修改http部分。
<http auto-config='true' entry-point-ref="casProcessingFilterEntryPoint"> <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> <intercept-url pattern="/index.jsp" access="ROLE_USER" /> <intercept-url pattern="/" access="ROLE_USER" /> <logout logout-success-url="/cas-logout.jsp"/> </http>
添加一个entry-point-ref引用cas提供的casProcessingFilterEntryPoint,这样在验证用户登录时就用上cas提供的机制了。 |
|
修改注销页面,将注销请求转发给cas处理。 <a href="https://localhost:9443/cas/logout">Logout of CAS</a> |
然后要提供userService和authenticationManager,二者会被注入到cas的类中用来进行登录之后的用户授权。
<user-service id="userService"> <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="user" password="user" authorities="ROLE_USER" /> </user-service> <authentication-manager alias="authenticationManager"/>
为了演示方便,我们将用户信息直接写在了配置文件中,之后cas的类就可以通过id获得userService,以此获得其中定义的用户信息和对应的权限。 |
|
对于authenticationManager来说,我们没有创建一个新实例,而是使用了“别名”(alias),这是因为在之前的namespace配置时已经自动生成了authenticationManager的实例,cas只需要知道这个实例的别名就可以直接调用。 |
创建cas的filter, entryPoint, serviceProperties和authenticationProvider。
<beans:bean id="casProcessingFilter" class="org.springframework.security.ui.cas.CasProcessingFilter"> <custom-filter after="CAS_PROCESSING_FILTER"/> <beans:property name="authenticationManager" ref="authenticationManager"/> <beans:property name="authenticationFailureUrl" value="/casfailed.jsp" /> <beans:property name="defaultTargetUrl" value="/" /> </beans:bean> <beans:bean id="casProcessingFilterEntryPoint" class="org.springframework.security.ui.cas.CasProcessingFilterEntryPoint"> <beans:property name="loginUrl" value="https://localhost:9443/cas/login" /> <beans:property name="serviceProperties" ref="casServiceProperties" /> </beans:bean> <beans:bean id="casServiceProperties" class="org.springframework.security.ui.cas.ServiceProperties"> <beans:property name="service" value="https://localhost:8443/ch09/j_spring_cas_security_check"/> <beans:property name="sendRenew" value="false"/> </beans:bean> <beans:bean id="casAuthenticationProvider" class="org.springframework.security.providers.cas.CasAuthenticationProvider"> <custom-authentication-provider /> <beans:property name="userDetailsService" ref="userService" /> <beans:property name="serviceProperties" ref="casServiceProperties" /> <beans:property name="ticketValidator"> <beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <beans:constructor-arg index="0" value="https://localhost:9443/cas" /> </beans:bean> </beans:property> <beans:property name="key" value="ch09" /> </beans:bean>
casProcessingFilter最终是要放到Spring security的安全过滤器链中才能发挥作用的。这里使用的customer-filter就会把它放到CAS_PROCESSING_FILTER位置的后面。 这个位置具体是在LogoutFilter和AuthenticationProcessingFilter之间,这样既不会影响注销操作,又可以在用户进行表单登陆之前拦截用户请求进行cas认证了。 |
|
当用户尚未登录时,会跳转到这个cas的登录页面进行登录。 |
|
用户在cas登录成功后,再次跳转回原系统时请求的页面。 CasProcessingFilter会处理这个请求,从cas获得已登录的用户信息,并对用户进行授权。 |
|
使用custom-authentication-provider之后,Spring Security其他的权限模块会从这个bean中获得权限验证信息。 |
|
系统需要验证当前用户的tickets是否有效。 |
经过了这么多的配置,我们终于把cas功能添加到spring security中了,看着一堆堆一串串的配置文件,好似又回到了acegi的时代,可怕啊。
下面运行系统,尝试使用了cas的权限控制之后有什么不同。
首先要保证cas中央认证服务器已经启动了。子系统的pom.xml中也已经配置好了SSL,所以可以进入ch09执行run.bat启动子系统。
现在直接访问http://localhost:8080/ch09/不再会弹出登陆页面,而是会跳转到cas中央认证服务器上进行登录。
输入user/user后进行登录,系统不会做丝毫的停留,直接跳转回我们的子系统,这时我们已经登录到系统中了。
我们再来试试注销,点击logout会进入cas-logout.jsp。
在此点击Logout of CAS会跳转至cas进行注销。
现在我们完成了Spring Security中cas的配置,enjoy it。
在使用cas的时候,我们要为cas中央认证服务器和子系统都配置上SSL,以此来对他们之间交互的数据进行加密。这里我们将使用JDK中包含的keytool工具生成配置SSL所需的密钥。
首先生成一个key store。
keytool -genkey -keyalg RSA -dname "cn=localhost,ou=family168,o=www.family168.com,l=china,st=beijing,c=cn" -alias server -keypass password -keystore server.jks -storepass password
我们会得到一个名为server.jks的文件,它的密码是password,注意cn=localhost部分,这里必须与cas服务器的域名一致,而且不能使用ip,因为我们是在本地localhost测试cas,所以这里设置的就是cn=localhost,在实际生产环境中使用时,要将这里配置为cas服务器的实际域名。
导出密钥
keytool -export -trustcacerts -alias server -file server.cer -keystore server.jks -storepass password
将密钥导入JDK的cacerts
keytool -import -trustcacerts -alias server -file server.cer -keystore D:/apps/jdk1.5.0_15/jre/lib/security/cacerts -storepass password
这里需要把使用实际JDK的安装路径,我们要把密钥导入到JDK的cacerts中。
我们在ch09/certificates/下放了一个genkey.bat,这个批处理文件中已经包含了上述的所有命令,运行它就可以生成我们所需的密钥。
jetty的配置可参考ch09中的pom.xml文件。
<connectors> <connector implementation="org.mortbay.jetty.security.SslSocketConnector"> <port>9443</port> <keystore>../certificates/server.jks</keystore> <password>password</password> <keyPassword>password</keyPassword> <truststore>../certificates/server.jks</truststore> <trustPassword>password</trustPassword> <wantClientAuth>true</wantClientAuth> <needClientAuth>false</needClientAuth> </connector> </connectors> <systemProperties> <systemProperty> <name>javax.net.ssl.trustStore</name> <value>../certificates/server.jks</value> </systemProperty> <systemProperty> <name>javax.net.ssl.trustStorePassword</name> <value>password</value> </systemProperty> </systemProperties>
要运行支持SSL的tomcat,把server.jks文件放到tomcat的conf目录下,然后把下面的连接器添加到server.xml文件中。
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreFile="${catalina.home}/conf/server.jks" keystoreType="JKS" keystorePass="password" truststoreFile="${catalina.home}/conf/server.jks" truststoreType="JKS" truststorePass="password" />
如果你希望客户端没有提供证书的时候SSL链接也能成功,也可以把clientAuth设置成want。
实例在ch103。