剛剛接觸spring webflow,相關的資料並不是很多,並且大都是那種入門的 .xml文件的配置。
用到的CAS 最新的4.0版本用的就是web-flow流,前台頁面使用的是表單提交。於是我就碰到了一個問題,問題描述如下:
第一次輸入錯誤的用戶名密碼提示用戶名密碼錯誤,第二次輸入了正確的用戶名和密碼仍然報錯,校驗不通過。
在使用cas之前的登錄頁面如果輸入用戶名和密碼錯誤的話,會彈出alert提示框,這個是使用ajax請求實現的。而ajax請求的一般都是那種spring mvc的controller,並且可以異步根據返回值回掉。但是cas的源碼里面使用的是表單提交,表單提交不是異步的,所以除非加iframe,否則沒法回調函數來刷新頁面。而加了iframe之后也存在許多的改動,后續的工作量也不可評估。(為什么一定要回調呢,因為cas的頁面表單里不只有用戶名和密碼輸入框,還有個討厭的隱藏元素:loginticket 和 flowExecutionKey,cas處理每次請求都需要重新生成一個 loginticket票據)。
想過幾種解決方案,剛開始都是想webflow和mvc整合,兩種方式:
1. 將login-webflow.xml中的end-state標簽中的externalRedirect修改成我的某個controller並在參數里面帶上錯誤信息,controller返回一個對象給到前端(一般是code:-1;msg:"";date:****這樣的json串的形式)。那么前端ajax調用login這個webflow的時候,就會重定向到我定義的這個controller,並且拿到這個controller的返回值進行回調。
<end-state id="redirectView" view="externalRedirect:http://user/res?code=-1&msg="error"" />
但是這樣有一個問題,彈出alert框之后點擊確定,alert框消失了,再重新輸入正確的用戶名和密碼,仍然會報錯,原因是loginticket驗證失敗。關於loginticket校驗的,可以從cas的源碼中看到:
AuthenticationViaFormAction: public final Event submit(final RequestContext context, final Credential credential, final MessageContext messageContext) throws Exception { // Validate login ticket final String authoritativeLoginTicket = WebUtils.getLoginTicketFromFlowScope(context); final String providedLoginTicket = WebUtils.getLoginTicketFromRequest(context); if (!authoritativeLoginTicket.equals(providedLoginTicket)) { logger.warn("Invalid login ticket {}", providedLoginTicket); messageContext.addMessage(new MessageBuilder().code("error.invalid.loginticket").build()); return newEvent(ERROR); } ……
是去拿flow流中的lt和請求中的票據lt進行比較的。然而表單中的票據沒有更新成最新的,我的webflow結束之后才返回的錯誤信息,那自然拿不到webflow里面生成的票據了。
先拋開這個問題不談,說第二種整合mvc的方法:
在end-state標簽中加入output,output中輸出一個對像;
第三種:繼承AbstractFlowHandler方法,
public class OcLoginFlowEndHandler extends AbstractFlowHandler { public String handleExecutionOutcome(FlowExecutionOutcome outcome, HttpServletRequest request, HttpServletResponse response) { if (outcome.getId().equals("redirectView")) { return "/user/loginRes?code=" + outcome.getOutput().get("bookingId"); } else { return "/hotels/index"; } } }
然而這都沒有解決我的問題。因為出了那個webflow容器拿到的lt總是不對的。
那怎么在login-webflow這個閉環中返回錯誤信息給前台頁面呢?
查到有一種方式:http://docs.spring.io/spring-webflow/docs/2.3.x/reference/htmlsingle/#end-state-element
使用一個viewstate,來展示popup 對話框:
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
不過這個加了這個viewstate之后也得重新定向到login的viewstate才行。
最后我采用的方案:
修改login的viewstate:
<view-state id="viewLoginForm" view="/platform/login" model="credential">
<binder>
<binding property="username" />
<binding property="password" />
</binder>
<on-entry>
<set name="requestScope.messages" value="messageContext.allMessages" />
<set name="viewScope.commandName" value="'credential'" />
</on-entry>
<transition on="submit" bind="true" validate="true" to="realSubmit">
<evaluate
expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credential)" />
</transition>
</view-state>
添加綠色的那一行,這個view在render到velocity頁面的時候,直接把requestScope里的messages給放進去,前端控制,如果messages為空,就不展示,如果不為空,就展示出來。
<label style="float: left; display: inline-block; font-size: 12px; color: red; text-decoration: none; margin: 5px 0;"> #foreach(${message} in ${messages} ) #if(${message.text} == "Invalid credentials.") 用戶名、密碼或驗證碼錯誤! #end #end </label>
done!
那么看下效果:

再輸入正確的用戶名和密碼就不會報那個loginticket驗證失敗的錯誤並且能成功登錄到系統中去了。
不足的是,其實很明顯的看到頁面刷新了一下。
