Struts2的请求包装器

请求包装器
placeholder image
admin 发布于:2011-08-03 12:43:00
阅读:loading

不管是form表单提交的请求,还是超链接的请求,都是请求,都会在数据都通过HttpServletRequest对象来封装传递的,那么请求包装器就是将request对象中的数据根据自己的需要重新包装一下。比如:

&shy;一个保存的动作,里面包含文本框,文本域,用户在文本框中输入  "/></"\'  等这种单引号,双引号之类的特殊字符,在JSP页面中肯定是这样写的:

<input type="text" name="user.userName" value="${user.userName}" />,如果这个保存的动作不做特殊处理,假设用户名为:ABCD  "/></"\'   ,则显示在页面上的

<input type="text" name="user.userName" value="ABCD  "/></"\' " />,用户名里面的 " 与 value=" "的双引号冲突,页面上显示肯定会出现问题,至于出现什么问题,你可以在你的程序里面输入这种类似的字符自己测试。如果在表单中输入<script type="text/javascript">while(true){alert('弹出');}</script>   这些字符,那肯定会哭的。所以做好这个事情还是很重要的。

 

现在说说有哪些办法能够解决这种冲突问题:

1、直接从表单处控制:即表单中不允许输入特殊字符,只能输入0-9 a-z A-Z和中文汉字,这样就避免了上述情况。

这种解决办法肯定是有缺陷的,不允许用户输入,这应该算是功能上的缺陷。有些请求,通过url注入,参数给你加上几个 特殊字符,那么表单不能输入的就被攻破了,所以这种办法并不高明,而且一个项目中会有多少的表单和不同的业务,这点小事儿单独处理太繁琐了,而且也没什么价值,时间长了,也很不爽的吧应该。

2、通过Entity对象的set属性方法设置:把每个javabean(Entity)对象的set属性函数中,添加上处理特殊字符的功能,这样也可以避免。但是这种做法和上面有个共同的缺陷,项目中,Entity对象肯定多的很的很,每个都加上这种东西......此处省略300字。

3、页面表单的value值使用jstl的<c:out />输出:此标签中有可以设置是否转换特殊字符的参数,会自动将value属性的值给转义。
4、请求包装器:
将request对象中的参数值按照一定的规则或配置进行重新处理,则在页面或者显示的时候不需要再做其他相关处理,甚至这活都被人做好了,你压根感觉不到有这种问题存在。我建议使用这种办法,至于怎么使用接下来就主要描述一下。

具体实现:

1、从request对象的getParameter(name)函数入手。

2、从request对象的getParameterMap()函数入手。

3、从getAttribute(name)函数入手。

4、从setAttribute(name,obj)函数入手。

这几个函数时干嘛用的,都该知道,而且像Struts中基本用到的都是 setAttribute(name,value)函数,如果包装他的话,需要name将value拿出来,然后value是Object类型的,所以还得判断这个value到底是什么类型的,然后强转该类型,找出这个类型的相关属性进行过滤替换等操作,当然这是反射完成的,不需要一个一个的写;如果包装 getAttibute(name)的话,此函数用户显示,如果用户输入的值为:ABCDE"<>/SCRIPT等字符,页面的表单text的maxlength属性设置的为10,那么将<转换为:&gt;肯定会显示不完整,则在修改的时候,会出现数据丢失问题,而且同样会和setA...一样需要用反射处理;如此那就直接来说说通过使用getPa...的两个函数怎么处理吧。

在Struts2中,提交表单请求,会调用getParameterMap()函数,拿到这个函数中的所有name属性,然后再根据name属性获取value,则直接针对的是基本类型的数据值操作了,所以不需要考虑原型为什么,但这种实现也有一种缺陷,比如:数据库长度为10,用户输入hello'a"bc,则后台处理会处理成 hello&apos;a&quot;bc,这样会造成数据库字段长度不够而报错,所以就需要更改数据库的长度,放置由于用户输入的特殊字符报错。

既然各种办法都有缺陷,那么把缺陷和便利降到最低则为首选,可能还有其他的办法,俺不知道罢了。

详细说下用3,4来实现request的包装器。

 

思路:重写Struts的Filter,再写一个MyStrutsHttpServletRequest类,然后里面重写getParameter函数和getParam..Map函数,再函数中做特殊字符的处理工作。

