美文网首页
Nginx仅监听https端口且非443端口时SpringSec

Nginx仅监听https端口且非443端口时SpringSec

作者: Teddy_b | 来源:发表于2023-09-21 15:01 被阅读0次

背景

Nginx反向代理着Spring boot搭建的Java服务

正常情况下Nginx同时监听着80和443端口,SpringSecurity中的CAS用着也没毛病

突然有一天,80和443端口不让用了,还只能监听https端口,CAS流程用着就不行了,主要归结为两个问题

问题

问题1--重定向到http

由于Spring boot应用是http部署的,nginx和后端之间也是http交互,所以后端获取到的scheme都是http

再重定向的时候都重定向到http的地址上,但是现在nginx是没有监听http的,因此会报错400 Send http request to https server

问题2--重定向无端口号

再解决了上一个问题后,出现的新问题,重定向的时候地址里没有包括端口号,直接重定向到了443端口,导致连接不通

解决方案

先说解决方案,再细说踩坑过程

Nginx配置
location / {
        
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_pass http://upstream;
    }

首先是再Nginx的location配置添加这两个Header

  • $scheme对应的是协议httphttps,取决于location位于http的server块中,还是https的server快中

  • $server_port对应的是当前server块里监听的端口号

Springboot启动参数
- --server.tomcat.protocol-header=X-Forwarded-Proto
- --server.tomcat.port-header=X-Forwarded-Port
- --server.use-forward-headers=true

再Springboot的启动参数里我们需要添加这三个,用于接收这些Header信息

就这么两步就能解决问题了,springboot版本为2.1.6,nginx版本为1.16.1

踩坑过程

踩坑1

最开始把Nginx的配置加在server块里面(不是location块里面),一直不生效,搞得都快怀疑人生了

但是为了解决问题,让Springboot在重定向的时候能够转到https,自定义了一个AuthenticationSuccessHandler

public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {

        log.info("Into custom success handler");
        setRequestCache(requestCache);
        SavedRequest savedRequest = requestCache.getRequest(request, response);

        if (savedRequest == null) {
            super.onAuthenticationSuccess(request, response, authentication);

            return;
        }
        String targetUrlParameter = getTargetUrlParameter();
        if (isAlwaysUseDefaultTargetUrl()
                || (targetUrlParameter != null && StringUtils.hasText(request
                .getParameter(targetUrlParameter)))) {
            requestCache.removeRequest(request, response);
            super.onAuthenticationSuccess(request, response, authentication);

            return;
        }

        clearAuthenticationAttributes(request);

        // Use the DefaultSavedRequest URL
        String targetUrl = savedRequest.getRedirectUrl();

        String refer = NetUtil.getSchemeFromRefer(request);
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(refer) && targetUrl.startsWith("http://")) {
            log.info("Replace http with https");
            targetUrl = targetUrl.replace("http://", "https://");
        }

        logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }
}

然后再CasAuthenticationFilter中启用这个AuthenticationSuccessHandler

@Bean
    public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
        CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
        casAuthenticationFilter.setAuthenticationManager(authenticationManager());
        casAuthenticationFilter.setFilterProcessesUrl(CAS_SERVICE_URL);
        casAuthenticationFilter.setAuthenticationDetailsSource(myWebAuthenticationDetailsSource());
        //casAuthenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());
        return casAuthenticationFilter;
    }

最后是我们的登录接口/login,这里原来重定向的时候不是完整的地址,而是只有请求路径和参数,这种路径在重定向的时候也会被重定向到http地址,而不是https地址,因此在这里把重定向的地址改为完成的https地址,避免出现重定向到http地址的问题

    @GetMapping(value = "/login")
    public void login(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String queryString = request.getQueryString();
        String redirect = "/";
       
        LOGGER.info("login redirect:{}", redirect);
        redirect = NetUtil.constructCasUrl(request, redirect, null);
        response.sendRedirect(redirect);
    }

这个解决方案也能正常工作,在AuthenticationSuccessHandler中主要是添加了参考Referer这个Header信息来重新替换https,这样让它在登录成功后能够重定向到https链接

String refer = NetUtil.getSchemeFromRefer(request);
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(refer) && targetUrl.startsWith("http://")) {
            log.info("Replace http with https");
            targetUrl = targetUrl.replace("http://", "https://");
        }

为啥会想到这个解决方案呢?这就需要我们开启Springboot的debug级别日志,添加启动参数

- --logging.level.root=debug

开启debug日志后你可以看到这些日志

