You can't avoid that. The enduser has full freedom over manipulating the request URL. Your best bet would be to stick to a single view/URL wherein the wizard steps are conditionally (ajax-)rendered based on the state of the view. This way the enduser can only navigate by the buttons provided in the UI and not by manipulating the URL.
E.g.
<h:panelGroup rendered="#{wizard.step == 1}">
<ui:include src="/WEB-INF/wizard/step1.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{wizard.step == 2}">
<ui:include src="/WEB-INF/wizard/step2.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{wizard.step == 3}">
<ui:include src="/WEB-INF/wizard/step3.xhtml" />
</h:panelGroup>
Also, this way you can get away with a single @ViewScoped
bean instead of a @ConversationScoped
one (with MyFaces CODI you would be able to use JSF @ViewScoped
on a CDI @Named
, for the case that is important).
Further, PrimeFaces has a <p:wizard>
component which does almost exactly like that. You may find it useful in order to save yourself from some painful boilerplate code as to validation and such.