文档章节

神奇的403

Mr_Qi
 Mr_Qi
发布于 06/13 13:08
字数 1319
阅读 591
收藏 4

背景

应曾老师要求 来写一篇神奇的403……

分享会结束后 某曾老师高兴地说道:“试用出bug了……测试【^^!】说你修改了profile“

先来看一下现象!

请求返回403~!

问题

/kzf6/maintain/getNoFinishMaintainList.do?idCar=4060591933968503770&maintainPkId= 403

 

首先一个403过来 开发反馈在测试环境上没有问题 但是在试用环境GG!

随后运维反馈nginx没有做过变更。

那么问题出现在哪呢?

分析

首先确认并非nginx问题 去tomcat查看对应access日志

[root@iZuf65br1kzrdlcne4bxpzZ logs]# grep 403 localhost_access_log.2018-05-09.txt |wc -l
1067

似乎全部集中在发布之后 看来还是新的代码导致!

查看该请求

/**
 * 未完工工单了列表
 *
 * @param  maintainSo 车辆主键
 * @return 未完成工单的列表
 * @throws Exception
 */
@RequestMapping(value = "/getNoFinishMaintainList", method = {RequestMethod.POST, RequestMethod.GET})
@RequiresPermissions(Permission.MAINTAIN_CAR_MAINTAIN_VIEW_LIST)
@ResponseBody
public List<TsMaintainVO> getNoFinishMaintainList(MaintainSo maintainSo) throws Exception {
}

这个请求同时支持GET和POST 似乎看起来也没有问题!

那问题出在哪呢???

首先看到大部分出问题的都是POST请求

由于前几次关于POST请求的跨域问题考虑是否跨域导致出现了一些问题!

尝试了一下url

$.post("https://yunxiu-trial.f6car.com/kzf6/maintain/getNoFinishMaintais")

依然返回了403 这个比较奇怪了!按照道理这个请求应该返回404 不应该返回403啊 那么可以断定请求没有打到Servlet!

因此猜测这个应该是到了某个filter出现了问题~ 调查后发现小伙伴加了tomcat的corsFilter

<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.support.credentials</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CorsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

把这段话注释掉了 果然一切正常!

解读

@Override
public void doFilter(final ServletRequest servletRequest,
        final ServletResponse servletResponse, final FilterChain filterChain)
        throws IOException, ServletException {
    if (!(servletRequest instanceof HttpServletRequest) ||
            !(servletResponse instanceof HttpServletResponse)) {
        throw new ServletException(sm.getString("corsFilter.onlyHttp"));
    }
 
    // Safe to downcast at this point.
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;
 
    // Determines the CORS request type.
    CorsFilter.CORSRequestType requestType = checkRequestType(request);
 
    // Adds CORS specific attributes to request.
    if (decorateRequest) {
        CorsFilter.decorateCORSProperties(request, requestType);
    }
    switch (requestType) {
    case SIMPLE:
        // Handles a Simple CORS request.
        this.handleSimpleCORS(request, response, filterChain);
        break;
    case ACTUAL:
        // Handles an Actual CORS request.
        this.handleSimpleCORS(request, response, filterChain);
        break;
    case PRE_FLIGHT:
        // Handles a Pre-flight CORS request.
        this.handlePreflightCORS(request, response, filterChain);
        break;
    case NOT_CORS:
        // Handles a Normal request that is not a cross-origin request.
        this.handleNonCORS(request, response, filterChain);
        break;
    default:
        // Handles a CORS request that violates specification.
        this.handleInvalidCORS(request, response, filterChain);
        break;
    }
}

我们由于得到了403 着重看一下什么情况返回403

/**
 * Handles a CORS request that violates specification.
 *
 * @param request
 *            The {@link HttpServletRequest} object.
 * @param response
 *            The {@link HttpServletResponse} object.
 * @param filterChain
 *            The {@link FilterChain} object.
 */
private void handleInvalidCORS(final HttpServletRequest request,
        final HttpServletResponse response, final FilterChain filterChain) {
    String origin = request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN);
    String method = request.getMethod();
    String accessControlRequestHeaders = request.getHeader(
            REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
 
    response.setContentType("text/plain");
    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    response.resetBuffer();
 
    if (log.isDebugEnabled()) {
        // Debug so no need for i18n
        StringBuilder message =
                new StringBuilder("Invalid CORS request; Origin=");
        message.append(origin);
        message.append(";Method=");
        message.append(method);
        if (accessControlRequestHeaders != null) {
            message.append(";Access-Control-Request-Headers=");
            message.append(accessControlRequestHeaders);
        }
        log.debug(message.toString());
    }
}

