【问题标题】:Resolving multipart/form-data request in spring filter解决弹簧过滤器中的多部分/表单数据请求
【发布时间】:2016-05-25 09:14:22
【问题描述】:

我正在尝试在 Spring MVC 3 中开发自己的 CSRF filter(有一些额外的培训让我这样做了,这就是我不考虑 Spring 安全性的原因。)

我的过滤器适用于除enctype="multipart/form-data" 之外的所有表单。所以我无法从普通的 HttpServletRequest 中获取请求参数。

我尝试将HttpServletRequest 转换为MultipartHttpServletRequest,但我发现我也做不到。

我的目标不是从请求中获取文件,而是只获取名为csrf 的简单表单输入。 (我已经用我的表单上传了文件)

这是我到目前为止的代码:

CSR过滤器

public class CSRFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        CSRF csrf = new CSRF(req);
        if(csrf.isOk()){
            chain.doFilter(req, res);
        }else {
            //todo : Show Error Page
            String redirect = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/access-forbidden";
            response.sendRedirect(redirect);
        }

    }
}

CSRF

public class CSRF {
    HttpServletRequest request;
    ServletRequest req;
    String token;
    boolean ok;
    private static final Logger logger = Logger.getLogger(CSRF.class);


    public CSRF(ServletRequest request) {
        this.request = (HttpServletRequest) request;
        this.req = request;
        init();
    }

    public CSRF() {
    }


    public void setRequest(HttpServletRequest request) {
        this.request = (HttpServletRequest) request;
        this.req = request;
        init();
    }

    private void init() {
        if (request.getMethod().equals("GET")) {
            generateToken();
            addCSRFTokenToSession();
            addCSRFTokenToModelAttribute();
            ok = true;
        } else if (request.getMethod().equals("POST")) {
            if (checkPostedCsrfToken()) {
                ok = true;
            }
        }
    }

    private void generateToken() {
        String token;
        java.util.Date date = new java.util.Date();
        UUID uuid = UUID.randomUUID();
        token = uuid.toString() + String.valueOf(new Timestamp(date.getTime()));
        try {
            this.token = sha1(token);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            this.token = token;
        }
    }

    private void addCSRFTokenToSession() {
        request.getSession().setAttribute("csrf", token);
    }

    private void addCSRFTokenToModelAttribute() {
        request.setAttribute("csrf", token);
    }

    private boolean checkPostedCsrfToken() {
        System.out.println("____ CSRF CHECK POST _____");
        if (request.getParameterMap().containsKey("csrf")) {
            String csrf = request.getParameter("csrf");
            if (csrf.equals(request.getSession().getAttribute("csrf"))) {
                return true;
            }
        }else {
            //Check for multipart requests

            MultipartHttpServletRequest multiPartRequest = new DefaultMultipartHttpServletRequest((HttpServletRequest) req);
            if (multiPartRequest.getParameterMap().containsKey("csrf")) {
                String csrf = multiPartRequest.getParameter("csrf");
                if (csrf.equals(request.getSession().getAttribute("csrf"))) {
                    return true;
                }
            }
        }

        log();
        return false;
    }

    private void log() {
        HttpSession session = request.getSession();
        String username = (String) session.getAttribute("username");
        if(username==null){
            username = "unknown (not logged in)";
        }
        String ipAddress = request.getHeader("X-FORWARDED-FOR");
        if (ipAddress == null) {
            ipAddress = request.getRemoteAddr();
        }
        String userAgent = request.getHeader("User-Agent");
        String address = request.getRequestURI();
        System.out.println("a CSRF attack detected from IP: " + ipAddress + " in address \"" + address + "\" - Client User Agent : " + userAgent + " Username: " + username);

        logger.error("a CSRF attack detected from IP: " + ipAddress + " in address \"" + address + "\" - Client User Agent : " + userAgent + " Username: " + username);
    }

    public boolean isOk() {
        return ok;
    }

    static String sha1(String input) throws NoSuchAlgorithmException {
        MessageDigest mDigest = MessageDigest.getInstance("SHA1");
        byte[] result = mDigest.digest(input.getBytes());
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < result.length; i++) {
            sb.append(Integer.toString((result[i] & 0xff) + 0x100, 16).substring(1));
        }
        return sb.toString();
    }
}

我的调度员也有这行:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- one of the properties available; the maximum file size in bytes -->
        <property name="maxUploadSize" value="40000000"/>
    </bean>

我也使用 springMultipartResolver 过滤器 ...

<filter>
        <display-name>springMultipartFilter</display-name>
        <filter-name>springMultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springMultipartFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</filter>

