web過濾器中獲取請求的參數(content-type:multipart/form-data)防止csrf攻擊

1.前言:

  1.1 在使用springMVC中,需要在過濾器中獲取請求中的參數token,根據token判斷請求是否合法;

  1.2 通過requst.getParameter(key)方法獲得參數值;

    這種方法有缺陷:它只能獲取  POST 提交方式中的Content-Type: application/x-www-form-urlencoded;

        HttpServletRequest request= (HttpServletRequest) req;
        String param = request.getParameter("param");

 

    

2.問題:

  在一般的請求中,content-type爲:application/x-www-form-urlencoded;在此種請求中,使用request.getParam(key)方法可以獲取到key對應的屬性值;

  因爲最近涉及到文件的上傳操作,上傳文件的請求中content-type爲:multipart/form-data;此種請求無法直接用request.getParam(key)獲取對應的屬性值,request中獲取的屬性值全部爲空,無法正常獲取;

 

3.問題描述:

  3.1 在web.xml中配置的過濾器

複製代碼
  <filter>
    <filter-name>requestFilter</filter-name>
    <filter-class>util.web.RequestFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>requestFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
複製代碼

 

  

  3.2  過濾器RequestFilter.java中獲取token做匹配

    在如下過濾器中,上傳文件中的content-type:multipart/form-data使用獲取request.getParameter(key)無法獲取相應的值。需要藉助Spring框架中的CommonsMultipartResolver.resolveMultipart(HttpServletRequest request)將request轉爲MultipartHttpServletRequest,從而使用getParameter(key)方法獲取指定的值;

    在將對象轉化完成後,要將轉化完成的對象賦值給過濾鏈中的request參數中,即如下代碼中的  req = multiReq; 賦值完成很重要,否則在controller層中依舊無法獲取其他參數。

 

    如果不需要再filter中獲取請求中的值,則無需如下的操作,在請求經過springMVC框架後,自動會識別請求方式,如果是文件請求,會自動調用CommonsMultipartResolver.resolveMultipart(HttpServletRequest request)方法轉化;

