文档章节

神奇的403

Mr_Qi
 Mr_Qi
发布于 06/13 13:08
字数 1319
阅读 579
收藏 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

粉丝 273
博文 350
码字总数 359193
作品 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
常见的网页报错

400 无法解析此请求。 401.1 未经授权:访问由于凭据无效被拒绝。 401.2 未经授权: 访问由于服务器配置倾向使用替代身份验证方法而被拒绝。 401.3 未经授权:访问由于 ACL 对所请求资源的设置...

21种犹豫
2014/12/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

70.shell的函数 数组 告警系统需求分析

20.16/20.17 shell中的函数 20.18 shell中的数组 20.19 告警系统需求分析 20.16/20.17 shell中的函数: ~1. 函数就是把一段代码整理到了一个小单元中,并给这个小单元起一个名字,当用到这段...

王鑫linux
今天
0
0
分布式框架spring-session实现session一致性使用问题

前言:项目中使用到spring-session来缓存用户信息,保证服务之间session一致性,但是获取session信息为什么不能再服务层获取? 一、spring-session实现session一致性方式 用户每一次请求都会...

WALK_MAN
今天
5
0
C++ yield()与sleep_for()

C++11 标准库提供了yield()和sleep_for()两个方法。 (1)std::this_thread::yield(): 线程调用该方法时,主动让出CPU,并且不参与CPU的本次调度,从而让其他线程有机会运行。在后续的调度周...

yepanl
今天
4
0
Java并发编程实战(chapter_3)(线程池ThreadPoolExecutor源码分析)

这个系列一直没再写,很多原因,中间经历了换工作,熟悉项目,熟悉新团队等等一系列的事情。并发课题对于Java来说是一个又重要又难的一大块,除非气定神闲、精力满满,否则我本身是不敢随便写...

心中的理想乡
今天
33
0
shell学习之获取用户的输入命令read

在运行脚本的时候,命令行参数是可以传入参数,还有就是在脚本运行过程中需要用户输入参数,比如你想要在脚本运行时问个问题,并等待运行脚本的人来回答。bash shell为此提 供了read命令。 ...

woshixin
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部