struts2, 注销账号后, 通过地址栏访问action仍然可以登录的问题(通过浏览器输入地址,绕过验证的问题)

不多废话, 先来问题.

问题: 用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">下面这一行, 将其改为多实例即可.

 

 

  后记: 因为最开始配置administrator这个bean的时候, 我并没有配任何初值, 我在思考, 会不会是因为我在前一次登录成功的时候, 那个bean的属性没有被spring清空, 所以spring给action分配bean的时候, 才用的是之前的那个bean中的属性. 

  如果我给administrator这个bean赋个初值的话, 那么spring第二次将这个bean分配给action的时候, 会将其属性值重新初始化一下呢?

     于是, 我把spring的配置改成下面这样:

<bean id="administrator" class="pojo.Administrator">   //仍然是单实例
        <property name="username" value="0">  //赋个初值给bean
        </property>
        <property name="password" value="0"> //赋个初值给bean
        </property>
    </bean>

 

  经验证, 仍然会出现上面的问题. 

  由此也验证了一个结论, spring分配bean的时候, 就仅仅是在最初初始化的时候初始化一下bean的属性, 如果同一个bean第二次分配给某一个action的时候, 它不会去重新初始化这个bean的内部属性!!!!!

 

posted on 2016-12-01 15:50  夏前  阅读(1158)  评论(2编辑  收藏  举报