複製代碼
package util.web;public class RequestFilter implements Filter {
    protected FilterConfig filterConfig;
    protected boolean filterEnabled;
    protected int logLevel;
    protected boolean needVCode;
    protected List<String> noFilerList =null;
    private CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    public RequestFilter() {
        this.filterConfig = null;
        this.filterEnabled = true;
        this.logLevel = -1;
    }
        
    @Override
    public void destroy() {
        // TODO Auto-generated method stub
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        if(this.filterEnabled){
            HttpServletRequest httpReq = (HttpServletRequest)req;
            HttpServletResponse httpResp = (HttpServletResponse)resp;
            String ctxPath = httpReq.getContextPath(); 
            String requestUri = httpReq.getRequestURI();        //請求的全路徑,比如:         
            String uri = requestUri.substring(ctxPath.length());//全路徑除去ctxPath
            String tarUri = uri.trim();    
            String operatorHtmlModel = (httpReq.getHeader("referer")!=null?httpReq.getHeader("referer"):"").trim(); //獲取當前頁面的url,判斷url是否是後臺而url(後臺的html就兩個)
            //不在過濾列表裏的url請求,過濾列表包括t_sys_filter表中數據及visitor角色用戶下的授權頁面
            if(!this.isInNoFilerList(tarUri)){
                UserInfo uInfo = SessionUtil.getCurrentUser();
                LoginAccount regAccout=SessionUtil.getCurrentPlateLoginAccount();
                int type = 0 ;//平臺賬號未登錄
                if(operatorHtmlModel.endsWith(Const.LOGIN_TYPE_HTML[0])||operatorHtmlModel.endsWith(Const.LOGIN_TYPE_HTML[1])){
                     type = 1;//後臺賬號未登錄
                }
            //    int type = (SessionUtil.getAttribute(Const.LOGIN_TYPE)!=null)?Integer.valueOf(SessionUtil.getAttribute(Const.LOGIN_TYPE).toString()):0;
            if(regAccout==null){//平臺賬號未登錄
                if(tarUri.endsWith(".do")){
                    httpResp.sendRedirect(ctxPath+"/"+Const.TIMEOUT_SERVICE);
                    return;                                
                }else if(tarUri.endsWith("/")){
                    httpResp.sendRedirect(ctxPath+"/"+Const.INDEX_PAGE);
                    return;
                }
            }else{//平臺賬號登錄
                if(tarUri.endsWith("/")){
                    httpResp.sendRedirect(ctxPath+"/error/noSecurity.htm");
                    return;
                }else if(tarUri.endsWith(".do") && !isWithoutUri(tarUri)){
                    String contentType = httpReq.getContentType();//獲取請求的content-type
                    String post_csrftoken = "";
                    if(contentType.contains("multipart/form-data")){//文件上傳請求 *特殊請求
              /*
                
CommonsMultipartResolver 是spring框架中自帶的類,使用multipartResolver.resolveMultipart(final HttpServletRequest request)方法可以將request轉化爲MultipartHttpServletRequest
                使用MultipartHttpServletRequest對象可以使用getParameter(key)獲取對應屬性的值
              */
                        MultipartHttpServletRequest multiReq = multipartResolver.resolveMultipart(httpReq);
                        post_csrftoken=multiReq.getParameter(Const.SESSION_CSRFTOKEN);//獲取參數中的token
                        req = multiReq;//將轉化後的reuqest賦值到過濾鏈中的參數 *重要
                    }else{//非文件上傳請求
                        post_csrftoken=httpReq.getParameter(Const.SESSION_CSRFTOKEN);//獲取參數中的token
                    }
                    //csrf防禦:判斷是否帶token
                    //post_csrftoken=httpReq.getParameter(Const.SESSION_CSRFTOKEN);
                    String csrftoken=(String)SessionUtil.getAttribute(Const.SESSION_CSRFTOKEN);
                    if(post_csrftoken==null || !csrftoken.equals(post_csrftoken)){
                        //判斷爲不安全的訪問
                        httpResp.sendRedirect(ctxPath+"/common/goNoSecurity.do");
                        return;
                    }
                }
            }
            // 設定網頁的到期時間,一旦過期則必須到服務器上重新調用
            httpResp.setDateHeader("Expires", -1);
            // Cache-Control 指定請求和響應應遵循的緩存機制 no-cache指示請求或響應消息是不能緩存的
            httpResp.setHeader("Cache-Control", "no-cache");
            // 用於設定禁止瀏覽器從本地緩存中調用頁面內容,設定後一旦離開頁面就無法從Cache中再調出
            httpResp.setHeader("Pragma", "no-cache");
            }        
        chain.doFilter(req, resp);
        }
    }

    
    @SuppressWarnings("unchecked")
    @Override
    public void init(FilterConfig cfg) throws ServletException {
        //初始化操作
    }

    
    private Boolean isWithoutUri(String tarUri){
        String[] withoutUriStrings = {//無需匹配token的請求
                "/common/goNoSecurity.do",
                "/plateFormCommon/isLoginForPlateForm.do",
                "/supplierForPlateForm/getCompanyListByRegId.do"
                /*,"/PfTaskFileCtrl/addOrUpdateTaskImgFile1.do"*/
                /*,"/PfTaskFileCtrl/addOrUpdateTaskImgFileForUpdate.do"*/
                };
        
        for(String uri:withoutUriStrings){
            if(uri.equals(tarUri)){
                return true;
            }
        }
        return false;
    }
    
}
複製代碼

 

 

  3.3 控制層 PfTaskFileCtrl.java

  如果在filter中未將轉化的request值賦值給過濾鏈,則在這裏無法獲取fileType對應的值;

複製代碼
package platform.common.controller;/**    
 * mogodb文件上傳下載
 * 項目名稱:outsideeasy    
 * 類名稱:PfTaskFileCtrl    
 * 創建人:mishengliang    
 * 創建時間:2016-4-26 下午1:55:19    
 * 修改人:mishengliang    
 * 修改時間:2016-4-26 下午1:55:19    
 * @version     
 *     
 */
@Controller
@RequestMapping("PfTaskFileCtrl")
public class PfTaskFileCtrl {

    @Autowired
    private PfRegisterAttchedService registerAttchedService;
    @Autowired
    private FileOptService fileService;
    @Autowired
    private PfUpdateRegisterAttchedService updateRegisterAttchedService;
    @Autowired
    private CompanyForPlateFormService companyForPlateFormService;
    
    @RequestMapping(value = { "/companyImageView" }, method = { RequestMethod.GET })
    public ModelAndView gojsp_companyImageView(ModelAndView modelAndView ){
        modelAndView.setViewName("/companyWindow/companyImageView");
        return modelAndView;
    }
    
