Использовать разные пути для государственных и частных ресурсов Джерси + Spring boot

Я использую защиту Spring boot + Jersey + Spring, я хочу иметь общедоступные и частные конечные точки, я хочу, чтобы схема выглядела следующим образом:

  • /rest - мой корневой контекст
  • /public. Я хочу разместить мои публичные конечные точки в этом контексте. Он должен быть внутри корневого контекста, например /rest/public/pings
  • /private. Я хочу разместить свои личные конечные точки в этом контексте. Он должен находиться внутри корневого контекста, например /rest/private/accounts

У меня есть моя конфигурация следующим образом:

Джерси:

@Configuration
@ApplicationPath("/rest")
public class RestConfig extends ResourceConfig {
    public RestConfig() {
        register(SampleResource.class);
    }
}

Spring безопасность:

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

........

    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/rest/public/**").permitAll();
        http.antMatcher("/rest/**").authorizeRequests().anyRequest().fullyAuthenticated().and().httpBasic();
        http.csrf().disable();
    }

}

Вопрос в том, как я могу зарегистрировать два пути приложения внутри контекста my/rest, один для /public, а другой для /private?

ПРИМЕЧАНИЕ. Я попытался создать еще один ResourceConfig следующим образом:

@Configuration
@ApplicationPath("/rest/public")
public class RestPublicConfig extends ResourceConfig{
    public RestPublicConfig() {
        register(PingResource.class);
    }
}

Но я получаю следующую ошибку:

 No qualifying bean of type [org.glassfish.jersey.server.ResourceConfig] is defined: expected single matching bean but found 2: restConfig,restPublicConfig

Спасибо за вашу помощь:)

Ответы

Ответ 1

В контейнере сервлета, время выполнения Джерси, выполняется как сервлет или как фильтр сервлета. Как spring boot настраивает сервлеты и фильтры через ServletRegistrationBean и FilterRegistrationBeans, соответственно. Чтобы понять, как эта конфигурация работает за сценой, вы можете посмотреть исходный код JerseyAutoConfiguration

В JerseyAutoConfiguration вы можете увидеть, что вводится ResourceConfig, и это ResourceConfig используется для создания фильтра сервлетов Джерси или Джерси (в зависимости от вашего выбора конфигурации). Поэтому причиной ошибки является то, что вы не можете иметь двусмысленный beans, который у вас есть два ResourceConfig beans. Таким образом, spring не знает, какой из них вводить.

Что вы можете сделать, это использовать два разных сервлета для каждого ResourceConfig. Проблема в том, что spring Boot только подключает вас к одному сервлету для Джерси, поэтому вам нужно настроить другой. Существует два варианта:

  • Используйте автоматическую конфигурацию загрузки spring для одного из приложений Джерси и добавьте еще один ServletRegistrationBean для вашего другого. Следует отметить, что ResourceConfig для вашего созданного ServletRegistrationBean не должен быть компонентом spring (т.е. No @Component или @Configuration), иначе вы все равно столкнетесь с той же ошибкой.

    public class PublicConfig extends ResourceConfig {
        public PublicConfig() {
            register(PingResource.class);
        }
    }
    ...
    // in your Spring Boot configuration class
    @Bean
    public ServletRegistrationBean publicJersey() {
        ServletRegistrationBean publicJersey 
                = new ServletRegistrationBean(new ServletContainer(new PublicConfig()));
        publicJersey.addUrlMappings("/rest/public/*");
        publicJersey.setName("PublicJersey");
        publicJersey.setLoadOnStartup(0);
        return publicJersey;
    }
    
  • Не используйте конфигурацию загрузки spring вообще. Просто создайте два ServletRegistrationBean s. В этом случае ни один из ваших классов ResourceConfig не должен быть spring beans.

    @Bean
    public ServletRegistrationBean publicJersey() {
        ServletRegistrationBean publicJersey 
                = new ServletRegistrationBean(new ServletContainer(new PublicConfig()));
        publicJersey.addUrlMappings("/rest/public/*");
        publicJersey.setName("PublicJersey");
        publicJersey.setLoadOnStartup(0);
        return publicJersey;
    }
    
    @Bean
    public ServletRegistrationBean privateJersey() {
        ServletRegistrationBean privateJersey 
               = new ServletRegistrationBean(new ServletContainer(new PrivateConfig()));
        privateJersey.addUrlMappings("/rest/private/*");
        privateJersey.setName("PrivateJersey");
        privateJersey.setLoadOnStartup(1);
        return privateJersey;
    }
    

Лично я предпочитаю второй вариант, так как легче рассуждать о конфигурациях, когда все они находятся в одном месте.

Еще одна вещь, которую следует отметить, заключается в том, что два приложения Джерси будут полностью независимыми, то есть вам нужно будет зарегистрировать поставщиков (например, фильтры) для обоих приложений.

Ответ 2

Вам не будет разрешено создавать два beans для вашего класса ресурсов. Вы можете достичь того, чего вы пытаетесь достичь, используя один класс ресурсов.

Вот пример:

@Path("rest")
public class SampleResourceClass {

  @Path("/public/pings")
  @GET
  public Responce getPings(){
    /* Code Here */
  }

  @Path("/private/accounts")
  @GET
  public Response getAccounts(){
    /* Code Here */
  }
}

Ответ 3

Ошибка, которую вы видите, не связана с вашей конфигурацией безопасности, вы можете взглянуть на этот билет, https://github.com/spring-projects/spring-boot/issues/3260

Если вы хотите разрешить весь трафик до конечных точек минус /public, вы можете добавить RequestMatcher в список игнорирования безопасности Spring.

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {

        web.ignoring().antMatchers("/rest/public/**");
     }

     protected void configure(HttpSecurity http) throws Exception {

          http.authorizeRequests().antMatcher("/rest/private/**")
          .anyRequest().authenticated().and()
          .httpBasic().and()
          .csrf().disable()
     }

}

http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc