I've added a discussion of what JSF 2.0 is doing here:
http://seamframework.org/Documentation/WebVulnerabilitiesAndCountermeasures#H-BuildingTheJSFComponentTreeOnDemand
In client-side state saving, the restore view step is independent of the user's session. All the state necessary to rebuild the view and process the user-initiated event is sent along in the form post. The user could request the form on one day and submit it days later assuming that other session data isn't required (e.g., a login or comment form).
This portability is not present in server-side state saving. The browser merely sends an identifier for looking up the component tree in the session. Without the session, there is no component tree. When that happens, the only reasonable thing for JSF to do is to throw and |ViewExpiredException| or simply render the page again without processing any events.
...that is, until Facelets came along. Facelets offers a "solution" to the stale form problem. By setting the |facelets.BUILD_BEFORE_RESTORE| context-param to true, you can have Facelets reconstruct the component tree in the restore view phase if the one in the session has expired (or the session as a whole expired). This would seem to match the behavior of client-side state saving. It does not. The reason is that the contract of requiring an existing view state to exist is now absent and a postback is no different than a page action. A form on a plain HTML page can now submit a post request directly to a JSF application (you do need an empty |javax.faces.ViewState| hidden field for it to work).
To make things even more troubling, Facelets has become the standard view handler in JSF 2.0 and this behavior of Facelets is now the default. So what do we do?
Well, as it turns out, because of a recent spec change, the "build before restore" feature has been inadvertently disabled in Mojarra, so we may not have to worry about it after all in JSF 2.0. But if it does get "fixed"...
Again, there needs to be a token stored in the request when using server-side state saving, similar to how a complex value is stored a hidden form field under client-side state saving. It's okay to have the component tree rebuilt, but only if you are sure that the request is coming from a valid source. One of the prime reasons for not just using client-side state saving is to reduce the amount of data being transferred back and forth between client and server (other reasons are because serialization takes time and destroys object identity).
After adding this section to the page, I noticed that the rest of the page duplicates a lot of what Christian has written on
http://www.seamframework.org/Documentation/CrossSiteRequestForgery, so likely some merging and cleanup is in order on my part.
-Dan