    /**
     * @Description:企業文件上傳
     * PfTaskFileCtrl
     * addOrUpdateTaskImgFile1
     * @param request
     * @param response
     * @return
     * @throws Exception String
     * @author yukai
     * 2016-8-4 上午10:03:00
     */
    @DocLogger(explain="企業文件上傳")
    @RequestMapping(value="addOrUpdateTaskImgFile1",method=RequestMethod.POST)
    @ResponseBody 
    public String addOrUpdateTaskImgFile1(HttpServletRequest request,HttpServletResponse response) throws Exception{
        Map<String,Object> qryParam = WebUtil.getDefaultParamsMap(request);
        LoginAccount regAccount = SessionUtil.getCurrentPlateLoginAccount();
        Map<String,Object> params=new HashMap<String, Object>();
        JSONObject json = new JSONObject();
        json.put("success", true);
        /*
         * 1.檢查參數
         */
        if(regAccount == null){//獲取任務id
            json.put("message", "未登錄");
            return json.toString() ;
        }
     // *如果在filter中未將轉化的request值賦值給過濾鏈,則在這裏無法獲取fileType對應的值
if(WebUtil.isEmpty(request.getParameter("fileType"))){//獲取任務id json.put("message", "沒有文件類型值"); return json.toString() ; } /* *2.賦值 */ int fileType = Integer.parseInt(request.getParameter("fileType")); String fileName = request.getParameter("fileName"); String formatType = request.getParameter("formatType"); PfRegisterAttched pfRegisterAttched = new PfRegisterAttched(); pfRegisterAttched.setFile_type_id(fileType);//文件類型值 pfRegisterAttched.setFile_name(fileName);//文件名 if(fileName.indexOf(",") != -1){ json.put("message", "文件名中存在非法字符(英文逗號),請先去除後上傳"); return json.toString() ; } /* * 3.對文件信息的處理 */ MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; if(WebUtil.isEmpty((CommonsMultipartFile) multipartRequest.getFile("file"))){ json.put("message", "沒有文件"); return json.toString() ; } CommonsMultipartFile file = (CommonsMultipartFile) multipartRequest.getFile("file"); //對應前臺文件對象 if(file!=null && file.getSize()>0){//檢查文件大小和格式 if (file.getSize() >5*1024*1024) { json.put("message", "文件太大,超過5M"); return json.toString() ; } String originalName=file.getOriginalFilename(); String this_suffix = ""; params.put(Const.ISIMG, 0); params.put(Const.USE_TYPE, fileType); params.put(Const.USERNAME, regAccount.getLogin_name()); params.put(Const.COM_ID, qryParam.get("companyId")); params.put(Const.COM_NAME,companyForPlateFormService.getCompanyNameByCompanyId(qryParam)); boolean flag=false;//默認不 是圖片 //獲取文件後綴,與傳過來的參數file_name重新組裝文件名 if(originalName.indexOf(".")>0){//有後綴 XX.jpg XX.RAR this_suffix=originalName.substring(originalName.lastIndexOf(".")); String[] format = null; if("image".equals(formatType)){//判斷上傳文件的類型:image 圖片,text 文檔 format = Const.imgArray; params.put(Const.ISIMG, 1); }else if("text".equals(formatType)){ format = Const.otherArray; } for(String suffix:format){ if(suffix.equalsIgnoreCase(this_suffix)){ flag=true; break; } } } if(!flag){ json.put("message", "不是指定格式"); return json.toString() ; }else{ /* * 4.進行信息的處理 */ Date date = new Date(); SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String mongodbId=fileService.SaveFile(file,params); pfRegisterAttched.setMogodb_id(mongodbId);//把存儲mongoDb的文件序號存到數據庫中 pfRegisterAttched.setCreate_dt(date); pfRegisterAttched.setFile_format(this_suffix); Integer id = registerAttchedService.addAppRegisterAttched(pfRegisterAttched);//獲取存入信息的id json.put("fileId",id); json.put("mongodbId", mongodbId); json.put("creatDate", sf.format(date)); json.put("message", "上傳成功"); } }else{ json.put("message", "文件不存在"); } return json.toString(); } }
複製代碼

 

 

4.解決方案

  在問題描述中已有問題解決方案。

  此方法主要利用了Spring框架中已有的工具類。

  參考資料:http://www.itdadao.com/articles/c15a279110p0.html

 

 

5.總結

  5.1 不同的content-type請求獲取參數值的方法不同。

  5.2 在multipart/form-data請求方式中,需要利用SpringMVC框架中的CommonsMultipartResolver類包裝轉化爲MultipartHttpServletRequest獲取參數值;

 

 

6. 參考學習

  1. servlet3.0 Tomcat7.0 簡潔方案

    如果使用的是servlet3.0及以上版本,multipart/form-data請求方式取值可以使用 HttpServletRequest.getPart(key)方法獲取指定值;

    參考資料:http://stackoverflow.com/questions/2422468/how-to-upload-files-to-server-using-jsp-servlet/2424824#2424824

 

 

 

  2.通常的三種請求方式獲取值方法

    * application/x-www-form-urlencoded

    *application/json

    * text/xml 

 

  可以使用將請求轉化爲流,再轉爲字符串獲取相應的值,通過如下方法獲取的字符串獲取指定的參數值;

  轉化方法如下:

複製代碼
    /** 
     * 獲取請求Body 
     * 
     * @param request 
     * @return 
     */  
    public static String getBodyString(ServletRequest 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();  
    }  
複製代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章