直接贴出代码吧。

1、配置文件,配置所有的需要过滤的特殊字符,并且包含不需要过滤的表单name属性,为何有些表单不需要过滤呢?有些用到富文本编辑器时,则不需要过滤(却是为何?想)

image.png

2、web.xml配置

image.png

把原本的过滤器,替换,写成自己的过滤器,前提是基础它这个过滤器。

3、MyStrutsPrepareAndExecuteFilter.java

image.png

接下来的MyRequestWrapper类就是自己的request类,这个类继承自StrutsRequestWrapper类,其他的就不说了。
补充一下:

以上这个不仅仅是过滤html的特殊字符,还能过滤脏话,比如,我KAO,CAO等之类的,只需要在xml配置一下。


 

再补充一下(2012年8月12日):

Struts2对文件上传请求做了封装,运行字符文本type="text"和二进制文件type="file"一块提交,如果遇到form的enctype="mu/...."的情况,Strutls2会走自己的文件拦截器。

再补充一下(9月20日)

还需要将request的getParameterValues()函数重新包装下,比如有些复选框需要用这个。
 

再补充一下(2013年06月09日)
        上面的请求包装器重新getParameter()和getParameterValues()其实会有个很直接的问题,就是,假设用户输入了<script> ,则转义后的字符串为&lt;script&gt;也就是说在后台获取到的数据为转义后的字符串,假设数据库的字段长度为8,页面上的<input />的maxlength属性也为8,此时存储数据库时,会出现字段长度超长的问题,故此种方式需要将数据库的长度与实际用户输入的长度还要大,最大为4倍,显然这种方式的弊端很严重。此时我考虑重写getAttibute()函数,也就是说数据库中存储的为用户输入的原型,只不过在读取出来的时候做一个html特殊字符的转义,最重要的是重写此函数了,它能支持${xx}和<%=xx%>的数据获取方式。下面给出相关的核心代码:
重写Struts的request类的getAttribute      

/**

 * 请求包装器,重写getParameter(Values)的时候,将所有的(表单)请求中的特殊字符替换

 * @author chendd<br/>

 * 这里不能用HttpServletRequestWrapper,由于是Struts的包装器,所以需要继承StrutsRequestWrapper

 */

public class SystemRequestParamsWrapper extends StrutsRequestWrapper {

 

   private SystemRequestParamsWrapperUtils portalRequestWrapperUtils = SystemRequestParamsWrapperUtils.getInstance();

  

   /**

    * 隐示的构造函数,必须有

    * @param request

    */

   publicSystemRequestParamsWrapper(HttpServletRequest request){

      super(request);

   }

  

   @Override

   public ObjectgetAttribute(String name) {

      Objectvalue = super.getAttribute(name);

      return getFilterValue(value);

   }

 

   /**

    * 包装request.getAttribute函数,只处理获取到的值为String类型的参数或者为某个对象的String类型的属性

    * @param value

    * @return

    */

   private ObjectgetFilterValue(Object value) {

      if(value != null){

         if(value instanceof String){

            returnwrapperString((String)value);

         }else{

            Class<?>clazz = value.getClass();

            if(clazz.isAnnotationPresent(SystemParamFilterAnnotation.class)){

                try {

                   SystemParamFilterAnnotationsystemParamFilter = clazz.getAnnotation(SystemParamFilterAnnotation.class);

                   this.wrapper(systemParamFilter,value);

                }catch(Exception e) {

                   e.printStackTrace();

                }

            }

         }

      }

      return value;

   }

 

   /**

    * 包装对象中的属性

    * @param systemParamFilter

    * @param systemParamFilterBean

    * @throws Exception

    */

