Struts2的请求包装器
请求包装器admin 发布于:2011-08-03 12:43:00
阅读:loading
不管是form表单提交的请求,还是超链接的请求,都是请求,都会在数据都通过HttpServletRequest对象来封装传递的,那么请求包装器就是将request对象中的数据根据自己的需要重新包装一下。比如:
­一个保存的动作,里面包含文本框,文本域,用户在文本框中输入 "/></"\' 等这种单引号,双引号之类的特殊字符,在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,那么将<转换为:>肯定会显示不完整,则在修改的时候,会出现数据丢失问题,而且同样会和setA...一样需要用反射处理;如此那就直接来说说通过使用getPa...的两个函数怎么处理吧。
在Struts2中,提交表单请求,会调用getParameterMap()函数,拿到这个函数中的所有name属性,然后再根据name属性获取value,则直接针对的是基本类型的数据值操作了,所以不需要考虑原型为什么,但这种实现也有一种缺陷,比如:数据库长度为10,用户输入hello'a"bc,则后台处理会处理成 hello'a"bc,这样会造成数据库字段长度不够而报错,所以就需要更改数据库的长度,放置由于用户输入的特殊字符报错。
既然各种办法都有缺陷,那么把缺陷和便利降到最低则为首选,可能还有其他的办法,俺不知道罢了。
详细说下用3,4来实现request的包装器。
思路:重写Struts的Filter,再写一个MyStrutsHttpServletRequest类,然后里面重写getParameter函数和getParam..Map函数,再函数中做特殊字符的处理工作。
直接贴出代码吧。
1、配置文件,配置所有的需要过滤的特殊字符,并且包含不需要过滤的表单name属性,为何有些表单不需要过滤呢?有些用到富文本编辑器时,则不需要过滤(却是为何?想)
2、web.xml配置
把原本的过滤器,替换,写成自己的过滤器,前提是基础它这个过滤器。
3、MyStrutsPrepareAndExecuteFilter.java
接下来的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> ,则转义后的字符串为<script>也就是说在后台获取到的数据为转义后的字符串,假设数据库的字段长度为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}即可,这种方式非常简单易容,只不过需要每个地方都需要这么搞有点太多了。
点赞