Browse Source

刷新token

yuhuitao 2 years ago
parent
commit
ae50246f06

+ 8 - 1
modules/core/pom.xml

@@ -74,13 +74,20 @@
 			<artifactId>jeesite-common</artifactId>
 			<version>${project.parent.version}</version>
 		</dependency>
-
+			<!--敏感词-->
+			<dependency>
+				<groupId>com.github.houbb</groupId>
+				<artifactId>sensitive-word</artifactId>
+				<version>0.1.0</version>
+			</dependency>
 		<!-- Framework -->
 		<dependency>
 			<groupId>com.jeesite</groupId>
 			<artifactId>jeesite-framework</artifactId>
 			<version>${project.parent.version}</version>
 		</dependency>
+
+
             <!--<dependency>
                 <groupId>com.jeesite</groupId>
                 <artifactId>jeesite-module-sso</artifactId>

+ 79 - 0
modules/core/src/main/java/com/jeesite/filter/SenFilter.java

@@ -0,0 +1,79 @@
+package com.jeesite.filter;
+
+import com.alibaba.fastjson.JSON;
+import com.jeesite.common.config.Global;
+import com.jeesite.common.web.http.ServletUtils;
+import org.springframework.web.bind.annotation.RequestMethod;
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+ 
+/**
+ * @description ${ 敏感字过滤器}
+ * @Version: 1.0
+ */
+public class SenFilter implements Filter {
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+ 
+    }
+ 
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
+        boolean flag = true;
+        SensitiveFilter filter = SensitiveFilter.DEFAULT;
+        if (httpServletRequest.getMethod().equals(RequestMethod.GET.name())) {   // GET请求
+            //获取前端传递的所有参数名的枚举
+            Enumeration pNames = httpServletRequest.getParameterNames();
+            //遍历枚举
+            while(pNames.hasMoreElements()){
+                //获取参数名
+                String name=(String)pNames.nextElement();
+                //获取参数值
+                String value =httpServletRequest.getParameter(name);
+                //对参数值进行敏感词处理,并重新设置到request
+                String str = filter.filter(value, '*');
+                if(value != str){
+                    servletResponse.setContentType("application/json; charset=UTF-8");
+                    PrintWriter out = servletResponse.getWriter();
+                    out.println(JSON.toJSONString(ServletUtils.renderResult(Global.FALSE,"您输入的信息有敏感信息")));
+                    out.flush();
+                    flag = false;
+                    break;
+                }
+                httpServletRequest.setAttribute(name,str);
+            }
+            if(flag){
+                filterChain.doFilter(servletRequest, servletResponse);
+            }
+        } else if (httpServletRequest.getMethod().equals(RequestMethod.POST.name())) {  // POST请求:
+        SenRequestWrapper senRequestWrapper = new SenRequestWrapper(httpServletRequest);
+        if(senRequestWrapper == null) {
+            filterChain.doFilter(servletRequest, servletResponse);
+        } else {
+            String body = senRequestWrapper.getBody();
+            String filted = filter.filter(body, '*');
+            if (body != filted) {
+                servletResponse.setContentType("application/json; charset=UTF-8");
+                PrintWriter out = servletResponse.getWriter();
+                out.println(JSON.toJSONString(ServletUtils.renderResult(Global.FALSE,"您输入的信息有敏感信息")));
+                out.flush();
+            } else {
+                filterChain.doFilter(senRequestWrapper, servletResponse);
+            }
+        }
+    } else {
+        filterChain.doFilter(servletRequest, servletResponse);
+    }
+ 
+ 
+    }
+ 
+    @Override
+    public void destroy() {
+ 
+    }
+}

+ 67 - 0
modules/core/src/main/java/com/jeesite/filter/SenRequestWrapper.java

