添加maven依賴html
<dependency> <groupId>org.owasp.antisamy</groupId> <artifactId>antisamy</artifactId> <version>1.5.8</version> </dependency>
根據antisamy api編寫相關工具類java
import java.io.IOException; import org.owasp.esapi.ESAPI; import org.owasp.validator.html.AntiSamy; import org.owasp.validator.html.CleanResults; import org.owasp.validator.html.Policy; import org.owasp.validator.html.PolicyException; import org.owasp.validator.html.ScanException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; public class XssUtils { private final static Logger logger = LoggerFactory.getLogger(XssUtils.class); private static Policy policy = null; static{ ResourceLoader resourceLoader = new DefaultResourceLoader(); Resource resource = resourceLoader.getResource("classpath:/antisamy.xml"); try { policy = Policy.getInstance(resource.getURL().getPath()); } catch (PolicyException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static String encode(String value){ if(value == null || value.length() == 0) { return value; } value = ESAPI.encoder().canonicalize(value); return ESAPI.encoder().encodeForHTML(value); } public static String[] encode(String[] values){ if(values == null || values.length == 0){ return values; } int len = values.length; String[] _values = new String[len]; for(int i = 0; i < len; i++){ _values[i] = encode(values[i]); } return _values; } public static String clean(String value){ AntiSamy antiSamy = new AntiSamy(); try { final CleanResults cr = antiSamy.scan(value, policy); return cr.getCleanHTML(); } catch (ScanException | PolicyException e) { logger.error("invoke xss clean error", e); } return value; } public static String[] clean(String[] values){ if(values == null || values.length == 0){ return values; } int len = values.length; String[] _values = new String[len]; for(int i = 0; i < len; i++){ _values[i] = clean(values[i]); } return _values; } }
使用filter處理HttpServletRequest,依次編寫如下代碼spring
自定義HttpServletRequestWrapperjson
import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import com.sw.busi.utils.XssUtils; public class XssCleanHttpServletRequestWrapper extends HttpServletRequestWrapper { public XssCleanHttpServletRequestWrapper(HttpServletRequest request) { super(request); } public String getQueryString() { String value = super.getQueryString(); if (value != null) { value = XssUtils.clean(value); } return value; } /** * 覆蓋getParameter方法,將參數名和參數值都作xss過濾。<br/> * 若是須要得到原始的值,則經過super.getParameterValues(name)來獲取<br/> * getParameterNames,getParameterValues和getParameterMap也可能須要覆蓋 */ public String getParameter(String name) { String value = super.getParameter(XssUtils.clean(name)); if (value != null) { value = XssUtils.clean(value); } return value; } public String[] getParameterValues(String name) { String[]parameters=super.getParameterValues(name); if (parameters==null||parameters.length == 0) { return null; } for (int i = 0; i < parameters.length; i++) { parameters[i] = XssUtils.clean(parameters[i]); } return parameters; } public Map<String, String[]> getParameterMap() { Map<String, String[]> params = super.getParameterMap(); if (params==null || params.size() == 0) { return params; } int capacity = (int) ((float) params.size() / 0.75F + 1.0F); Map<String, String[]> _params = new HashMap<String, String[]>(capacity); for (Map.Entry<String, String[]> e : params.entrySet()) { String key = e.getKey(); String[] values = e.getValue(); for (int i = 0; i < values.length; i++) { values[i] = XssUtils.clean(values[i]); } _params.put(key, values); } return _params; } /** * 覆蓋getHeader方法,將參數名和參數值都作xss過濾。<br/> * 若是須要得到原始的值,則經過super.getHeaders(name)來獲取<br/> getHeaderNames 也可能須要覆蓋 */ public String getHeader(String name) { String value = super.getHeader(XssUtils.clean(name)); if (value != null) { value = XssUtils.clean(value); } return value; } }
自定義filterapi
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; public class XssFilter implements Filter { protected FilterConfig filterConfig = null; public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } public void destroy() { this.filterConfig = null; } public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException { chain.doFilter(new XssCleanHttpServletRequestWrapper((HttpServletRequest) request), response); } }
若使用spring mvc,且使用了@RequestBody的方式綁定參數(spring內部封裝了httpservletrequest的裝配器,使用了getInputStream的方式獲取請求體,並轉換爲對應的實體),經過filter的方式就沒法知足了,筆者對spring mvc的序列化與反序列化進行了修改,這裏以jackson爲示例,編寫代碼。mvc
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.sw.busi.json.jackson.databind.deser.StringDeserializer; import com.sw.busi.json.jackson.databind.ser.StringSerializer; public class CustomObjectMapper extends ObjectMapper { private static final long serialVersionUID = -8543006375974774016L; public CustomObjectMapper(){ SimpleModule simpleModule = new SimpleModule(); //序列化與反序列化字符串,使用了xss clean simpleModule.addSerializer(String.class, StringSerializer.instance); simpleModule.addDeserializer(String.class, StringDeserializer.instance); this.registerModule(simpleModule); } }
import java.io.IOException; import java.lang.reflect.Type; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; import com.fasterxml.jackson.databind.ser.std.NonTypedScalarSerializerBase; import com.sw.busi.utils.XssUtils; /** * This is the special serializer for regular {@link java.lang.String}s. *<p> * Since this is one of "native" types, no type information is ever * included on serialization (unlike for most scalar types as of 1.5) */ public final class StringSerializer extends NonTypedScalarSerializerBase<String> { private static final long serialVersionUID = 1L; public static final StringSerializer instance = new StringSerializer(); public StringSerializer() { super(String.class); } /** * For Strings, both null and Empty String qualify for emptiness. */ @Override @Deprecated public boolean isEmpty(String value) { return (value == null) || (value.length() == 0); } @Override public boolean isEmpty(SerializerProvider prov, String value) { return (value == null) || (value.length() == 0); } @Override public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(XssUtils.clean(value)); } @Override public JsonNode getSchema(SerializerProvider provider, Type typeHint) { return createSchemaNode("string", true); } @Override public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException { if (visitor != null) visitor.expectStringFormat(typeHint); } }
import java.io.IOException; import com.fasterxml.jackson.core.Base64Variants; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.sw.busi.utils.XssUtils; //去掉註解,防止自定義反序列化器被認爲是標準實現,從而反序列化String[] collection<String> Map<*, String> 無效 // com.fasterxml.jackson.databind.util.ClassUtil.isJacksonStdImpl(Object) //@JacksonStdImpl public class StringDeserializer extends StdScalarDeserializer<String> { private static final long serialVersionUID = 1L; public final static StringDeserializer instance = new StringDeserializer(); public StringDeserializer() { super(String.class); } // since 2.6, slightly faster lookups for this very common type @Override public boolean isCachable() { return true; } public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { JsonToken curr = jp.getCurrentToken(); if (curr == JsonToken.VALUE_STRING) { return XssUtils.clean(jp.getText()); } // Issue#381 if (curr == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { jp.nextToken(); final String parsed = _parseString(jp, ctxt); if (jp.nextToken() != JsonToken.END_ARRAY) { throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, "Attempted to unwrap single value array for single 'String' value but there was more than a single value in the array"); } return XssUtils.clean(parsed); } // [JACKSON-330]: need to gracefully handle byte[] data, as base64 if (curr == JsonToken.VALUE_EMBEDDED_OBJECT) { Object ob = jp.getEmbeddedObject(); if (ob == null) { return null; } if (ob instanceof byte[]) { return XssUtils.clean(Base64Variants.getDefaultVariant().encode((byte[]) ob, false)); } // otherwise, try conversion using toString()... return XssUtils.clean(ob.toString()); } // allow coercions for other scalar types String text = jp.getValueAsString(); if (text != null) { return XssUtils.clean(text); } throw ctxt.mappingException(_valueClass, curr); } // Since we can never have type info ("natural type"; String, Boolean, // Integer, Double): // (is it an error to even call this version?) @Override public String deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { return deserialize(p, ctxt); } }