美文网首页
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仅监听https端口且非443端口时SpringSec

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