@@ -0,0 +1,67 @@
+package com.jeesite.filter;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.*;
+ 
+public class SenRequestWrapper extends HttpServletRequestWrapper {
+    private String body;
+    public SenRequestWrapper(HttpServletRequest request) throws IOException {
+        super(request);
+        StringBuilder stringBuilder = new StringBuilder();
+        BufferedReader bufferedReader = null;
+        try {
+            InputStream inputStream = request.getInputStream();
+            if (inputStream != null) {
+                bufferedReader = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
+                char[] charBuffer = new char[128];
+                int bytesRead = -1;
+                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
+                    stringBuilder.append(charBuffer, 0, bytesRead);
+                }
+            } else {
+                stringBuilder.append("");
+            }
+        } catch (IOException ex) {
+            throw ex;
+        } finally {
+            if (bufferedReader != null) {
+                try {
+                    bufferedReader.close();
+                } catch (IOException ex) {
+                    throw ex;
+                }
+            }
+        }
+        body = stringBuilder.toString();
+    }
+ 
+    @Override
+    public ServletInputStream getInputStream() throws IOException {
+        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
+        ServletInputStream servletInputStream = new ServletInputStream() {
+            public boolean isFinished() {
+                return false;
+            }
+            public boolean isReady() {
+                return false;
+            }
+            public void setReadListener(ReadListener readListener) {}
+            public int read() throws IOException {
+                return byteArrayInputStream.read();
+            }
+        };
+        return servletInputStream;
+ 
+    }
+ 
+    @Override
+    public BufferedReader getReader() throws IOException {
+        return new BufferedReader(new InputStreamReader(this.getInputStream(),"UTF-8"));
+    }
+    public String getBody() {
+        return this.body;
+    }
+}

+ 224 - 0
modules/core/src/main/java/com/jeesite/filter/SensitiveFilter.java

