通過過濾器Filter實現平臺級校驗

涉及的場景

  • session校驗
    比如,部分系統必須保證系統登錄之後才能正常使用,登錄之後會將登錄信息保存在session中,因此可以在Filter實現session數據的校驗
  • 請求攔截
    如果平臺涉及到黑白名單相關的機制,可以使用Filter實現攔截相關請求,並響應異常。
  • 平臺級的數據檢驗
    當平臺的所有接口都需要按一定的協議進行加密或者驗籤,可以通過Filter獲取到請求的數據,校驗是否符合加密規則或者驗籤規則,符合通過,不符合拒絕
  • 削峯限流
    防止突發的併發請求擊垮整個服務,可以考慮在Filter中使用限流機制削減洪峯,保障服務可用性的同時給用戶一個友好的提示。

測試示例

  • 請求數據幫助類
    用戶Post請求時通過Stream獲取請求數據

    import javax.servlet.http.HttpServletRequest;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.nio.charset.Charset;
    
    public class HttpGetBodyInfoHelper {
        public static String getBodyString(HttpServletRequest request) {
            StringBuilder sb = new StringBuilder();
            InputStream inputStream = null;
            BufferedReader reader = null;
            try {
                inputStream = request.getInputStream();
                reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return sb.toString();
        }
    }
    
  • 定一個RequestWrapper包裝器
    由於body中的數據是以I/O流的形式發送給服務端的,因此在服務端讀取的時候,如果不使用包裝器將讀取出來的數據包裝之後傳給下一個處理器,那麼在下一個處理器處理的時候,將無法讀取到數據;因爲前一個處理器處理數據的時候讀取過了ByteBuffer,此時ByteBuffer中的postion和limit指針指向了同一個位置,因此無法再次讀取數據;通過包裝器對象,可以將請求數據傳遞給下一個處理器

    import javax.servlet.ReadListener;
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.BufferedReader;
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.nio.charset.Charset;
    
    public class RequestReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
        private final byte[] body;
        private String bodyStr;
    
        public RequestReaderHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
            //獲取前端傳遞的參數
            this.bodyStr = HttpGetBodyInfoHelper.getBodyString(request);
            //將文本數據轉成數組對象
            body = bodyStr.getBytes(Charset.forName("UTF-8"));
        }
    
        //封裝一個返回文本數據的對象
        public String getBody() {
            return bodyStr;
        }
    
        @Override
        public BufferedReader getReader() {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    
        @Override
        public ServletInputStream getInputStream() {
            //將數據流傳給下一個處理器
            //因爲這裏讀取了數據,流數據讀取一次之後就沒有了,因此這裏以流的形式將數據再次會給下一個處理器
            final ByteArrayInputStream bais = new ByteArrayInputStream(body);
    
            return new ServletInputStream() {
    
                @Override
                public int read() {
                    return bais.read();
                }
    
                @Override
                public boolean isFinished() {
                    return false;
                }
    
                @Override
                public boolean isReady() {
                    return false;
                }
    
                @Override
                public void setReadListener(ReadListener readListener) {
    
                }
            };
        }
    }
    
  • 定義Filter
    以下示例了初始化參數的獲取、session的操作、cookie的操作、ip的獲取、get/post請求數據獲取等,當拿到這些數據之後,我們就可以根據自己業務的需要,對這些數據進行處理

    import org.apache.commons.lang3.StringUtils;
    import org.springframework.http.HttpMethod;
    
    import javax.servlet.*;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    
    public class ServiceCheckFilter implements Filter {
    
        private Map<String, String> initParameters = new HashMap<>();
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            //用於初始化基礎參數使用
            //初始化時候的參數
            Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
            //遍歷鏈表
            while (initParameterNames.hasMoreElements()) {
                //得到鏈表的key
                String key = initParameterNames.nextElement();
                //取到對應的值
                String va = filterConfig.getInitParameter(key);
                //判斷是否是配置不攔截的key value的值
                if (StringUtils.isNotEmpty(va)) {
                    //使用;將值切分成數組
                    initParameters.put(key, va);
                    System.out.println("init方法得到的自定義值,key:" + key + " va:" + va);
                }
            }
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            if (request instanceof HttpServletRequest) {
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    
                //獲取到session
                HttpSession httpSession = httpServletRequest.getSession();
                //設置session的值
                httpSession.getAttribute("sessionKey");
                //設置session的值
                httpSession.setAttribute("sessionKey", "sessionValue");
                //刪除session中對應key的值
                httpSession.removeAttribute("sessionKey");
                //設置session超時時間
                httpSession.setMaxInactiveInterval(60);
    
                Cookie[] cookies = httpServletRequest.getCookies();
                if (null != cookies) {
                    for (Cookie cookie : cookies) {
                        //操作cookie數據
                    }
                }
    
                System.out.println("getRequestURI()-->" + httpServletRequest.getRequestURI());
                System.out.println("getRequestURL().toString()-->" + httpServletRequest.getRequestURL().toString());
                System.out.println("getQueryString()-->" + httpServletRequest.getQueryString());
                System.out.println("getRemoteAddr()-->" + httpServletRequest.getRemoteAddr());
                System.out.println("getRemoteHost()-->" + httpServletRequest.getRemoteHost());
                System.out.println("getRemotePort()-->" + httpServletRequest.getRemotePort());
    
    
                //獲取請求方式
                String method = httpServletRequest.getMethod();
                //轉爲HttpMethod枚舉
                HttpMethod httpMethod = HttpMethod.valueOf(method);
    
                //定義請求包裝器,便於將數據傳遞給下一個處理器
                RequestReaderHttpServletRequestWrapper requestWrapper = null;
                switch (httpMethod) {
                    case GET:
                        //當前請求是get方式
                        Enumeration<String> parameterNames = httpServletRequest.getParameterNames();
                        while (parameterNames.hasMoreElements()) {
                            String key = parameterNames.nextElement();
                            String va = httpServletRequest.getParameter(key);
                            System.out.println("get請求獲取數據Key:" + key + " va:" + va);
                        }
    
                        //可以根據自己的需要進行業務校驗,如果不符合業務要求,可以根據個人的情況返回錯誤
                        //if(1==1) {
                        //    interceptMsg(response, "{'code':-1,'msg':'出現錯誤!'}");
                        //    return;
                        //}
    
                        break;
                    case POST:
                        //當前請求是post
                        //這裏需要實例化一個requestWrapper包裝器,將數據包裝起來,便於傳遞給下一個Filter
                        //記住,這裏務必要使用一個包裝器,因爲Post需要通過Stream去獲取數據,緩存區的數據只能夠讀取一遍,
                        // 如果不使用包裝器包裝的話,在這裏獲取一遍之後,下一個Filter在處理的時候就沒辦法在request中read到數據了
                        requestWrapper = new RequestReaderHttpServletRequestWrapper(httpServletRequest);
                        //得到請求數據
                        String body = requestWrapper.getBody();
    
                        System.out.println("post請求獲取數據:" + body);
    
                        //得到請求數據進行校驗
                        break;
                }
    
                if (null == requestWrapper) {
                    chain.doFilter(request, response);
                } else {
                    chain.doFilter(requestWrapper, response);
                }
            }
        }
    
        /**
         * 返回數據
         *
         * @param response
         * @param json
         */
        private void interceptMsg(ServletResponse response, String json) {
            PrintWriter writer = null;
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            try {
                writer = response.getWriter();
                writer.print(json);
            } catch (IOException e) {
                System.out.println("寫數據出現異常");
            } finally {
                if (writer != null)
                    writer.close();
            }
        }
    
        @Override
        public void destroy() {
            //過濾器銷燬的時候 執行相關收尾工作
    
            //清楚所有的數據
            initParameters.clear();
        }
    }
    
  • 註冊Filter
    以下是SpringBoot的方式

        @Bean
        public FilterRegistrationBean signCheckFilterRegistration() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            //設置過濾器
            registration.setFilter(new ServiceCheckFilter());
            //匹配攔截的Url
            registration.addUrlPatterns("/*");
            //這個可以根據自己的業務場景初始化數據,在Filter對象初始化的時候會調用init(config)方法將數據傳遞過去
            registration.addInitParameter("initData", "initVa");
            //設置別名
            registration.setName("signCheckFilter");
            //設置註冊的順序,因爲可能存在多個Filter
            registration.setOrder(1);
            return registration;
        }
    
  • 獲取用戶的真是IP
    上面雖然列舉了獲取IP的方式,但是如果用戶使用了代理之後,獲取的就是代理的IP,可以通過以下方式獲取到用戶的正式IP

    import javax.servlet.http.HttpServletRequest;
    
    public class ClientIPUtil {
        /**
         * 獲取用戶真實IP地址,不使用request.getRemoteAddr();的原因是有可能用戶使用了代理軟件方式避免真實IP地址,
         * <p>
         * 可是,如果通過了多級反向代理的話,X-Forwarded-For的值並不止一個,而是一串IP值,究竟哪個纔是真正的用戶端的真實IP呢?
         * 答案是取X-Forwarded-For中第一個非unknown的有效IP字符串。
         * <p>
         * 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
         * 192.168.1.100
         * <p>
         * 用戶真實IP爲: 192.168.1.110
         *
         * @param request
         * @return
         */
        public static String getIpAddress(HttpServletRequest request) {
            String ip = request.getHeader("x-forwarded-for");
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
            return ip;
        }
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章