不多廢話, 先來問題.
問題: 用struts2結合spring做了一個用戶登錄的, 然後點擊之後可以注銷的例子.
登錄頁面很簡單, 大概就是像如下這個樣子
登錄的action和注銷的action在strut.xml中為如下配置:
<action name="checkUser" class="checkUserAction" method="execute"> //這裡的class="checkUserAction"是獲取的spring配置文件中的bean
<result name="login">/WEB-INF/jsp/login/login.jsp</result> //這裡是登錄失敗的話, 會重新返回到登錄頁面
<result name="success">/WEB-INF/jsp/list/home.jsp</result> //這裡是登錄成功的話, 會進入到歡迎界面
</action>
<action name="quitUser" class="checkUserAction" method="quitUser"> //注銷的action,注銷成功返回登錄頁面
<result name="success">/WEB-INF/jsp/login/login.jsp </result>
</action>
spring的配置文件如下:
<bean id="administrator" class="pojo.Administrator"> </bean> <bean id="checkUserAction" class="action.checkUserAction" scope="prototype"> <property name="adminer"> <ref bean="administrator"/> </property> <property name="adminUserService"> <ref bean="adminUserServiceImpl"/> </property> </bean>
checkUserAction的源代碼如下:
public class checkUserAction extends ActionSupport implements ModelDriven<Administrator>{ Administrator adminer; AdminUserService adminUserService; @Override public String execute() throws Exception { String result = adminUserService.findAdministrator(adminer); //這裡的一行, 是我在將表單提交過來的用戶名密碼和數據庫中的做對比, 如果成功就將這個放入session, 失敗的話就做相應的提示, if(!result.equals("success")) { if(result.equals(AdminUserService.NOT_EXIST)) { ServletActionContext.getRequest().setAttribute("error", "用戶名不存在"); } else if(result.equals(AdminUserService.WRONG_PASSWORD)) { ServletActionContext.getRequest().setAttribute("error", "密碼錯誤"); } return "login"; } ActionContext.getContext().getSession().put("adminUser", adminer); return result; }
/*下述為注銷用戶時使用的方法, 將session清空*/ public String quitUser() throws Exception { ActionContext.getContext().getSession().clear(); return "success"; } ......get和set方法略, 並且模型驅動的部分也略 }
就是這麼一個簡單的程序, 登錄沒問題, 退出也可以正常退出. 但是這裡我卻發現了一個嚴重的問題,
就是我在點擊退出之後, 通過在地址欄直接輸入action的地址:"http://localhost:8080/xxx/checkUser" 來繞過通過點擊"登錄"按鈕, 直接訪問的時候, 居然可以正常登錄到歡迎界面!!!
這個問題困擾了我很久, 在網上也查了很多方法:
1. 有人說需要建立攔截器, 通過在攔截器中屏蔽不合法的登錄用戶可以做到, 後來我嘗試了不行.
2. 還有的說要禁止動態訪問, 可我發現我的頁面本身就是默認禁止動態訪問的action中的method的. (只不過人家是直接訪問的action的名字, 不是訪問的action中的method).
3. 我又查, 看struct2能夠直接屏蔽在地址欄直接輸入action的名字來進行訪問的方法, 似乎也米有這種方法, 也就說地址欄是肯定可以直接輸入action的名字來進行訪問的!
繼續分析, 我在checkUserAction 中, 加了打印, 直接將用來獲取表單數據的adminer對象(也就是存放用戶名密碼)裡面的數據都打了出來, 居然打出來的值也都是正確的. 然後我就很納悶了, 我明明已經退出登錄了, 通過strut2的標簽<s:debug/>檢查, session裡面確實已經沒有任何東西了, 但是通過地址欄訪問checkUser這個action的時候, 居然那個adminer對象裡面還有值! 而我又不是通過點擊表單的登錄按鈕, 而是通過直接在地址欄訪問action來訪問的頁面, 那麼這個正確的數據到底是誰提交的呢? 然後, 我又把浏覽器的抓包工具打開, 發現我在直接訪問action的時候, 確實裡面也沒有附帶任何的表單(用戶名密碼)信息, 那這個值到底是誰給裝進去的呢?
我然後, 又懷疑, 是不是模型驅動干了什麼壞事? 是不是模型驅動在我退出的時候, 把用戶名密碼給裝填了進去, 然後發回給了浏覽器, 但是後來看了下模型驅動的源碼, 模型驅動僅僅在action執行之前的時候, 會把獲取到的表單數據, 通過一個模型壓入到值棧中, action執行完之後, 並沒有做任何事情. 而且我還做了這樣的操作, 我在quitUser()的函數中, 在返回"success"之前, 把值棧裡面所有的內容也都給清空了, 然而, 問題依舊!!!
最後最後, 我才忽然發現了一個問題, 既然通過地址欄直接訪問action地址, 是無法提交表單數據的, 同時session中的數據也清空了, 那只能說明一個問題, 就是這個 adminer對象裡面的值肯定是服務器給賦進去的! 於是自然而然的想到了肯定是spring的問題. 原因: 因為spring在產生action對象時, 應該是多實例的, 就是每有一個請求, 就會產生一個action對象, 這是沒問題的. 但問題是, 我在配置checkUserAction的時候, 沒有把它的屬性adminer對象也給賦值成多實例, 導致了在下一次的action中, spring返回給action使用的adminer對象, 仍然是之前成功登錄過一次的那個adminer對象, 這個對象裡面仍然保存著上一次成功登錄時的用戶名密碼信息, 因此, 直接訪問地址欄的時候, 它直接就用了之前的這個對象去進行驗證了, 那當然是可以通過的了. 這就是問題的根本所在.
其實經驗證, 我通過地址欄訪問checkUser這個Action, 登錄後的頁面裡面, 打開<s:debug/>開關, 能夠看到每一次訪問之後, session中存的那個adminer對象的值都沒變化過, 就能夠說明這個問題.
所以, 只需將spring配置文件裡面<bean id="administrator" class="pojo.Administrator" scope="prototype">下面這一行, 將其改為多實例即可.