Thanks for the quick response, Scott.
We are indeed replicating session state as well as the ticket registry state
between the 2 servers behind the load balancer. The introduction of this
per-server private key is a new source of server state that has to be
handled for load balancing to work properly, and hopefully our last hurdle
for our CAS cluster. We want server failover to work transparently to our
users, so even if we had sticky sessions, we would still have issues as
users would potentially get the CAS 500 error page when this happened.
I have looked at the source code and the CAS spec a little more. I see that
the web flow key is being used as the login ticket "lt" parameter on the
login page, and the spec indicates that a probabilistically unique key must
be used. I see that the keys are being generated by concatenating a random
uuid with the execution id and the next snapshot id, then encrypting the
result, and encoding to a hex string. What if we just remove the encrypt
step? The addition of the random uuid (which is using a SecureRandom
underneath the covers) should make this combination probabilistically
unique, even before encrypting it. It seems like this could be viewed as a
random nonce value that just happens to also contain the execution id and
snapshot id as a little extra payload--the encryption is unnecessary? The
only thing that I can think that the encryption step is doing is hiding the
execution id and snapshot id from users. Is that really important? Users
can already observe their ticket granting ticket by browsing their cookies.
This would eliminate the need for a shared key across servers, making
administering and configuring a CAS cluster easier. I have modified the
encrypt/decrypt methods in the CasFlowExecutionKeyFactory class to eliminate
the encryption/decryption:
protected String decrypt(final String value) {
if (value == null) {
return null;
}
try {
// final Cipher cipher =
Cipher.getInstance(this.cipherAlgorithm);
// cipher.init(Cipher.DECRYPT_MODE, this.key, this.ivs);
// return new
String(cipher.doFinal(hexStringToByteArray(value)));
return new String(hexStringToByteArray(value));
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
protected String encrypt(final String value) {
if (value == null) {
return null;
}
try {
// final Cipher cipher =
Cipher.getInstance(this.cipherAlgorithm);
// cipher.init(Cipher.ENCRYPT_MODE, this.key, this.ivs);
return byteArrayToHexString(value.getBytes());
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
My initial testing indicates that this works. All of my login attempts
appear to work when hitting my 2 servers behind the load balancer in round
robin mode, unlike when encryption/decryption is in place, and many of my
login attempts fail because the login ticket is being decrypted on a
different server (with a different key) than the server than it was
encrypted on.
So, my 3 questions are:
1. Is the encryption of the web flow key really necessary, or is the
addition of the random uuid to the key sufficient? If the encryption isn't
necessary, it would make clustering CAS easier for those of us that need to
do this. Sharing keys is a bit of a hassle.
2. If we can, indeed, eliminate the encryption, and we are replicating
session state, do you believe that the web flow login process should work
fine when the load balancer bounces requests to different servers? This is
our observed behavior, but we haven't really hit it hard yet.
3. Are there any other problems that you can think of that we might have in
clustering CAS behind a load balancer running in round robin mode (like I
said we are already replicating the session as well as the ticket registry)?
Thanks again for your help,
--Jon
On Wed, Apr 6, 2011 at 6:30 AM, Scott Battaglia
<[email protected]>wrote:
> On Wed, Apr 6, 2011 at 12:30 AM, Jon Oler <[email protected]> wrote:
>
>> <snip />
>
>
>
>> Some questions:
>>
>> 1. Are there any possible problems with the workaround I've implemented
>> that I don't know about right now? Is there a better way to workaround the
>> issue?
>>
>
> Yes, there are possible problems. The login token is now easily guessed.
> There are two options: sticky sessions and sharing the key.
>
>
>> 2. Is it necessary to encrypt this web flow session key at all? I don't
>> have the big picture of what is going on here, but all of our CAS
>> interactions are happening over https, so I'm not sure that it's necessary
>> for me to encrypt these session keys. If it's true that this encryption is
>> not neessary when using https, perhaps a flag in a config file, to disable
>> this encryption would be a good idea?
>>
>
> Yes, its necessary. The Login token is required to be hard to guess. This
> happened automatically in Spring Web Flow 1. In Spring Web Flow 2, they
> changed their method of generating tokens which meant that we were no longer
> compliant with our protocol. A hard to guess token prevents scripted methods
> of accessing the CAS server. The only reason the value is encrypted is
> because it needs to meet our needs and still encode the information that
> Spring Web Flow requires (the execution and snapshot ids)
>
>
>> 3. If encryption really is necessary, then perhaps the encryption key
>> should be externalized into a config file, so that I can set it to be the
>> same key for both of my servers? This doesn't appear to be possible (or at
>> least easy) with the way it's coded now.
>>
>>
> There is a constructor that takes a secret key. Is that not working? If
> not, we'll obviously make it work better :-) Though to be honest I'm amazed
> that web flow was working in a load balanced fashion before this change. The
> flow is stored in the users session. Were you sharing session state?
>
> Thanks
> Scott
>
>
>
>> Some debug output from server1 (note the key 26CB737...):
>>
>> 2011-04-05 23:26:40,325 DEBUG
>> [org.springframework.webflow.conversation.impl.SessionBindingConversationManager]
>> - <Putting conversation attribute 'name' with value login>
>> 2011-04-05 23:26:40,325 DEBUG
>> [org.springframework.webflow.conversation.impl.SessionBindingConversationManager]
>> - <Putting conversation attribute 'caption' with value null>
>> 2011-04-05 23:26:40,325 DEBUG
>> [org.springframework.webflow.conversation.impl.SessionBindingConversationManager]
>> - <Putting conversation attribute 'description' with value null>
>> 2011-04-05 23:26:40,325 DEBUG
>> [org.springframework.webflow.conversation.impl.SessionBindingConversationManager]
>> - <Putting conversation attribute 'flowExecutionSnapshotGroup' with value
>> org.springframework.webflow.execution.repository.impl.SimpleFlowExecutionSnapshotGroup@21645be6
>> >
>> 2011-04-05 23:26:40,429 DEBUG
>> [org.springframework.webflow.engine.impl.FlowExecutionImpl] - <Assigned key
>> 26CB7376FF1CBB089B50B84AF907EE519166845CABB838FAF2D8F4468453C208F360C11FBE172E0FA6B6DD824342F4B5>
>>
>> Some debug output from server2 (note the key 26CB737...):
>>
>> 2011-04-05 23:26:53,243 DEBUG
>> [org.springframework.web.servlet.DispatcherServlet] - <DispatcherServlet
>> with name 'cas' processing POST request for [/cas/login]>
>> 2011-04-05 23:26:53,243 DEBUG
>> [org.springframework.webflow.mvc.servlet.FlowHandlerMapping] - <Mapping
>> request with URI '/cas/login' to flow with id 'login'>
>> 2011-04-05 23:26:53,243 DEBUG
>> [org.springframework.webflow.executor.FlowExecutorImpl] - <Resuming flow
>> execution with key
>> '26CB7376FF1CBB089B50B84AF907EE519166845CABB838FAF2D8F4468453C208F360C11FBE172E0FA6B6DD824342F4B5>
>> 2011-04-05 23:26:53,255 DEBUG
>> [org.springframework.web.servlet.DispatcherServlet] - <Could not complete
>> request>
>> java.lang.RuntimeException: javax.crypto.BadPaddingException: Given final
>> block not properly padded
>> at
>> org.jasig.cas.web.flow.CasFlowExecutionKeyFactory.decrypt(CasFlowExecutionKeyFactory.java:92)
>> at
>> org.jasig.cas.web.flow.CasFlowExecutionKeyFactory.parseFlowExecutionKey(CasFlowExecutionKeyFactory.java:168)
>> at
>> org.springframework.webflow.executor.FlowExecutorImpl.resumeExecution(FlowExecutorImpl.java:164)
>> at
>> org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:183)
>> at
>> org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790)
>> at
>> org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
>> at
>> org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
>> at
>> org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
>> at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
>> at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
>> at
>> org.jasig.cas.web.init.SafeDispatcherServlet.service_aroundBody2(SafeDispatcherServlet.java:115)
>> at
>> org.jasig.cas.web.init.SafeDispatcherServlet.service_aroundBody3$advice(SafeDispatcherServlet.java:44)
>> at
>> org.jasig.cas.web.init.SafeDispatcherServlet.service(SafeDispatcherServlet.java:1)
>> at
>> org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
>> at
>> org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
>> at
>> com.github.inspektr.common.web.ClientInfoThreadLocalFilter.doFilter(ClientInfoThreadLocalFilter.java:63)
>> at
>> org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
>> at
>> org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
>> at
>> org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
>> at
>> org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
>> at
>> org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:237)
>> at
>> org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
>> at
>> org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
>> at
>> org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
>> at
>> org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
>> at
>> org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
>> at
>> org.terracotta.modules.tomcat.tomcat_5_5.SessionValve55.tcInvoke(SessionValve55.java:99)
>> at
>> org.terracotta.modules.tomcat.tomcat_5_5.SessionValve55.invoke(SessionValve55.java:86)
>> at
>> org.terracotta.session.ModernTomcatSessionValve.invoke(ModernTomcatSessionValve.java:66)
>> at
>> org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
>> at
>> org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
>> at
>> org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
>> at
>> org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
>> at
>> org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)
>> at
>> org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
>> at
>> org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
>> at java.lang.Thread.run(Thread.java:662)
>> Caused by: javax.crypto.BadPaddingException: Given final block not
>> properly padded
>> at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
>> at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
>> at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
>> at javax.crypto.Cipher.doFinal(DashoA13*..)
>> at
>> org.jasig.cas.web.flow.CasFlowExecutionKeyFactory.decrypt(CasFlowExecutionKeyFactory.java:90)
>> ... 36 more
>> 2011-04-05 23:26:53,275 DEBUG
>> [org.jasig.cas.web.support.CasArgumentExtractor] - <Extractor generated
>> service for:
>> http://test-web.acteea.local/Admin/j_spring_cas_security_check>
>>
>> Thanks,
>>
>> --Jon
>> --
>> You are currently subscribed to [email protected] as:
>> [email protected]
>>
>> To unsubscribe, change settings or access archives, see
>> http://www.ja-sig.org/wiki/display/JSG/cas-user
>>
>
> --
> You are currently subscribed to [email protected] as:
> [email protected]
> To unsubscribe, change settings or access archives, see
> http://www.ja-sig.org/wiki/display/JSG/cas-user
>
>
--
You are currently subscribed to [email protected] as:
[email protected]
To unsubscribe, change settings or access archives, see
http://www.ja-sig.org/wiki/display/JSG/cas-user