2023-09-21 07:36:38.246|DEBUG|http-nio-33442-exec-6 (HttpSessionRequestCache.java:61) - DefaultSavedRequest added to Session: DefaultSavedRequest[http://xx.xx.com:33443/login?path=/xxxxxxxxxxxxxxxxxxxxxxx]
2023-09-21 07:36:38.246|DEBUG|http-nio-33442-exec-6 (ExceptionTranslationFilter.java:212) - Calling Authentication entry point.

2023-09-21 07:37:20.277|DEBUG|http-nio-33442-exec-7 (DefaultRedirectStrategy.java:54) - Redirecting to 'http://xx.xx.com:33443/login?path=/xxxxxxxxxxxxxxxxxxxxxxx'

可以看到在登陆之前Spring Security就会把你之前的登录请求保存下来,在你CAS登录成功后直接读取之前保存的请求进行重定向;

但是问题出在这里保存的登录请求就已经是http了,所以重定向的时候就重定向到了http地址,导致请求400问题

所以上面的解决方案就是不管你之前保存的是http地址还是https地址,在你重定向的时候再查验一下Referer这个Header信息,如果这个Header是https的,那就将重定向的地址改为https;

那为啥这个方案没有让我满意呢?是因为还有这么一行日志打印了Referer这个Header信息

2023-09-21 07:37:20.277|INFO|http-nio-33442-exec-7 (CustomAuthenticationSuccessHandler.java:31) - Into custom success handler
2023-09-21 07:37:20.277|INFO|http-nio-33442-exec-7 (NetUtil.java:100) - Referer header:https://auth.xxx.com/
2023-09-21 07:37:20.277|INFO|http-nio-33442-exec-7 (CustomAuthenticationSuccessHandler.java:57) - Replace http with https
2023-09-21 07:37:20.277|DEBUG|http-nio-33442-exec-7 (DefaultRedirectStrategy.java:54) - Redirecting to 'https://xx.xx:33443/login?path=/xxxxxxxxxxxxxxxxxxxxxxx'

可以看到这里出现了Referer这个Header信息为CAS的地址,这就意味着我们项目里是否替换https还需要依赖CAS的地址吗?

这显然是不科学的,如果CAS地址是http的,那我们就GG了;或者我们是http的,但是CAS是https的,那我们也GG了

所以还是没有采纳这个方案

踩坑2

病急乱投医,一顿瞎配置

最初把Nginx的X-Forwarded-*配置放在server块里,一直没觉得有啥问题,给Springboot一顿瞎配置

# Springboot 2.1.6不能用这两个配置
server.tomcat.remoteip.remote-ip-header=x-your-remote-ip-header
server.tomcat.remoteip.protocol-header=x-your-protocol-header

# 这个限制IP的也不需要
server.tomcat.remoteip.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}

# Springboot 2.1.6 不能用这个配置
server.forward-headers-strategy=native

# 这个官网给的配置也不需要
server.tomcat.redirect-context-root=false
踩坑3

最开始只想要能够通过X-Forwarded-Proto拿到scheme信息,所以只加了这一个Header

但是再登录成功后重定向到了https://xx.xx/login?path=/xxxxxxxxxxxxxxxxxxxxxxx地址,即重定向到了443端口

为啥会重定向到443端口呢?我们具体看下端口是怎么计算出来的

public int getServerPort(ServletRequest request) {
        int serverPort = request.getServerPort();
                ...

        return serverPort;
    }

可以看到是ServletRequest里没有获取到Nginx中指定的端口,只获取到了默认的端口443

后来加上了X-Forwarded-Port这个Header后解决了,让Nginx把端口信息也传下来就可以了

相关文章

  • nginx端口转发

    我们常用的端口有 80/443 端口80端口对应着HTTP,443端口对应着HTTPS配置好 nginx 之后,可...

  • 记录一次上线错误

    因为在nginx上面不仅配置了https的443端口的监听,同时监听了80端口。通过浏览器测试确实是没有问题,但是...

  • 配置https

    我的https 是申请腾讯免费的监听80端口转发到https 监听443端口 可以参考一下

  • https docker nginx

    docker 配置nginx 容器 https step 1启动容器 暴露 80 端口 与 443 端口 www....

  • PHP怎么判断当前是否为https协议

    https默认是443但是什么端口都支持转发的时候带个标志 或者 转发个非443的 我https,获取到的端口是...

  • docker配置nginx

    拉取nginx镜像 创建文件夹 启动 映射端口443,用于https请求 映射端口80,用于http请求; ngi...

  • nginx conf

    nginx配置监听端口: 问题经过:react-router启动express服务器,未经过nginx监听的端口,...

  • Nginx与Trojan共用443

    Nginx与Trojan共用443端口 这个部分主要是摘自Trojan 共用 443 端口方案。首先是为什么Ngi...

  • nginx安全问题

    一、nginx 默认转发至现有虚拟主机nginx 在开启某些监听端口时,必须关闭默认端口转发(比如: 你nginx...

  • 阿里云负载均衡监听端口异常的分析

    今天为应用配置负载均衡,监听的前端设置为https 443端口,后端为http 80端口,设置后,健康监测一直显示...

网友评论

      本文标题:Nginx仅监听https端口且非443端口时SpringSec

      本文链接:https://www.haomeiwen.com/subject/ozcavdtx.html