当我在 multipart/form-data 表单上尝试时,我得到 java.lang.IllegalStateException: Multipart request not initialized 异常。

我在互联网上查看了许多示例。他们中的大多数是为了文件上传而无法帮助我,我还尝试了不同的方法将 HttpServletRequest 转换为任何其他可以解决多部分请求的对象,但我无法成功。

我该怎么做?

谢谢。

【问题讨论】:

    标签: java spring forms spring-mvc servlets


    【解决方案1】:

    您不能将HttpServletRequest 转换为MultipartHttpServletRequest,因为您首先必须解决您的请求。

    我使用CommonsMultipartResolver 类并使用commonsMultipartResolver.resolveMultipart(request) 方法获得MultipartHttpServletRequest(其中请求是HttpServletRequest 的类型)

    所以,这是我的 CSRF 类,checkPostedCsrfToken() 方法:

    private boolean checkPostedCsrfToken() {
            if (request.getParameterMap().containsKey("csrf")) {
                String csrf = request.getParameter("csrf");
                if (csrf.equals(request.getSession().getAttribute("csrf"))) {
                    return true;
                }
            } else if (request.getContentType() != null && request.getContentType().toLowerCase().contains("multipart/form-data")) {
                CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
                MultipartHttpServletRequest multipartRequest = commonsMultipartResolver.resolveMultipart(request);
                if (multipartRequest.getParameterMap().containsKey("csrf")) {
                    String csrf = multipartRequest.getParameter("csrf");
                    if (csrf.equals(request.getSession().getAttribute("csrf"))) {
                        return true;
                    }
                }
            }
    
            log();
            return false;
        }
    

    但是,请注意,使用这种方法您最终会丢失所有请求参数和数据。因此,您必须扩展 HttpServletRequestWrapper 类来读取请求字节并使用它们来获取参数,如果您认为参数不会丢失抛出过滤器链对您很重要。换句话说,您需要克隆您的请求。

    这是我在 StackOverflow 中找到的一个很好的帮助类,(我找不到问题了,如果找到我会编辑它)。

    MultiReadHttpServletRequest

    public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
        private ByteArrayOutputStream cachedBytes;
    
        public MultiReadHttpServletRequest(HttpServletRequest request) {
            super(request);
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException {
            if (cachedBytes == null)
                cacheInputStream();
    
            return new CachedServletInputStream();
        }
    
        @Override
        public BufferedReader getReader() throws IOException{
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    
        private void cacheInputStream() throws IOException {
        /* Cache the inputstream in order to read it multiple times. For
         * convenience, I use apache.commons IOUtils
         */
            cachedBytes = new ByteArrayOutputStream();
            IOUtils.copy(super.getInputStream(), cachedBytes);
        }
    
        /* An inputstream which reads the cached request body */
        public class CachedServletInputStream extends ServletInputStream {
            private ByteArrayInputStream input;
    
            public CachedServletInputStream() {
          /* create a new input stream from the cached request body */
                input = new ByteArrayInputStream(cachedBytes.toByteArray());
            }
    
            @Override
            public int read() throws IOException {
                return input.read();
            }
        }
    }
    

    现在您需要做的就是在过滤器中使用MultiReadHttpServletRequest 而不是普通的HttpServletRequest

    public class CSRFilter extends GenericFilterBean {
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
            // The important part!! wrap the request:
            MultiReadHttpServletRequest multiReadHttpServletRequest = new MultiReadHttpServletRequest(request);
            CSRF csrf = new CSRF(multiReadHttpServletRequest);
            if(csrf.isOk()){
                chain.doFilter(multiReadHttpServletRequest, res);
            }else {
                //todo : Show Error Page
                String redirect = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/access-forbidden";
                response.sendRedirect(redirect);
            }
        }
    }
    

    我希望这对某人有帮助:)

    【讨论】:

      【解决方案2】:

      我需要能够在不损坏 Servlet 或后续过滤器的情况下检查请求的主体,因此我创建了一个小型项目来完成此任务。

      jar 小于 10kb,如果你使用的是 Tomcat,那么你不需要任何东西。此外,它是 MIT 许可的,因此您可以在您可能需要的任何项目中使用它。

      您可以在https://github.com/isapir/servlet-filter-utils找到该项目

      您所要做的就是用RereadableServletRequest 包装传入的请求,例如

      HttpServletRequest requestWrapper = new RereadableServletRequest(servletRequest);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-05-13
        • 1970-01-01
        • 2021-03-05
        • 1970-01-01
        • 1970-01-01
        • 2017-09-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多