Spring Security3对CAS的支持主要在这个spring-security-cas-client-3.0.2.RELEASE.jar包中
Spring Security和CAS集成的配置资料不少。这里讲解的比较详细
http://lengyun3566.iteye.com/blog/1358323java
配置方面,主要为下面的部分:web
<security:http auto-config="true" entry-point-ref="casAuthEntryPoint" access-denied-page="/error/403.jsp"> <security:custom-filter ref="casAuthenticationFilter" position="CAS_FILTER"/> <security:form-login login-page="/login.jsp"/> <security:logout logout-success-url="/login.jsp"/> <security:intercept-url pattern="/admin.jsp*" access="ROLE_ADMIN"/> <security:intercept-url pattern="/index.jsp*" access="ROLE_USER,ROLE_ADMIN"/> <security:intercept-url pattern="/home.jsp*" access="ROLE_USER,ROLE_ADMIN"/> <security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN"/> </security:http> <security:authentication-manager alias="authenticationmanager"> <security:authentication-provider ref="casAuthenticationProvider"/> </security:authentication-manager> <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> <property name="ticketValidator" ref="casTicketValidator"/> <property name="serviceProperties" ref="casService"/> <property name="key" value="docms"/> <property name="authenticationUserDetailsService" ref="authenticationUserDetailsService"/> </bean> <bean id="casAuthEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> <property name="loginUrl" value="https://server:8443/cas/login"/> <property name="serviceProperties" ref="casService"/> </bean> <bean id="casService" class="org.springframework.security.cas.ServiceProperties"> <property name="service" value="http://localhost:8888/docms/j_spring_cas_security_check"/> </bean> <bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> <property name="authenticationManager" ref="authenticationmanager"/> </bean> <bean id="casTicketValidator" class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <constructor-arg value="https://server:8443/cas/"/> </bean> <bean id="authenticationUserDetailsService" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <property name="userDetailsService" ref="userDetailsManager"/> </bean>
这里须要强调一下http标签的entry-point-ref属性,由于以前没有着重的介绍,英文的意思是入口点引用。为何须要这个入口点呢。这个入口点其实仅仅是被ExceptionTranslationFilter引用的。前面已经介绍过ExceptionTranslationFilter过滤器的做用是异常翻译,在出现认证异常、访问异常时,经过入口点决定redirect、forward的操做。好比如今是form-login的认证方式,若是没有经过UsernamePasswordAuthenticationFilter的认证就直接访问某个被保护的url,那么通过ExceptionTranslationFilter过滤器处理后,先捕获到访问拒绝异常,并把跳转动做交给入口点来处理。form-login的对应入口点类为LoginUrlAuthenticationEntryPoint,这个入口点类的commence方法会redirect或forward到指定的url(form-login标签的login-page属性)
清楚了entry-point-ref属性的意义。那么与CAS集成时,若是访问一个受保护的url,就经过CAS认证对应的入口点org.springframework.security.cas.web.CasAuthenticationEntryPoint类redirect到loginUrl属性所配置的url中,即通常为CAS的认证页面(好比:https://server:8443/cas/login)。spring
下面为CasAuthenticationEntryPoint类的commence方法。其主要任务就是构造跳转的url,再执行redirect动做。根据上面的配置,实际上跳转的url为:https://server:8443/cas/login?service=http%3A%2F%2Flocalhost%3A8888%2Fdocms%2Fj_spring_cas_security_check缓存
public final void commence(final HttpServletRequest servletRequest, final HttpServletResponse response, final AuthenticationException authenticationException) throws IOException, ServletException { final String urlEncodedService = createServiceUrl(servletRequest, response); final String redirectUrl = createRedirectUrl(urlEncodedService); preCommence(servletRequest, response); response.sendRedirect(redirectUrl); }
接下来继续分析custom-filter ref="casAuthenticationFilter" position="CAS_FILTER"网络
这是一个自定义标签,而且在过滤器链中的位置是CAS_FILTER。这个过滤器在什么时候会起做用呢?带着这个疑问继续阅读源码session
CasAuthenticationFilter对应的类路径是app
org.springframework.security.cas.web.CasAuthenticationFilterless
这个类与UsernamePasswordAuthenticationFilter同样,都继承于AbstractAuthenticationProcessingFilter。实际上全部认证过滤器都继承这个抽象类,其过滤器自己只要实现attemptAuthentication方法便可。jsp
CasAuthenticationFilter的构造方法直接向父类的构造方法传入/j_spring_cas_security_check用于判断当前请求的url是否须要进一步的认证处理ide
public CasAuthenticationFilter() { super("/j_spring_cas_security_check"); }
CasAuthenticationFilter类的attemptAuthentication方法源码以下
public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException { //设置用户名为有状态标识符 final String username = CAS_STATEFUL_IDENTIFIER; //获取CAS认证成功后返回的ticket String password = request.getParameter(this.artifactParameter); if (password == null) { password = ""; } //构造UsernamePasswordAuthenticationToken对象 final UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); //由认证管理器完成认证工做 return this.getAuthenticationManager().authenticate(authRequest); }
在以前的源码分析中,已经详细分析了认证管理器AuthenticationManager认证的整个过程,这里就再也不赘述了。
因为AuthenticationManager是依赖于具体的AuthenticationProvider的,因此接下来看
<security:authentication-manager alias="authenticationmanager"> <security:authentication-provider ref="casAuthenticationProvider"/> </security:authentication-manager>
注意这里的ref属性定义。若是没有使用CAS认证,此处通常定义user-service-ref属性。这两个属性的区别在于
ref:直接将ref依赖的bean注入到AuthenticationProvider的providers集合中
user-service-ref:定义DaoAuthenticationProvider的bean注入到AuthenticationProvider的providers集合中,而且DaoAuthenticationProvider的变量userDetailsService由user-service-ref依赖的bean注入。
因而可知,采用CAS认证时,AuthenticationProvider只有AnonymousAuthenticationProvider和CasAuthenticationProvider
继续分析CasAuthenticationProvider是如何完成认证工做的
public Authentication authenticate(Authentication authentication) throws AuthenticationException { //省略若干判断 CasAuthenticationToken result = null; //注意这里的无状态条件。主要用于无httpsession的环境中。如soap调用 if (stateless) { // Try to obtain from cache //经过缓存来存储认证明体。主要避免每次请求最新ticket的网络开销 result = statelessTicketCache.getByTicketId(authentication.getCredentials().toString()); } if (result == null) { result = this.authenticateNow(authentication); result.setDetails(authentication.getDetails()); } if (stateless) { // Add to cache statelessTicketCache.putTicketInCache(result); } return result; } //完成认证工做 private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException { try { //经过cas client的ticketValidator完成ticket校验,并返回身份断言 final Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(), serviceProperties.getService()); //根据断言信息构造UserDetails final UserDetails userDetails = loadUserByAssertion(assertion); //检查帐号状态 userDetailsChecker.check(userDetails); //构造CasAuthenticationToken return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(), userDetails.getAuthorities(), userDetails, assertion); } catch (final TicketValidationException e) { throw new BadCredentialsException(e.getMessage(), e); } } //经过注入的authenticationUserDetailsService根据token中的认证主体即用户名获取UserDetails protected UserDetails loadUserByAssertion(final Assertion assertion) { final CasAssertionAuthenticationToken token = new CasAssertionAuthenticationToken(assertion, ""); return this.authenticationUserDetailsService.loadUserDetails(token); }
须要注意的是为何要定义authenticationUserDetailsService这个bean。因为CAS须要authentication-manager标签下定义<security:authentication-provider ref="casAuthenticationProvider"/>,而不是以前所介绍的
user-service-ref属性,因此这里仅仅定义了一个provider,而没有注入UserDetailsService,因此这里须要单独定义authenticationUserDetailsService这个bean,并注入到CasAuthenticationProvider中。
这里须要对CasAuthenticationToken、CasAssertionAuthenticationToken单独解释一下
CasAuthenticationToken:一个成功经过的CAS认证,与UsernamePasswordAuthenticationToken同样,都是继承于AbstractAuthenticationToken,而且最终会保存到SecurityContext上下文、session中
CasAssertionAuthenticationToken:一个临时的认证对象用于辅助获取UserDetails
配置文件中几个bean定义这里就不一一分析了,都是为了辅助完成CAS认证、跳转的工做。
如今,能够对整个CAS认证的过程总结一下了:
1.客户端发起一个请求,试图访问系统系统中受保护的url
2.各filter链进行拦截并作相应处理,因为没有经过认证,ExceptionTranslationFilter过滤器会捕获到访问拒绝异常,并把该异常交给入口点处理
3.CAS 认证对应的入口点直接跳转到CAS Server端的登陆界面,并携带参数service(通常为url:……/j_spring_cas_security_check)
4.CAS Server对登陆信息进行处理,若是登陆成功,就跳转到应用系统中service指定的url,并携带ticket
5.应用系统中的各filter链再次对该url拦截,此时CasAuthenticationFilter拦截到j_spring_cas_security_check,就会对ticket进行验证,验证成功返回一个身份断言,再经过身份断言从当前应用系统中获取对应的UserDetails、GrantedAuthority。此时,若是步骤1中受保护的url权限列表有一个权限存在于GrantedAuthority列表中,说明有权限访问,直接响应客户端所试图访问的url