前面三篇文章里面提到的由于对源码做了改动,这对开源程序是一种损害。故在前面的基础之上,将原先的代码重构,完全解耦,另外又重新阅读了login-webflow.xml 的逻辑,只在配置文件中引入自己写的类,实现SSO单点登录的功能。
改动之处主要在:
- <span style="font-family:SimHei;font-size:18px;">
- <var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
- <on-start>
- <evaluate expression="initialFlowSetupAction" />
- </on-start>
- <decision-state id="ticketGrantingTicketExistsCheck">
- <if test="flowScope.ticketGrantingTicketId neq null" then=<span style="color:#CCFFFF;"><span style="color:#000000;"><span style="background-color: rgb(102, 255, 255);">"hasServiceCheck"</span></span> </span>else="gatewayRequestCheck" />
- </decision-state>
- <decision-state id="gatewayRequestCheck">
- <if test="externalContext.requestParameterMap['gateway'] neq '' && externalContext.requestParameterMap['gateway'] neq null && flowScope.service neq null" then="gatewayServicesManagementCheck" else="generateLoginTicket" />
- </decision-state>
- <decision-state id=<span style="background-color: rgb(102, 255, 255);">"hasServiceCheck"</span>>
- <span style="color:#FF0000;"><on-entry>
- <evaluate expression="serviceRegisterCheck.doChangeStatus(flowRequestContext)" />
- </on-entry></span>
- <if test="flowScope.service != null" then="renewRequestCheck" else="viewGenericLoginSuccess" />
- </decision-state>
- <decision-state id="renewRequestCheck">
- <if test="externalContext.requestParameterMap['renew'] neq '' && externalContext.requestParameterMap['renew'] neq null" then="generateLoginTicket" else=<span style="background-color: rgb(102, 255, 255);">"serviceRegisterCheck"</span> />
- </decision-state>
- <!--
- The "warn" action makes the determination of whether to redirect directly to the requested
- service or display the "confirmation" page to go back to the server.
- -->
- <decision-state id="warn">
- <if test="flowScope.warnCookieValue" then="showWarningView" else="redirect" />
- </decision-state>
- <span style="color:#CC0000;"><action-state id=<span style="background-color: rgb(102, 255, 255);">"serviceRegisterCheck"</span>>
- <span style="color:#330099;"><evaluate expression="serviceRegisterCheck.doCheckRedirection(flowRequestContext)" /></span>
- <transition on="success" to="generateServiceTicket"/>
- <transition on="error" to="viewServiceErrorView"/>
- </action-state></span>
- <!--
- <action-state id="startAuthenticate">
- <action bean="x509Check" />
- <transition on="success" to="sendTicketGrantingTicket" />
- <transition on="warn" to="warn" />
- <transition on="error" to="generateLoginTicket" />
- </action-state>
- -->
- <action-state id="generateLoginTicket">
- <evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />
- <transition on="success" to="viewLoginForm" />
- </action-state>
- <view-state id="viewLoginForm" view="casLoginView" model="credentials">
- <binder>
- <binding property="username" />
- <binding property="password" />
- </binder>
- <on-entry>
- <set name="viewScope.commandName" value="'credentials'" />
- </on-entry>
- <transition on="submit" bind="true" validate="true" to="loginServiceCheck">
- <evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
- </transition>
- </view-state>
- <span style="color:#FF6600;"><action-state id="loginServiceCheck">
- <span style="color:#000099;"> <evaluate expression="serviceRegisterCheck.doCheckURL(flowRequestContext,flowScope.credentials)" /></span>
- <transition on="success" to="realSubmit"/>
- <transition on="error" to="viewServiceErrorView"/>
- </action-state></span>
- <action-state id="realSubmit">
- <evaluate expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" />
- <transition on="warn" to="warn" />
- <transition on="success" to="sendTicketGrantingTicket" />
- <transition on="error" to="generateLoginTicket" />
- </action-state>
- <action-state id="sendTicketGrantingTicket">
- <evaluate expression="sendTicketGrantingTicketAction" />
- <transition to="serviceCheck" />
- </action-state>
- <decision-state id="serviceCheck">
- <if test="flowScope.service neq null" then="generateServiceTicket" else="viewGenericLoginSuccess" />
- </decision-state>
- <action-state id="generateServiceTicket">
- <evaluate expression="generateServiceTicketAction" />
- <transition on="success" to ="warn" />
- <transition on="error" to="generateLoginTicket" />
- <transition on="gateway" to="gatewayServicesManagementCheck" />
- </action-state>
- <action-state id="gatewayServicesManagementCheck">
- <evaluate expression="gatewayServicesManagementCheck" />
- <transition on="success" to="redirect" />
- </action-state>
- <action-state id="redirect">
- <evaluate expression="flowScope.service.getResponse(requestScope.serviceTicketId)" result-type="org.jasig.cas.authentication.principal.Response" result="requestScope.response" />
- <transition to="postRedirectDecision" />
- </action-state>
- </span>
<var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
<on-start>
<evaluate expression="initialFlowSetupAction" />
</on-start>
<decision-state id="ticketGrantingTicketExistsCheck">
<if test="flowScope.ticketGrantingTicketId neq null" then="hasServiceCheck" else="gatewayRequestCheck" />
</decision-state>
<decision-state id="gatewayRequestCheck">
<if test="externalContext.requestParameterMap['gateway'] neq '' && externalContext.requestParameterMap['gateway'] neq null && flowScope.service neq null" then="gatewayServicesManagementCheck" else="generateLoginTicket" />
</decision-state>
<decision-state id="hasServiceCheck">
<on-entry>
<evaluate expression="serviceRegisterCheck.doChangeStatus(flowRequestContext)" />
</on-entry>
<if test="flowScope.service != null" then="renewRequestCheck" else="viewGenericLoginSuccess" />
</decision-state>
<decision-state id="renewRequestCheck">
<if test="externalContext.requestParameterMap['renew'] neq '' && externalContext.requestParameterMap['renew'] neq null" then="generateLoginTicket" else="serviceRegisterCheck" />
</decision-state>
<!--
The "warn" action makes the determination of whether to redirect directly to the requested
service or display the "confirmation" page to go back to the server.
-->
<decision-state id="warn">
<if test="flowScope.warnCookieValue" then="showWarningView" else="redirect" />
</decision-state>
<action-state id="serviceRegisterCheck">
<evaluate expression="serviceRegisterCheck.doCheckRedirection(flowRequestContext)" />
<transition on="success" to="generateServiceTicket"/>
<transition on="error" to="viewServiceErrorView"/>
</action-state>
<!--
<action-state id="startAuthenticate">
<action bean="x509Check" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="warn" to="warn" />
<transition on="error" to="generateLoginTicket" />
</action-state>
-->
<action-state id="generateLoginTicket">
<evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />
<transition on="success" to="viewLoginForm" />
</action-state>
<view-state id="viewLoginForm" view="casLoginView" model="credentials">
<binder>
<binding property="username" />
<binding property="password" />
</binder>
<on-entry>
<set name="viewScope.commandName" value="'credentials'" />
</on-entry>
<transition on="submit" bind="true" validate="true" to="loginServiceCheck">
<evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
</transition>
</view-state>
<action-state id="loginServiceCheck">
<evaluate expression="serviceRegisterCheck.doCheckURL(flowRequestContext,flowScope.credentials)" />
<transition on="success" to="realSubmit"/>
<transition on="error" to="viewServiceErrorView"/>
</action-state>
<action-state id="realSubmit">
<evaluate expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" />
<transition on="warn" to="warn" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="generateLoginTicket" />
</action-state>
<action-state id="sendTicketGrantingTicket">
<evaluate expression="sendTicketGrantingTicketAction" />
<transition to="serviceCheck" />
</action-state>
<decision-state id="serviceCheck">
<if test="flowScope.service neq null" then="generateServiceTicket" else="viewGenericLoginSuccess" />
</decision-state>
<action-state id="generateServiceTicket">
<evaluate expression="generateServiceTicketAction" />
<transition on="success" to ="warn" />
<transition on="error" to="generateLoginTicket" />
<transition on="gateway" to="gatewayServicesManagementCheck" />
</action-state>
<action-state id="gatewayServicesManagementCheck">
<evaluate expression="gatewayServicesManagementCheck" />
<transition on="success" to="redirect" />
</action-state>
<action-state id="redirect">
<evaluate expression="flowScope.service.getResponse(requestScope.serviceTicketId)" result-type="org.jasig.cas.authentication.principal.Response" result="requestScope.response" />
<transition to="postRedirectDecision" />
</action-state>
根据CAS登录的流程:
(1)验证TGT存在?true:在进入hasServiceCheck的时候增加了on-entry节点,该节点的功能是验证TGT是否expired,如果已经失效了,那么应该修改用户表里面的Token状态为未登录的状态(我们这里的用户表增加Token的功能主要是为了限制用户只能在一个地方登录。因此当用户已经在一个地方登录Token=1,退出或者关闭浏览器或者TGT失效的情况下回更改 Token = 0 )。
(2)第二个增加的流程是:当TGT存在的时候会进入验证reNew元素,如果renew ==null,说明不需要重新登录,那么这时候验证该URL是否在用户的关联列表里面,这个在数据库的User_Apps表里面会查询。如果存在就继续如果不存在,那么拒绝用户访问该应用。
(3)第三个需要更改的地方就是:当用户提交登录信息的时候,流程里面会有个Context和用户信息Credentials的绑定,这个时候还没有验证用户还没有生成TGT,然后我们在doBind和dorealSubmit之间增加URL的验证环节,考虑绑定之前还没有用户信息,所以不能增加验证,考虑提交之后会生成TGT这个时候再验证URL合法性,已经失去了意义,所以在中间这个环节增加节点,且不与原来的代码耦合,这样代码完全独立,只根据上下文获取信息。
上图为增加的几个主要的类,第一个UserLoginedOrNotAuthenticationHandler.Java类,部署在deploymengConfig.xml里面的authenticationHandler的<list>节点里面。
- <bean class="<span style="font-family:SimHei;">xxx</span>.<span style="font-family:SimHei;">xxx</span>.<span style="font-family:SimHei;">xxx</span>.auth.authentication.handler.support.UserLoginedOrNotAuthenticationHandler">
- <property name="queryDatabaseAuthenticationHandler" ref="queryDatabaseAuthenticationHandler"/>
- <property name="modifyLoginedStatusAttributeDAO" ref="modifyLoginedStatusAttributeDAO"/>
- </bean>
<bean class="xxx.xxx.xxx.auth.authentication.handler.support.UserLoginedOrNotAuthenticationHandler">
<property name="queryDatabaseAuthenticationHandler" ref="queryDatabaseAuthenticationHandler"/>
<property name="modifyLoginedStatusAttributeDAO" ref="modifyLoginedStatusAttributeDAO"/>
</bean>
这里面引用了源码里面的QueryDatabaseAuthenticationHandler类的验证用户名和密码的功能,然后自己添加了第二个属性用于更改用户登录状态的类,这个类引用了jdbcTemplate,用户连接数据库。
第二个包里面是两个DAO层用于修改相应的数据库状态。分别是验证用户URL和修改登录Token的。里面的具体结构就不在这里详述了。
第三个包里面是重构源码中的LogoutController类,然后再配置文件中,class的值替换为该类。这里面实现了退出时修改用户的Token=0,其他与源码保持一致。
第四个主要是应用在login-webflow.xml里面,该类引用了上面两个DAO。
credentials属性为第一次登陆的之后记录credentials,此后沿用这个credentials。当用户重定向以后从request里面得不到用户信息,所以这里有必要保存