• tomcat跨域请求过滤器CorsFilter使用的预检preFlight及其他过滤器


    前言

      之前我很肤浅的以为为了实现某种请求过滤功能(比如图片转换、文件上传、安全认证等),都需要自己去实现javax.servlet.Filter。之后在web.xml中配置即可。

      但事实上,Tomcat已经提供了部分相关的过滤器(本文只介绍常用的7个过滤器),只需要简单配置就可以使用。最近通过系统学习Tomcat架构之后,结合部分源码记录总结最常用的几种过滤器。

      参考资料《Tomcat架构解析》(有需要PFD电子书的朋友可以评论或者私信),Tomcat官方Filter配置(Tomcat 8为例)


    一、跨域过滤器CorsFilter

    一. 为什么要发预检请求

    我们都知道浏览器的同源策略,就是出于安全考虑,浏览器会限制从脚本发起的跨域HTTP请求,像XMLHttpRequest和Fetch都遵循同源策略。
    浏览器限制跨域请求一般有两种方式:

    1. 浏览器限制发起跨域请求
    2. 跨域请求可以正常发起,但是返回的结果被浏览器拦截了

    一般浏览器都是第二种方式限制跨域请求,那就是说请求已到达服务器,并有可能对数据库里的数据进行了操作,但是返回的结果被浏览器拦截了,那么我们就获取不到返回结果,这是一次失败的请求,但是可能对数据库里的数据产生了影响。

    为了防止这种情况的发生,规范要求,对这种可能对服务器数据产生副作用的HTTP请求方法,浏览器必须先使用OPTIONS方法发起一个预检请求,从而获知服务器是否允许该跨域请求:如果允许,就发送带数据的真实请求;如果不允许,则阻止发送带数据的真实请求。

    二. 什么时候发预检请求

    HTTP请求包括: 简单请求 和 需预检的请求

    1. 简单请求

    简单请求不会触发CORS预检请求,“简属于
    单请求”术语并不属于Fetch(其中定义了CORS)规范。
    若满足所有下述条件,则该请求可视为“简单请求”:

    • 使用下列方法之一:
      • GET
      • HEAD
      • POST
        • Content-Type: (仅当POST方法的Content-Type值等于下列之一才算做简单需求)
          • text/plain
          • multipart/form-data
          • application/x-www-form-urlencoded

    注意: WebKit Nightly 和 Safari Technology Preview 为Accept
    , Accept-Language
    , 和 Content-Language
    首部字段的值添加了额外的限制。如果这些首部字段的值是“非标准”的,WebKit/Safari 就不会将这些请求视为“简单请求”。WebKit/Safari 并没有在文档中列出哪些值是“非标准”的,不过我们可以在这里找到相关讨论:Require preflight for non-standard CORS-safelisted request headers Accept, Accept-Language, and Content-Language, Allow commas in Accept, Accept-Language, and Content-Language request headers for simple CORS, and Switch to a blacklist model for restricted Accept headers in simple CORS requests。其它浏览器并不支持这些额外的限制,因为它们不属于规范的一部分。

    2.需预检的请求

    “需预检的请求”要求必须首先使用OPTIONS方法发起一个预检请求到服务区,以获知服务器是否允许该实际请求。“预检请求”的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

    当请求满足下述任一条件时,即应首先发送预检请求:

    • 使用了下面任一 HTTP 方法:
      • PUT
      • DELETE
      • CONNECT
      • OPTIONS
      • TRACE
      • PATCH
    • 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
      • Accept
      • Accept-Language
      • Content-Language
      • Content-Type
      • DPR
      • Downlink
      • Save-Data
      • Viewport-Width
      • Width
      • Content-Type的值不属于下列之一:
        • application/x-www-form-urlencoded
        • multipart/form-data
        • text/plain

    如下是一个需要执行预检请求的HTTP请求:

    var invocation = new XMLHttpRequest();
    var url = 'http://bar.other/resources/post-here/';
    var body = '<?xml version="1.0"?><person><name>Arun</name></person>';
        
    function callOtherDomain(){
      if(invocation)
        {
          invocation.open('POST', url, true);
          invocation.setRequestHeader('X-PRODUCT', 'H5');
          invocation.setRequestHeader('Content-Type', 'application/xml');
          invocation.onreadystatechange = handler;
          invocation.send(body); 
        }
    }
    
    ......
    

    上面的代码使用POST请求发送一个XML文档,该请求包含了一个自定义的首部字段(X-PRODUCT:H5)。另外,该请求的Content-Typeapplication/xml。因此,该请求需要首先发起“预检请求”。

    如果是一个简单请求,那就直接发起请求,只需在请求中加入Origin字段表明自己来源,在响应中检查
    Access-Control-Allow-Origin,如果不符合要求就报错,不需要再单独询问了。服务器响应字段中还有一个Access-Control-Max-Age,它表明了这个询问结果的有效期,后面浏览器在有效期内也可以不必再次询问。

    在OPTIONS请求里新增了几个字段:

    • Origin:发起请求原来的域
    • Access-Control-Request-Method:将要发起的跨域请求方式(GET/PUT/POST/DELETE/······)
    • Access-Control-Request-Headers:将要发起的跨域请求中包含的请求头字段

    服务器在响应字段中来表明是否允许这个跨域请求,浏览器收到后检查如果不符合要求,就拒绝后面的请求

    • Access-Control-Allow-Origin:允许哪些域来访问(*表示允许所有域的请求)
    • Access-Control-Allow-Methods:允许哪些请求方式
    • Access-Control-Allow-Headers:允许哪些请求头字段
    • Access-Control-Allow-Credentials:是否允许携带Cookie

    而org.apcache.catalina.filters.CorsFilter是跨域资源共享规范的一个实现,常常用于前后端分离,静态资源与后端分离等情况。它主要在HttpServletResponse中增加Access-Control-*头,同时保护HTTP响应避免拆分,如果请求无效或者禁止访问,则返回403响应码。

    配置示例

    复制代码
    <filter>
      <filter-name>CorsFilter</filter-name>
      <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
      <init-param>
        <param-name>cors.allowed.origins</param-name>
        <param-value>*</param-value>
      </init-param>
      <init-param>
        <param-name>cors.allowed.methods</param-name>
        <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
      </init-param>
      <init-param>
        <param-name>cors.allowed.headers</param-name>
        <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
      </init-param>
      <init-param>
        <param-name>cors.exposed.headers</param-name>
        <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
      </init-param>
      <init-param>
        <param-name>cors.support.credentials</param-name>
        <param-value>true</param-value>
      </init-param>
      <init-param>
        <param-name>cors.preflight.maxage</param-name>
        <param-value>10</param-value>
      </init-param>
    </filter>
    <filter-mapping>
      <filter-name>CorsFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    复制代码

    参数说明

      1、cors.allowed.origins

        允许访问的跨域资源列表,"*"表示允许访问来自任何域的资源,多个域用逗号分隔,默认为"*"

      2、cors.allowed.methods

        可以用于访问资源的HTTP方法列表,","分隔,用于跨域请求。这些方法将出现在Prefligh(预检请求)响应头Access-Control-Allow-Methods的一部分,t默认为"GET, POST, HEAD, OPTIONS"

      3、cors.allowed.headers

        构造请求时可以使用的请求头,以","分隔,这些方法将出现在Prefligh(预检请求)响应头Access-Control-Allow-Headers的一部分,默认为Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers

      4、cors.exposed.headers

        浏览器允许访问的头部信息列表,","分隔。这些方法将出现在Prefligh(预检请求)响应头Access-Control-Allow-Headers的一部分,默认为空。

      5、cors.preflight.maxage

        浏览器允许缓存的Preflght请求结果的时间,单位为秒。如果为负数,则表示CorsFilter不会添加头到Preflight响应,这些方法将出现在Prefligh(预检请求)响应头Access-Control-Max-Age的一部分,默认为1800.

      6、cors.support.credentials

        表示资源是否支持用户证书,这些方法将出现在Prefligh(预检请求)响应头Access-Control-Allow-Credentials的一部分,默认为true

      7、cors.request.decorate

        Cors规范属性是否已经添加到HttpServletRequest,默认为true。CorsFiter会为HttpServletRequest添加请求相关信息,cors.request.decorate配置为true,那么以下属性将会被添加

        1)cors.isCorsRequest: 用于请求是否为Cors请求。

        2)cors.request.origin: 源URL,请求源自的页面URL。

        3)cors.request.type: Cors的请求类型,如下:

          SIMPLE: 非Preflight请求为先导的请求。

          ACTUAL: 以Preflight请求为先导的请求。

          PRE_FLIGHT: Preflight请求

          NOT_CORS: 正常同域请求

          INVALID_CORS: 无效的域请求

        4)cors.request.headers: 作为Preflight请求Access-Control-Request-Header头发送的请求头信息。

    preFlight踩过的坑

    这两天在使用NodeJS Express搭建REST服务器时遇到一个很典型的AJAX跨域包含自定义请求头问题(用于身份验证),在花了大半天时间排查问题后发现自己对CORS真正的理解还很不够,尤其是pre-flight。

    需求描述

    服务端使用NodeJS Express搭建包含JWT身份验证的REST Full API, 客户端在获取到JWT信息之后的每次API请求头中都附带上JWT信息,完成身份验证后才能执行API操作,否则返回401错误。

    代码

    服务器端(CORS核心部分):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    ------ App -----
    ...
    // Enable CORS from client-side
    app.use(function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials");
    res.header("Access-Control-Allow-Credentials", "true");
    next();
    });

    //parse application/json and look for raw text
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.text());
    app.use(bodyParser.json({ type: 'application/json' }));

    // Routes configuration
    apiRoutes(app);

    app.listen(port);

    ------- User -----
    //==========================
    // User Routes
    //==========================
    apiRoutes.use('/user', passport.authenticate('jwt', {session: false }), userRoutes);
    userRoutes.get('/', user.getUsers);
    userRoutes.get('/:id', user.getUser);
    userRoutes.post('/', user.postUser);
    userRoutes.put('/:id', user.updateUser);
    userRoutes.delete('/:id', user.deleteUser);

    上面的代码看起来还是那么多清晰,在PostMan 测试中附带jwt也是没有任何的问题,成功返回。
    image

    接下来是客户端(jquery ajax):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    ------- Core --------
    function BaseManager(auth) {
    this.baseApiUrl = 'http://localhost:8080/api/';
    this.auth = auth;
    }
    BaseManager.prototype.get = function (url, successCallback, errorCallback) {
    this.ajax(url, {}, 'get', successCallback, errorCallback);
    }
    BaseManager.prototype.ajax = function (url, data, type, successCallback, errorCallback) {
    let that = this;
    $.ajax({
    url: url,
    method: type,
    data: data,
    beforeSend: function (req) {
    req.setRequestHeader('Authorization', that.auth.authorizationToken);
    }
    })
    .done(successCallback)
    .fail(errorCallback);
    }
    ----------- User -------
    User.prototype.getUserById = function (id, successCallback, errorCallback) {
    let url = this.baseApiUrl + '/user/' + id;
    this.get(url, successCallback, errorCallback);
    }

    永远的401

    然后, 问题出现了,尽管参数是如何的对,Chrome console下总是返回让人咬牙切齿的大红色401,甚至断点都没有进入到passport的Jwt middleware下。
    image

    无数次的尝试,先是怀疑客户端ajax调用没对,甚至搬用最原生的ajax方法, 也怀疑过是服务端Jwt passport没写对,最后比较http请求头的时候发现了一些问题。

    使用Post man在node服务器端得到的request是这样的:

    image

    通过浏览器ajax请求是这样的:
    image

    有人给我把请求头信息更改了!Authorization不见了,甚至连req.method都变成了OPTIONS,而不是GET。

    罪魁祸首—预检(Pre-flight)

    百思不得其解,Google相关关键词后,pre-flight浮出水面,到了这步,突然想起阮一峰的《跨域资源共享 CORS 详解》,当时只是略读,大概了解CORS中有两种请求:简单请求和非简单请求。于是又翻出来看了下,此时的情况正是属于非简单请求,会发送两次的请求,第一次就是preflight,用于请求验证, 第二次才是用户真正需要发送的请求。

    对于Pre-flight权威的解读: mozilla.org

    回到代码中,不巧,每次服务端捕捉到的就是这个preflight请求,然后做next,其中就包括Jwt 中间件,而因为请求头中没有Authorization这个header,Jwt就返回了401,而这个过程是在passport的JWT中自动检测的,自己写的JWT验证部分甚至都没有执行到!

    解决办法

    看了express cors源码后,其实把请求类型OPTIONS做个简单的过滤就好啦!!!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Enable CORS from client-side
    app.use(function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials");
    res.header("Access-Control-Allow-Credentials", "true");
    if (req.method == "OPTIONS") {
    res.send(200);
    }
    else {
    next();
    }
    });

    总结:

    又想了一下为什么之前的项目一直没有这个问题,其实是因为很多框架以及帮我们实现好了,比如说.NET中的WebAPI, 在做验证的时候我们都不用去考虑需要捕捉pre-flight请求,而在express中,甚至如果我当初直接使用三方库express cors 也可以避免,但是幸运的是,因为这种偶然,我们更有机会看得更清楚这些请求的后面到底是什么。

    看似简单的问题,却包括了很多需要自己去了解的东西,尤其是http各种请求头的含义,比如Content-type, Accept, 以及对应ajax应该传递的参数,最后,当然还有 Pre-flight!

     

    二、CSRF保护过滤器CsrfPreventionFilter

      org.apcache.catalina.filters.CsrfPreventionFilter为Web应用提供了基本的CSRF保护。返回的客户端的所有链接均通过HttpServletResponse.encodeRedirectURL(String)与HttpServletResponse.encodeURL(String)进行编码,该过滤器生成一个随机数并存储到会话session中进行对比,URL使用该随机数进行编码。当接收到下一个请求时,请求中随机数与会话中的进行对比,只有两者相同时,请求才会被允许。

    配置示例

    复制代码
    <filter>
            <filter-name>CsrfPreventionFilter</filter-name>
            <filter-class>org.apache.catalina.filters.CsrfPreventionFilter</filter-class>
            <init-param>
                <param-name>denyStatus</param-name>
                <param-value>403</param-value>
            </init-param>
            <init-param>
                <param-name>entryPoints</param-name>
                <param-value>/html,/html/list</param-value>
            </init-param>
            <init-param>
                <param-name>nonceCacheSize</param-name>
                <param-value>5</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>CsrfPreventionFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    复制代码

    参数说明

      1、denyStatus:HTTP响应吗,用于驳回拒绝请求,默认为403

      2、entryPoints:以","为分隔的URL列表,这些列表将不会进行随机数检测(主要用于通过导航离开受保护应用,之后再返回) 

     if ("GET".equals(req.getMethod()) && this.entryPoints.contains(this.getRequestedPath(req))) {
                    skipNonceCheck = true;
     }

      3、nonceCacheSize:随机数缓存大小。先前发布的随机数被缓存到一个LRU缓存中以支持并发请求,有限的用于浏览器刷新等行为(可能导致随机数不是当前的),默认为5

    复制代码
    private int nonceCacheSize = 5;
    ....
    if (nonceCache == null) {
        nonceCache = new CsrfPreventionFilter.LruCache(this.nonceCacheSize);
          if (session == null) {
               session = req.getSession(true);
           }
    
        session.setAttribute("org.apache.catalina.filters.CSRF_NONCE", nonceCache);
    }
    复制代码

      4、randomClass:用于生成随机数的类,必须是java.util.Random实例,如不设置默认为java.security.SecureRandom

    三、防止参数丢失过滤器FailedRequestFilter

      org.apcache.catalina.filters.FailedRequestFilter用于触发请求的参数解析,当参数解析失败时,将会拒绝请求,该Filter用于确保客户端提交的参数信息不发生丢失。该过滤器的原理是:先调用ServletRequest.getParameter(首次调用会触发Tomcat服务器的请求参数解析,如果参数解析失败,将结果放到请求属性org.apache.catalina.parameter_parse_failed中),之后判断属性org.apache.catalina.parameter_parse_failed的值,如果不为空则直接返回400。

      为了能正确解析参数,需要该Filter之前设置字符集编码过滤器SetCharacterEncodingFilter。此外,该过滤器是不支持r初始化参数的

    // 判断是否为有效的请求:org.apache.catalina.parameter_parse_failed为null
    private boolean isGoodRequest(ServletRequest request) {
            request.getParameter("none");
            return request.getAttribute("org.apache.catalina.parameter_parse_failed") == null;
        }

     

    四、获取客户端IP过滤器RemoteAddrFilter

      org.apcache.catalina.filters.RemoteAddrFiler允许比较提交的客户端IP地址(通过ServletRequest.getRemoteAddr获取)是否符合指定正则表达式。

    配置示例

    复制代码
        <filter>
          <filter-name>Remote Address Filter</filter-name>
          <filter-class>org.apache.catalina.filters.RemoteAddrFilter</filter-class>
          <init-param>
            <param-name>allow</param-name>
            <param-value>127.d+.d+.d+|::1|0:0:0:0:0:0:0:1</param-value>
          </init-param>
        </filter>
        <filter-mapping>
          <filter-name>Remote Address Filter</filter-name>
          <url-pattern>/*</url-pattern>
        </filter-mapping>
    复制代码

    参数说明

      1、allow:指定允许访问的客户端IP地址

      2、deny:拒绝访问的客户端地址

      3、denyStatus:拒绝请求时返回的HTTP响应吗。

     

    五、获取客户端Host过滤器RemoteHostFilter

      org.apcache.catalina.filters.RemoteHostFiler允许比较提交请求的客户端主机名是否符合指定的正则表达式,以确定是否允许继续处理请求。参数同RemoteAddrFilter

     

    六、获取原始客户端IP过滤器RemoteIpFilter

        当客户端通过HTTP代理或者负载均衡访问服务器时,对于服务器来说,请求直接源自前置的代理服务器,此时获取到的远程IP实际为代理服务器的IP地址。

          这时候如何获得原始的客户端的IP地址呢?

          HTTP协议通过X-Forwarded-For头信息记录了资客户端到应用服务器前置代理的IP地址,RemoteIpFilter通过解析该请求头,将请求中的IP地址与主机名替换为客户端真实的IP地址和主机信息,此外还可以通过X-Forwardred-Proto请求头替换当前的协议名称http/https、服务器端口及request.secure。

          X-Forwarded-For的格式如下:

          X-Forwarded-For:  client, proxy1, proxy2

          最左侧client为最原始的客户端IP,如上示例中客户端经过了proxy1、proxy2、proxy3三级代理(最后一层proxy3不显示,通过ServletRquest.getRemoteAddr获取)。在负载均衡的情况下,RemoteAddrFilter和RemoteHostFilter需要与该过滤器配合使用,否则无法正确限制访问客户端。

      通常我们获取X-Forwarded-For使用如下Java代码:

    复制代码
     public static String getIp(HttpServletRequest request) {
            String requestAddr = request.getHeader("x-forwarded-for");
            if (requestAddr == null || requestAddr.length() == 0 || "unknown".equalsIgnoreCase(requestAddr)) {
                requestAddr = request.getHeader("Proxy-Client-IP");
            }
    
            if (requestAddr == null || requestAddr.length() == 0 || "unknown".equalsIgnoreCase(requestAddr)) {
                requestAddr = request.getHeader("WL-Proxy-Client-IP");
            }
    
            if (requestAddr == null || requestAddr.length() == 0 || "unknown".equalsIgnoreCase(requestAddr)) {
                requestAddr = request.getRemoteAddr();
            }
    
            return requestAddr;
        }
    复制代码

    配置示例

      1)基本处理X-Forwarded-For头的配置

    复制代码
      <filter>
            <filter-name>RemoteIpFilter</filter-name>
            <filter-class>org.apache.catalina.filters.RemoteIpFilter</filter-class>
          </filter>
    
          <filter-mapping>
            <filter-name>RemoteIpFilter</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
          </filter-mapping>
    复制代码

      2)处理X-Forwarded-For与x-forwarded-proto头部的配置

    复制代码
      <filter>
            <filter-name>RemoteIpFilter</filter-name>
            <filter-class>org.apache.catalina.filters.RemoteIpFilter</filter-class>
            <init-param>
              <param-name>protocolHeader</param-name>
              <param-value>x-forwarded-proto</param-value>
            </init-param>
          </filter>
    
          <filter-mapping>
            <filter-name>RemoteIpFilter</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
          </filter-mapping>
    复制代码

      3)使用内部代理的高级配置

    复制代码
     <filter>
           <filter-name>RemoteIpFilter</filter-name>
           <filter-class>org.apache.catalina.filters.RemoteIpFilter</filter-class>
           <init-param>
             <param-name>allowedInternalProxies</param-name>
             <param-value>192.168.0.10|192.168.0.11</param-value>
           </init-param>
           <init-param>
             <param-name>remoteIpHeader</param-name>
             <param-value>x-forwarded-for</param-value>
           </init-param>
           <init-param>
             <param-name>remoteIpProxiesHeader</param-name>
             <param-value>x-forwarded-by</param-value>
           </init-param>
           <init-param>
             <param-name>protocolHeader</param-name>
             <param-value>x-forwarded-proto</param-value>
           </init-param>
         </filter>
    复制代码

      4)使用可信任代理高级配置

    复制代码
    <filter>
           <filter-name>RemoteIpFilter</filter-name>
           <filter-class>org.apache.catalina.filters.RemoteIpFilter</filter-class>
           <init-param>
             <param-name>allowedInternalProxies</param-name>
             <param-value>192.168.0.10|192.168.0.11</param-value>
           </init-param>
           <init-param>
             <param-name>remoteIpHeader</param-name>
             <param-value>x-forwarded-for</param-value>
           </init-param>
           <init-param>
             <param-name>remoteIpProxiesHeader</param-name>
             <param-value>x-forwarded-by</param-value>
           </init-param>
           <init-param>
             <param-name>trustedProxies</param-name>
             <param-value>proxy1|proxy2</param-value>
           </init-param>
         </filter>
    复制代码

    七、字符集编码过滤器SetCharacterEncodingFilter

      提供了一种设置字符集编码的方式,通常情况下默认ISO-8859-1编码,但实际生产环境推荐使用UTF-8编码,而请求中的编码可以在未指定编码时使用,也可以强制覆盖。

    配置示例

    复制代码
    <filter>
            <filter-name>SetCharacterEncodingFilter</filter-name>
            <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
            <init-param>
                <param-name>encoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
            <init-param>
                <param-name>ignore</param-name>
                <param-value>false</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>SetCharacterEncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    复制代码

    参数说明

      1、encoding:指定的字符集编码  

      2、ignore:表示是否忽略客户端请求设置的字符集编码,如果为true那么都会将请求字符集编码覆盖,如果为false,请求没有指定字符集编码时设置。默认为false

    转自:https://www.jianshu.com/p/b55086cbd9af

    https://www.cnblogs.com/jian0110/p/10512188.html

    https://troyyang.com/2017/06/06/Express_Cors_Preflight_Request/

  • 相关阅读:
    Ext.Net 1.2.0_利用 Ext.Net 自定义 GridPanel Ajax 控件
    ASP.NET_0404_ASP.NET 重定向:页面传值
    程序设计_洗牌程序
    表单/验证表单——千万不要做一个只会拖控件、“照猫画虎”、copy/paste 程序员
    ASP.NET_0204_ASP.NET 重定向:如何将用户重定向到另一页
    Oracle 11g R1(11.1) Joins表连接
    隐藏 iframe 技术——Ajax 时代一个重要的环节
    Ext.Net 1.2.0_改变 Ext.Net.GridPanel 某行或某列的式样
    数据结构冒泡排序和直接插入排序
    XMLHttpRequest——Ajax 时代的到来
  • 原文地址:https://www.cnblogs.com/nizuimeiabc1/p/13059913.html
Copyright © 2020-2023  润新知