Announcement Announcement Module
Collapse
No announcement yet.
Adding "Acceptable Use" page to web flow in CAS (with SPNEGO) Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Adding "Acceptable Use" page to web flow in CAS (with SPNEGO)

    Hello,

    I've got an instance of CAS (which uses Spring Web Flow) configured to use SPNEGO for Windows authentication. When SPNEGO "works", CAS authenticates the user transparently. When SPNEGO "breaks" (eg, misconfiguration, accessing from the wrong host, etc), the user is presented with a login page (casLoginView). Everything works great.

    For reference, here is my web flow XML:
    Code:
    <flow xmlns="http://www.springframework.org/schema/webflow"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.springframework.org/schema/webflow
                              http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
    
        <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 '' &amp;&amp; externalContext.requestParameterMap['gateway'] neq null &amp;&amp; flowScope.service neq null" then="gatewayServicesManagementCheck" else="startAuthenticate" />
        </decision-state>
        
        <decision-state id="hasServiceCheck">
    	<if test="flowScope.service != null" then="renewRequestCheck" else="viewGenericLoginSuccess" />
        </decision-state>
        
        <decision-state id="renewRequestCheck">
    	<if test="externalContext.requestParameterMap['renew'] neq '' &amp;&amp; externalContext.requestParameterMap['renew'] neq null" then="startAuthenticate" else="generateServiceTicket" />
        </decision-state>
        
        <decision-state id="warn">
    	<if test="flowScope.warnCookieValue" then="showWarningView" else="redirect" />
        </decision-state>
        
        
    
        <action-state id="startAuthenticate">  
    	<evaluate expression="negociateSpnego" />  
    	<transition on="success" to="spnego" />
        </action-state> 
        
        <action-state id="spnego">  
    	<evaluate expression="spnego" />  
    	<transition on="success" to="sendTicketGrantingTicket" />  
    	<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="realSubmit">
    	    <evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
    	</transition>
        </view-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>
    
        <decision-state id="postRedirectDecision">
            <if test="requestScope.response.responseType.name() eq 'POST'" then="postView" else="redirectView" />
        </decision-state>
    
        
        <end-state id="viewGenericLoginSuccess" view="casLoginGenericSuccessView" />
    
        <end-state id="showWarningView" view="casLoginConfirmView" />
    
        <end-state id="postView" view="postResponseView">
            <on-entry>
                <set name="requestScope.parameters" value="requestScope.response.attributes" />
                <set name="requestScope.originalUrl" value="flowScope.service.id" />
            </on-entry>
        </end-state>
    
        <end-state id="redirectView" view="externalRedirect:${requestScope.response.url}" />
    	
        <end-state id="viewServiceErrorView" view="viewServiceErrorView" />
        
        <end-state id="viewServiceSsoErrorView" view="viewServiceSsoErrorView" />
        
        
        <global-transitions>
    	<transition to="viewServiceErrorView" on-exception="org.springframework.webflow.execution.repository.NoSuchFlowExecutionException" />
    	<transition to="viewServiceSsoErrorView" on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException" />
    	<transition to="viewServiceErrorView" on-exception="org.jasig.cas.services.UnauthorizedServiceException" />
        </global-transitions>
    </flow>

    Now, I am trying to insert a new view into the Web Flow so that before authentication occurs, the user is presented with a page describing an "acceptable use policy". So, I added a view-state for this page, and altered the decision-states that currently go to "startAuthenticate" so that they now transition to this new page. Then, the acceptable use page has a submit button, and on the submit event, it transitions to "startAuthenticate".

    I made sure to include these 3 hidden inputs on the acceptable use page:
    Code:
    <input type="hidden" name="lt" value="${loginTicket}"/>
    <input type="hidden" name="execution" value="${flowExecutionKey}"/>
    <input type="hidden" name="_eventId" value="submit"/>
    So, now what happens is that the user is successfully presented with this new view, and upon clicking the "submit" button, the flow continues execution to the "startAuthenticate" state. But here's where it gets weird...

    When SPNEGO "works", everything happens as expected. The user clicks the "submit" button, gets transparently authenticated, and winds up at the page originally requested.

    However, when SPNEGO does not work (my main test case is trying to access the page from the server instead of the client), instead of being dropped to a login page (CAS's "casLoginView"), the new acceptable use page gets popped up again. Every time the submit button is pressed, this new page is displayed again. I can't figure out why this is happening.

    I upped CAS's logging levels to DEBUG/TRACE, and was able to see that the flow execution does indeed continue to "startAuthenticate", and then to "spnego", "generateLoginTicket", and finally "casLoginView". It even states that the casLoginView JSP gets rendered. However, inexplicably, right after that, a new execution starts for the entire login flow - which then lands it at the acceptable use page again...

    Is there any mechanism by which this new execution would get launched? Is there something else I need to add to my acceptable use page so that it can properly interact with the flow execution?

    I can provide log files if requested.

    Thanks,
    Doug

  • #2
    Ok, I mostly found the problem. I already knew that SPNEGO authentication does not work when the request originates from the same server where CAS/SPNEGO is deployed (requires a 3-system configuration). However, I apparently incorrectly assumed that attempting to access a site from the CAS/SPNEGO server would result in the same behavior as if SPNEGO itself was "broken".

    However, on a hunch, I purposefully broke SPNEGO's configuration, redeployed, and then attempted access from a workstation. Turns out, it worked as expected - "acceptable use" page followed by being dropped to a login form. So it appears that when SPNEGO is attempted from the application server, it is doing something (I have no idea what) to mangle the request/flow.

    So, to remedy, I created a bean and a new action-state that determines if the servlet request is coming from the local machine or from an external location - if local, just use login form, if external, use SPNEGO. This works perfectly.

    Comment

    Working...
    X