果然如此 在cors不合法的场景下直接返回了403 SC_FORBIDDEN

interesting~为何全部走到了InvalidCORS呢?

很明显关键在于该方法

/**
 * Determines the request type.
 *
 * @param request
 */
protected CORSRequestType checkRequestType(final HttpServletRequest request) {
    CORSRequestType requestType = CORSRequestType.INVALID_CORS;
    if (request == null) {
        throw new IllegalArgumentException(
                sm.getString("corsFilter.nullRequest"));
    }
    String originHeader = request.getHeader(REQUEST_HEADER_ORIGIN);
    // Section 6.1.1 and Section 6.2.1
    if (originHeader != null) {
        if (originHeader.isEmpty()) {
            requestType = CORSRequestType.INVALID_CORS;
        } else if (!isValidOrigin(originHeader)) {
            requestType = CORSRequestType.INVALID_CORS;
        } else if (isLocalOrigin(request, originHeader)) {
            return CORSRequestType.NOT_CORS;
        } else {
            String method = request.getMethod();
            if (method != null) {
                if ("OPTIONS".equals(method)) {
                    String accessControlRequestMethodHeader =
                            request.getHeader(
                                    REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
                    if (accessControlRequestMethodHeader != null &&
                            !accessControlRequestMethodHeader.isEmpty()) {
                        requestType = CORSRequestType.PRE_FLIGHT;
                    } else if (accessControlRequestMethodHeader != null &&
                            accessControlRequestMethodHeader.isEmpty()) {
                        requestType = CORSRequestType.INVALID_CORS;
                    } else {
                        requestType = CORSRequestType.ACTUAL;
                    }
                } else if ("GET".equals(method) || "HEAD".equals(method)) {
                    requestType = CORSRequestType.SIMPLE;
                } else if ("POST".equals(method)) {
                    String mediaType = getMediaType(request.getContentType());
                    if (mediaType != null) {
                        if (SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES
                                .contains(mediaType)) {
                            requestType = CORSRequestType.SIMPLE;
                        } else {
                            requestType = CORSRequestType.ACTUAL;
                        }
                    }
                } else {
                    requestType = CORSRequestType.ACTUAL;
                }
            }
        }
    } else {
        requestType = CORSRequestType.NOT_CORS;
    }
 
    return requestType;
}

一开始就赋值为CORSRequestType requestType = CORSRequestType.INVALID_CORS; 因此很容易得出结论 当未重新赋值的时候cors就是失败的~

所以可以从这段代码中得到答案

String mediaType = getMediaType(request.getContentType());
                   if (mediaType != null) {
                       if (SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES
                               .contains(mediaType)) {
                           requestType = CORSRequestType.SIMPLE;
                       } else {
                           requestType = CORSRequestType.ACTUAL;
                       }
                   }

当mediaType为空时【前提是POST】此时结果就是CORS失败导致403 而mediaType是从ContentType中获取的 因此ContentType必须不能为空才可以。 否则post请求过来就是cors失败返回403~

至于为啥POST请求某些场景没有携带ContentType  从前端分析吧

 

彩蛋

你以为结束了么!事实上在测试环境和试用环境两个不同的结果也是一个问题!为何在测试环境一切OK了 上了生产该问题集中爆发了呢?

事实上测试环境也有问题【只不过在某种场景下测试环境没有问题】

private boolean isLocalOrigin(HttpServletRequest request, String origin) {
 
    // Build scheme://host:port from request
    StringBuilder target = new StringBuilder();
    String scheme = request.getScheme();
    if (scheme == null) {
        return false;
    } else {
        scheme = scheme.toLowerCase(Locale.ENGLISH);
    }
    target.append(scheme);
    target.append("://");
 
    String host = request.getServerName();
    if (host == null) {
        return false;
    }
    target.append(host);
 
    int port = request.getServerPort();
    if ("http".equals(scheme) && port != 80 ||
            "https".equals(scheme) && port != 443) {
        target.append(':');
        target.append(port);
    }
 
    return origin.equalsIgnoreCase(target.toString());
}

我们从这段代码中可以看到 tomcat的CorsFilter通过该请求来判断是否是LocalOrigin

在测试环境下由于没有nginx 因此直接通过http请求访问 此时获取到的scheme为http 因此实质上异常的post请求【即没有携带ContentType】也能得到未跨域的结果 因此正常返回

但是对于试用环境 由于 https 也一直强调的getSchema仍然会返回http【为什么?自己想!!】那么port返回啥? 参考 https使用nginx设置port

因此此时很明显一个http 而另一个是https 明显匹配不上【此时后端认为需要跨域了……】

结果很明了~

 

© 著作权归作者所有

共有 人打赏支持
Mr_Qi

Mr_Qi

粉丝 280
博文 359
码字总数 369228
作品 0
南京
程序员
私信 提问
ruby gem 0.9.4的问题

在配置gem的过程中,有极小机率会遇到gem安装后失效的问题,比方说会403,如下所示: C:Usersgeraldlau>gem update --system Updating RubyGems... ERROR: While executing gem ... (Gem::R...

神勇小白鼠
2011/03/08
0
0
登录框之另类思考:来自客户端的欺骗

  *本文原创作者:TopScrew,本文属FreeBuf原创奖励计划,未经许可禁止转载   0×01 前言   前几天刚见人发了一个登录框引发的血案,而常规的爆破有风控和各种变态验证码,或者大型的电...

FreeBuf
07/02
0
0
HTTP常见错误代码列表汇总及解决方案

HTTP常见错误代码列表汇总及解决方案 常见的HTTP错误可以分为以下四大类。每一大类又细分为很多类小错误。分别是: 1、401类错误 最常见的出错提示:401 UNAUTHORIZED 这表示你必须有一个正确...

长平狐
2012/10/08
68
0
服务器错误数字(代码)对照表

400 错误请求 — 请求中有语法问题,或不能满足请求。 404 找不到 — 服务器找不到给定的资源;文件不存在 500 内部错误 — 因为意外情况,服务器不能完成请求 或者出问题了 2xx 成功 200 正...

陈映亮
2016/07/21
7
0
nginx error_page注意事项

nginx 的errorpage对于同一个http状态码,只能执行一次,第二次就不会再执行了(初认识有错误)。 例如:403状态 下面这段代码,如果访问出现403,会跳到retry段,在没有,就会直接出现403,...

perofu
2015/09/18
4.5K
0

没有更多内容

加载失败,请刷新页面

加载更多

浅析微信支付:开通免充值产品功能及如何进行接口升级指引

本文是【浅析微信支付】系列文章的第十五篇,主要讲解如何开通免充值产品功能流程和其中的注意事项,对于接口升级会重要讲解,避免爬坑。 浅析微信支付系列已经更新十五篇了哟~,没有看过的...

YClimb
31分钟前
0
0
看看ArrayDeque源码

之前看了其他实现Deque接口的类,这里再看看ArrayDeque吧,下图可以看到这个类设计的结构层次,其实Deque接口是继承了Queue接口的。用可调整大小的数组实现Deque接口。没有容量限制,他们根据...

woshixin
31分钟前
0
0
如何存储登录cookie,发送一个post类型的api请求?

其实发送post请求和get请求一样,主要就是不知道如何存储登录cookie进行发送请求,请教xx得到如下解决办法 点击右下方的cookies,填写cookies信息,如图 1填写登录的cookie名,2填写cookie的...

七曦777
31分钟前
0
0
利用cefSharp实现网页自动注册登录的需要注册的一些事项

最近朋友有个需要自动注册登录点击的事,我帮着写了写,好久没写过这东西了,在写的过程中总结了需要注意的一些事项。 一、换IP之后要测试一下速度,我目前用的最简单的测试方法就是20-30秒加...

我退而结网
40分钟前
1
0
Go语言中使用 BoltDB数据库

boltdb 是使用Go语言编写的开源的键值对数据库,Github的地址如下: https://github.com/boltdb/bolt boltdb 存储数据时 key 和 value 都要求是字节数据,此处需要使用到 序列化和反序列化。...

Oo若离oO
40分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部