   protected void wrapper(SystemParamFilterAnnotationsystemParamFilter , Object systemParamFilterBean) throws Exception{

     

      try {

         PropertyDescriptor[]props = PropertyUtils

                .getPropertyDescriptors(systemParamFilterBean);

         Stringexcludes[] = systemParamFilter.excludeProps();

         for (PropertyDescriptorprop : props) {

            StringpropName = prop.getName();

            //排除基类的class属性

            if ("class".equals(propName)){

                continue;

            }

            //判断当前的属性是否在需要排除的属性中

            boolean exist = false;//定义临时标记,当前属性是否在排除的属性之外

            for (String exclude :excludes) {

                if(exclude.equals(propName)) {

                   exist= true;

                   break;

                }

            }

            if (exist == true){

                continue;

            }

            Methodmethod = prop.getReadMethod();

            ObjectpropValue = method.invoke(systemParamFilterBean, new Object[] {});

            if(propValue != null){

                if (propValue instanceof String) {

                   Stringvalue = propValue.toString();

                   StringnewValue = this.wrapperString(value);

                   prop.getWriteMethod().invoke(systemParamFilterBean, newObject[] {newValue });

                }

                //这里不必考虑对象中的对象属性也需要过滤的问题,如果需要考虑则将注释打开,递归调用即可

                /*

                elseif(propValue.getClass().isAnnotationPresent(SystemParamFilterAnnotation.class)){

                   SystemParamFilterAnnotationnewParamFilter =propValue.getClass().getAnnotation(SystemParamFilterAnnotation.class);

                   this.wrapper(newParamFilter,propValue);

                }

                */

            }

         }

      }catch(Exception e) {

         throw new Exception(e);

      }

   }

  

   /**

    * 返回包装后的value

    * @param value

    * @return

    */

   private StringwrapperString(String value){

     

      Set<Entry<String,String>> codeSet = portalRequestWrapperUtils.getCodeMaps().entrySet();

      for (Entry<String,String> entry : codeSet) {

         Stringcode = entry.getKey();

         Stringreplace = entry.getValue();

         value= value.replace(code,replace);

      }

      return value;

   }

  

   public static void main(String[] args) {

     

      Set<Entry<String,String>> codeSet = SystemRequestParamsWrapperUtils.getInstance().getCodeMaps().entrySet();

      Stringvalue = "dfsadfsadf<??fdadfasdfasdafsfda:::'\"\"f'dadfas''\"\"fda'''fdadfsadfasdafs>safdsafsda";

      for (Entry<String,String> entry : codeSet) {

         Stringcode = entry.getKey();

         Stringreplace = entry.getValue();

         value= value.replaceAll(code,replace);

      }

      System.out.println(value);

   }

}
注解类

 

/**

 * 定义一个系统参数过滤的注解

 * <font color='red'>

 * <br/>在类的申明上面指定@SystemParamFilterAnnotation注解,默认是过滤全部的属性方法,可以通过excludeProps属性来设置不需要过滤的属性集合

 * </font>

 * @author chendd

 *

 */

@Target(value= {ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface SystemParamFilterAnnotation {

   /**

    * 设置需要排除的属性集

    * @return

    */

   public String[] excludeProps() default{};

}

javabean引用注解类
                                                                                             

/**

 * 普通的javabean对象

 * @author chendd

 * excludeProps指的是从此类中所有的属性中排除的属性“name”,本例中过滤的只有address

 */

@SystemParamFilterAnnotation(excludeProps = {"name"})

public classUser{

   private Integer id;

   private String name;

   private String address;

   public User(){

   }

   public User(Integer id,Stringname){

      this.id = id;

      this.name = name;

   }

   public Integer getId() {

      return id;

   }

   public void setId(Integer id) {

      this.id = id;

   }

   public String getName() {

      return name;

   }

   public void setName(String name) {

      this.name = name;

   }

   public String getAddress() {

      return address;

   }

   public void setAddress(Stringaddress) {

      this.address = address;

   }

}

任意一个jsp页面调用

<%

User user = new User();

user.setId(1001);

user.setName("chen'dd");

user.setAddress("'<address>'");

request.setAttribute("user", user);

%>

<h1>

   ${user.name}------${user.address}

</h1>

页面上输出的为:chen'dd------'<address>' 主:name属性中设置的特殊字符为不过滤

其他:
如果想解决此种问题,用<c:out />输出${xx}即可,这种方式非常简单易容,只不过需要每个地方都需要这么搞有点太多了。


上述许多内容已经过时和过期了,留存本篇文章仅为方便个人查看,原始文章的信息参考:

原始链接:hhttps://www.chendd.cn/information/viewInformation/other/73.a

最后更新:2011-08-03 12:43:00

访问次数:111

评论次数:0

点赞个数:0

 点赞


 发表评论

当前回复:作者

 评论列表


留言区