如何将 Spring Security 与负载均衡器一起使用?

Posted

技术标签:

【中文标题】如何将 Spring Security 与负载均衡器一起使用?【英文标题】:How to use Spring Security with load balancer? 【发布时间】:2022-01-01 00:27:21 【问题描述】:

我是 loadBalancing 的新手,所以我需要帮助,这就是我所做的: 我将 2 个服务构建为 2 个应用程序(A、B)我在它们两个上都使用了 Spring Security (它们都是 restfull api,它们有 ymleaf 和完整的前端页面), 然后我做了另一个应用程序作为弹簧云负载平衡器。 当我发送请求时,它会从负载平衡器应用程序转到 2 个服务之一,但问题是当我未通过身份验证时,响应将为空,它不会像我使用普通 A 应用程序时一样将我带到默认登录页面直接,当我转到不需要经过身份验证才能访问它的页面时,它会在没有我的 css/js 样式的情况下返回

这是我的 A 应用控制器(它返回的不是 json 视图)

    package com.hariri_stocks.controllers;

    import java.util.ArrayList;
    import java.util.List;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;

    import com.hariri_stocks.models.Estates;
    import com.hariri_stocks.models.SoldEstates;
    import com.hariri_stocks.models.Users;
    import com.hariri_stocks.services.estatesService;

    @Controller
    public class LoginController 

        @Autowired
        estatesService ES;
        
        
        @GetMapping(value = "/")
        public String login() 
            return "/signIn-up.html";
        
        
        @GetMapping(value = "/dashboard")
        public String dashboard(Model model ,@RequestParam(required = false) String add_result
                                                    ,@RequestParam(required = false) String alert_err) 
            List<Estates> estates = ES.findAll();
            model.addAttribute("estates",estates);

        return "/dashboard";
        
        
        @GetMapping(value = "/dashboard/unSold")
        public String unselled_stocks(Model model) 
            List<Estates> estates = ES.findUnsold();

            if(estates.size() > 0) 
            model.addAttribute("estates",estates);
            
            else
            model.addAttribute("error","there is no sold estates yet !!");
            return "/dashboard";
        
        
        @Value(value = "$server.port")
        String port_num;
        
        @GetMapping("/port")
        public String hello() 
            return port_num;
        
        
    

这是我使用 @restcontroller 的负载平衡器控制器

    package com.hariri_loadbalancer;

    import reactor.core.publisher.Mono;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.reactive.function.client.WebClient;

    @SpringBootApplication
    @RestController
    public class UserApplication 

      private final WebClient.Builder loadBalancedWebClientBuilder;
      private final ReactorLoadBalancerExchangeFilterFunction lbFunction;

      public UserApplication(WebClient.Builder webClientBuilder,
          ReactorLoadBalancerExchangeFilterFunction lbFunction) 
        this.loadBalancedWebClientBuilder = webClientBuilder;
        this.lbFunction = lbFunction;
      

      public static void main(String[] args) 
        SpringApplication.run(UserApplication.class, args);
      

      @RequestMapping("/port")
      public Mono<String> showMePort() 
        return loadBalancedWebClientBuilder.build().get().uri("http://hariri/port")
            .retrieve().bodyToMono(String.class);
      
      @RequestMapping("/")
      public Mono<String> showMainPage() 
        return loadBalancedWebClientBuilder.build().get().uri("http://hariri/")
            .retrieve().bodyToMono(String.class);
      
    

那我该怎么办?我觉得我在做的事情很愚蠢, 我是否应该将我所有的 Thymleaf 页面移动到负载均衡器,以便应用程序返回它想要返回的内容 @restController 然后负载均衡器使用 @controller 进入样式首页或者有一种方法,并且为了安全,我应该使用负载均衡器而不是 A、B 应用程序来实现弹簧安全吗 ………………………………………………………………………………………………………… 8080 是负载均衡器端口 9091 是一个应用程序端口 所以它接缝当A返回html页面时,html正在8080的负载均衡器机器上搜索css,而它们存在于9091的A应用程序中

【问题讨论】:

请使用详细的 curl 或邮递员对 LoginController 进行接受和拒绝的请求,并粘贴完整的响应(包括标题)。也通过负载均衡器做同样的事情。我相信你会注意到一些不同的东西。 【参考方案1】:

bodyToMono 对正文进行解码,但您没有处理标题。 在 Spring Security 上,很可能会重定向到登录页面......所以如果你只关注身体,它就不会起作用。这可能也会以某种方式影响样式。

检查以下内容: How to extract response header & status code from Spring 5 WebClient ClientResponse

【讨论】:

对于样式,它在负载均衡应用程序上搜索它们,而它们在应用程序 A ... @muhammad 我认为这也可能是一个配置问题。无论如何,IMO 你不应该将你的样式表移动到 LB。那是混合的担忧。您可以将它们放置在一些类似 CDN 的具有非常长的 TTL 缓存中,因此它们是可访问的和高性能的。

以上是关于如何将 Spring Security 与负载均衡器一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

负载均衡器后面带有 Spring Security 的 Spring Boot:将 HTTP 重定向到 HTTPS

Grails Spring Security 使用 https 重定向到登录页面

(031)Spring Boot之服务的注册与发现,使用zookeeper演示负载均衡

(031)Spring Boot之服务的注册与发现,使用zookeeper演示负载均衡

2.Spring Cloud Alibaba实现负载均衡-Ribbon

spring cloud --- Ribbon 客户端负载均衡 + RestTemplate ---心得无熔断器