@@ -0,0 +1,224 @@
+package com.jeesite.filter;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.util.Iterator;
+import java.util.NavigableSet;
+ 
+/**
+ * 敏感词过滤器,以过滤速度优化为主。<br/>
+ * * 增加一个敏感词:{@link #put(String)} <br/>
+ * * 过滤一个句子:{@link #filter(String, char)} <br/>
+ * * 获取默认的单例:{@link #DEFAULT}
+ *
+ */
+public class SensitiveFilter implements Serializable{
+   
+   private static final long serialVersionUID = 1L;
+ 
+   /**
+    * 默认的单例,使用自带的敏感词库
+    */
+   public static final SensitiveFilter DEFAULT = new SensitiveFilter(
+         new BufferedReader(new InputStreamReader(
+               ClassLoader.getSystemResourceAsStream("sensi_words.txt")
+               , StandardCharsets.UTF_8)));
+   
+   /**
+    * 为2的n次方,考虑到敏感词大概在10k左右,
+    * 这个数量应为词数的数倍,使得桶很稀疏
+    * 提高不命中时hash指向null的概率,
+    * 加快访问速度。
+    */
+   static final int DEFAULT_INITIAL_CAPACITY = 131072;
+   
+   /**
+    * 类似HashMap的桶,比较稀疏。
+    * 使用2个字符的hash定位。
+    */
+   protected SensitiveNode[] nodes = new SensitiveNode[DEFAULT_INITIAL_CAPACITY];
+   
+   /**
+    * 构建一个空的filter
+    * 
+    * @author ZhangXiaoye
+    * @date 2017年1月5日 下午4:18:07
+    */
+   public SensitiveFilter(){
+      
+   }
+   
+   /**
+    * 加载一个文件中的词典,并构建filter<br/>
+    * 文件中,每行一个敏感词条<br/>
+    * <b>注意:</b>读取完成后会调用{@link BufferedReader#close()}方法。<br/>
+    * <b>注意:</b>读取中的{@link IOException}不会抛出
+    * 
+    * @param reader
+    */
+   public SensitiveFilter(BufferedReader reader){
+      try{
+         for(String line = reader.readLine(); line != null; line = reader.readLine()){
+            put(line);
+         }
+         reader.close();
+      }catch(IOException e){
+         e.printStackTrace();
+      }
+   }
+   
+   /**
+    * 增加一个敏感词,如果词的长度(trim后)小于2,则丢弃<br/>
+    * 此方法(构建)并不是主要的性能优化点。
+    * 
+    * @param word
+    */
+   public boolean put(String word){
+      // 长度小于2的不加入
+      if(word == null || word.trim().length() < 2){
+         return false;
+      }
+      // 两个字符的不考虑
+      if(word.length() == 2 && word.matches("\\w\\w")){
+         return false;
+      }
+      StringPointer sp = new StringPointer(word.trim());
+      // 计算头两个字符的hash
+      int hash = sp.nextTwoCharHash(0);
+      // 计算头两个字符的mix表示(mix相同,两个字符相同)
+      int mix = sp.nextTwoCharMix(0);
+      // 转为在hash桶中的位置
+      int index = hash & (nodes.length - 1);
+      
+      // 从桶里拿第一个节点
+      SensitiveNode node = nodes[index];
+      if(node == null){
+         // 如果没有节点,则放进去一个
+         node = new SensitiveNode(mix);
+         // 并添加词
+         node.words.add(sp);
+         // 放入桶里
+         nodes[index] = node;
+      }else{
+         // 如果已经有节点(1个或多个),找到正确的节点
+         for(;node != null; node = node.next){
+            // 匹配节点
+            if(node.headTwoCharMix == mix){
+               node.words.add(sp);
+               return true;
+            }
+            // 如果匹配到最后仍然不成功,则追加一个节点
+            if(node.next == null){
+               new SensitiveNode(mix, node).words.add(sp);
+               return true;
+            }
+         }
+      }
+      return true;
+   }
+   
+   /**
+    * 对句子进行敏感词过滤<br/>
+    * 如果无敏感词返回输入的sentence对象,即可以用下面的方式判断是否有敏感词:<br/><code>
+    * String result = filter.filter(sentence, '*');<br/>
+    * if(result != sentence){<br/>
+    * &nbsp;&nbsp;// 有敏感词<br/>
+    * }
+    * </code>
+    * 
+    * @param sentence 句子
+    * @param replace 敏感词的替换字符
+    */
+   public String filter(String sentence, char replace){
+      // 先转换为StringPointer
+      StringPointer sp = new StringPointer(sentence);
+      
+      // 标示是否替换
+      boolean replaced = false;
+      
+      // 匹配的起始位置
+      int i = 0;
+      while(i < sp.length - 1){
+         /*
+          * 移动到下一个匹配位置的步进:
+          * 如果未匹配为1,如果匹配是匹配的词长度
+          */
+         int step = 1;
+         // 计算此位置开始2个字符的hash
+         int hash = sp.nextTwoCharHash(i);
+         /*
+          * 根据hash获取第一个节点,
+          * 真正匹配的节点可能不是第一个,
+          * 所以有后面的for循环。
+          */
+         SensitiveNode node = nodes[hash & (nodes.length - 1)];
+         /*
+          * 如果非敏感词,node基本为null。
+          * 这一步大幅提升效率 
+          */
+         if(node != null){
+            /*
+             * 如果能拿到第一个节点,
+             * 才计算mix(mix相同表示2个字符相同)。
+             * mix的意义和HashMap先hash再equals的equals部分类似。
+             */
+            int mix = sp.nextTwoCharMix(i);
+            /*
+             * 循环所有的节点,如果非敏感词,
+             * mix相同的概率非常低,提高效率
+             */
+            outer:
+            for(; node != null; node = node.next){
+               /*
+                * 对于一个节点,先根据头2个字符判断是否属于这个节点。
+                * 如果属于这个节点,看这个节点的词库是否命中。
+                * 此代码块中访问次数已经很少,不是优化重点
+                */
+               if(node.headTwoCharMix == mix){
+                  /*
+                   * 查出比剩余sentence小的最大的词。
+                   * 例如剩余sentence为"色情电影哪家强?",
+                   * 这个节点含三个词从小到大为:"色情"、"色情电影"、"色情信息"。
+                   * 则从“色情电影”开始向前匹配
+                   */
+                  NavigableSet<StringPointer> desSet = node.words.headSet(sp.substring(i), true);
+                  if(desSet != null){
+                     for(StringPointer word: desSet.descendingSet()){
+                        /*
+                         * 仍然需要再判断一次,例如"色情信息哪里有?",
+                         * 如果节点只包含"色情电影"一个词,
+                         * 仍然能够取到word为"色情电影",但是不该匹配。
+                         */
+                        if(sp.nextStartsWith(i, word)){
+                           // 匹配成功,将匹配的部分,用replace制定的内容替代
+                           sp.fill(i, i + word.length, replace);
+                           // 跳过已经替代的部分
+                           step = word.length;
+                           // 标示有替换
+                           replaced = true;
+                           // 跳出循环(然后是while循环的下一个位置)
+                           break outer;
+                        }
+                     }
+                  }
+                  
+               }
+            }
+         }
+         
+         // 移动到下一个匹配位置
+         i += step;
+      }
+      
+      // 如果没有替换,直接返回入参(节约String的构造copy)
+      if(replaced){
+         return sp.toString();
+      }else{
+         return sentence;
+      }
+   }
+ 
+}

+ 38 - 0
modules/core/src/main/java/com/jeesite/filter/SensitiveNode.java

@@ -0,0 +1,38 @@
+package com.jeesite.filter;
+
+import java.io.Serializable;
+import java.util.TreeSet;
+ 
+/**
+ * 敏感词节点,每个节点包含了以相同的2个字符开头的所有词
+ *
+ */
+public class SensitiveNode implements Serializable{
+   
+   private static final long serialVersionUID = 1L;
+ 
+   /**
+    * 头两个字符的mix,mix相同,两个字符相同
+    */
+   protected final int headTwoCharMix;
+   
+   /**
+    * 所有以这两个字符开头的词表
+    */
+   protected final TreeSet<StringPointer> words = new TreeSet<StringPointer>();
+   
+   /**
+    * 下一个节点
+    */
+   protected SensitiveNode next;
+   
+   public SensitiveNode(int headTwoCharMix){
+      this.headTwoCharMix = headTwoCharMix;
+   }
+   
+   public SensitiveNode(int headTwoCharMix, SensitiveNode parent){
+      this.headTwoCharMix = headTwoCharMix;
+      parent.next = this;
+   }
+ 
+}

+ 163 - 0
modules/core/src/main/java/com/jeesite/filter/StringPointer.java

@@ -0,0 +1,163 @@
+package com.jeesite.filter;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.TreeMap;
+ 
+/**
+ * 没有注释的方法与{@link String}类似<br/>
+ *  * <b>注意:</b>没有(数组越界等的)安全检查<br/>
+ *  * 可以作为{@link HashMap}和{@link TreeMap}的key
+ */
+public class StringPointer implements Serializable, CharSequence, Comparable<StringPointer>{
+   
+   private static final long serialVersionUID = 1L;
+ 
+   protected final char[] value;
+   
+   protected final int offset;
+   
+   protected final int length;
+   
+   private int hash = 0;
+   
+   public StringPointer(String str){
+      value = str.toCharArray();
+      offset = 0;
+      length = value.length;
+   }
+   
+   public StringPointer(char[] value, int offset, int length){
+      this.value = value;
+      this.offset = offset;
+      this.length = length;
+   }
+   
+   /**
+    * 计算该位置后(包含)2个字符的hash值
+    * 
+    * @param i 从 0 到 length - 2
+    * @return hash值
+    */
+   public int nextTwoCharHash(int i){
+      return 31 * value[offset + i] + value[offset + i + 1];
+   }
+   
+   /**
+    * 计算该位置后(包含)2个字符和为1个int型的值<br/>
+    * int值相同表示2个字符相同
+    * 
+    * @param i 从 0 到 length - 2
+    * @return int值
+    */
+   public int nextTwoCharMix(int i){
+      return (value[offset + i] << 16) | value[offset + i + 1];
+   }
+   
+   /**
+    * 该位置后(包含)的字符串,是否以某个词(word)开头
+    * 
+    * @param i 从 0 到 length - 2
+    * @param word 词
+    * @return 是否?
+    */
+   public boolean nextStartsWith(int i, StringPointer word){
+      // 是否长度超出
+      if(word.length > length - i){
+         return false;
+      }
+      // 从尾开始判断
+      for(int c =  word.length - 1; c >= 0; c --){
+         if(value[offset + i + c] != word.value[word.offset + c]){
+            return false;
+         }
+      }
+      return true;
+   }
+   
+   /**
+    * 填充(替换)
+    * 
+    * @param begin 从此位置开始(含)
+    * @param end 到此位置结束(不含)
+    * @param fillWith 以此字符填充(替换)
+    */
+   public void fill(int begin, int end, char fillWith){
+      for(int i = begin; i < end; i ++){
+         value[offset + i] = fillWith;
+      }
+   }
+   
+   public int length(){
+      return length;
+   }
+   
+   public char charAt(int i){
+      return value[offset + i];
+   }
+   
+   public StringPointer substring(int begin){
+      return new StringPointer(value, offset + begin, length - begin);
+   }
+   
+   public StringPointer substring(int begin, int end){
+      return new StringPointer(value, offset + begin, end - begin);
+   }
+   @Override
+   public CharSequence subSequence(int start, int end) {
+      return substring(start, end);
+   }
+   
+   public String toString(){
+      return new String(value, offset, length);
+   }
+   
+   public int hashCode() {
+      int h = hash;
+      if (h == 0 && length > 0) {
+         for (int i = 0; i < length; i++) {
+            h = 31 * h + value[offset + i];
+         }
+         hash = h;
+      }
+      return h;
+   }
+   
+   public boolean equals(Object anObject) {
+        if (this == anObject) {
+            return true;
+        }
+        if (anObject instanceof StringPointer) {
+           StringPointer that = (StringPointer)anObject;
+            if (length == that.length) {
+                char v1[] = this.value;
+                char v2[] = that.value;
+                for(int i = 0; i < this.length; i ++){
+                   if(v1[this.offset + i] != v2[that.offset + i]){
+                      return false;
+                   }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+   @Override
+   public int compareTo(StringPointer that) {
+      int len1 = this.length;
+        int len2 = that.length;
+        int lim = Math.min(len1, len2);
+        char v1[] = this.value;
+        char v2[] = that.value;
+        int k = 0;
+        while (k < lim) {
+            char c1 = v1[this.offset + k];
+            char c2 = v2[that.offset + k];
+            if (c1 != c2) {
+                return c1 - c2;
+            }
+            k++;
+        }
+        return len1 - len2;
+   }
+}

+ 2 - 0
modules/core/src/main/resources/sensi_words.txt

@@ -0,0 +1,2 @@
+傻瓜
+傻逼

+ 24 - 2
web/src/main/java/com/jeesite/modules/Application.java

@@ -4,10 +4,16 @@
  */
 package com.jeesite.modules;
 
+import com.beust.jcommander.internal.Maps;
+import com.jeesite.filter.SenFilter;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.Bean;
+
+import java.util.Map;
 
 /**
  * Application
@@ -25,7 +31,23 @@ public class Application extends SpringBootServletInitializer {
 	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
 		this.setRegisterErrorPageFilter(false); // 错误页面有容器来处理,而不是SpringBoot
 		System.out.println();
-	return builder.sources(Application.class);
+		return builder.sources(Application.class);
 	}
 
-}
+
+	@Bean
+	public FilterRegistrationBean senFilterRegistration() {
+		FilterRegistrationBean registration = new FilterRegistrationBean();
+		registration.setFilter(new SenFilter());
+		registration.addUrlPatterns("/*");
+		registration.setName("senFilter");
+		registration.setOrder(2);
+		Map<String, String> initParameters = Maps.newHashMap();
+		//-excludes用于配置不需要参数过滤的请求url;
+		//-isIncludeRichText默认为true,主要用于设置富文本内容是否需要过滤。
+		initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
+		initParameters.put("isIncludeRichText", "true");
+		registration.setInitParameters(initParameters);
+		return registration;
+	}
+}