После Spring Boot 2 сервер авторизации upgade возвращает «По крайней мере, один redirect_uri должен быть зарегистрирован у клиента».

#spring-boot #spring-security

#spring-boot #spring-безопасность

Вопрос:

Я обновил наш сервер авторизации с Spring Boot 1.5.13.RELEASE до 2.1.3.RELEASE, и теперь я могу аутентифицироваться, но я больше не могу получить доступ к сайту. Вот результирующий URL и ошибка после отправки в /login.

 https://auth-service-test-examle.cfapps.io/oauth/authorize?client_id=proxy-serviceamp;redirect_uri=http://test.example.com/loginamp;response_type=codeamp;state=QihbF4

   OAuth Error

   error="invalid_request", error_description="At least one redirect_uri must be registered with the client."
  

Для устранения неполадок я запустил новый проект, основанный на примере Spring Security 5.1.4.RELEASE «oauth2authorizationserver». Я применил функции, используемые на нашем сервере авторизации Spring Boot 1.5.13, убедившись, что модульные тесты пройдены (за исключением одного тестового класса). Если я @игнорирую неудачные тесты и развертываю код, я получаю проблему, описанную выше.

Проблема воспроизводима в тесте AuthenticationTests.loginSucceeds() JUnit, который прошел перед обновлением. Он ожидает 302, но теперь он получает 403, потому что он переходит в корень сервера аутентификации. Я опубликовал весь пример на GitHub spring-security-5-upgrade_sso-auth-server

Клонируйте проект и запустите модульные тесты, и вы увидите сбои.

Вот некоторые из ключевых настроек, которые можно найти в проекте на GitHub.

   public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

  private final String privateKey;

  private final String publicKey;

  private final AuthClientDetailsService authClientDetailsService;

  private final AuthenticationManager authenticationManager;

  private final AuthUserDetailsService authUserDetailsService;

  @Autowired
  public AuthServerConfig(
      @Value("${keyPair.privateKey}") final String privateKey,
      @Value("${keyPair.publicKey}") final String publicKey,
      final AuthClientDetailsService authClientDetailsService,
      final AuthUserDetailsService authUserDetailsService,
      final AuthenticationConfiguration authenticationConfiguration) throws Exception {
    this.privateKey = privateKey;
    this.publicKey = publicKey;
    this.authClientDetailsService = authClientDetailsService;
    this.authUserDetailsService = authUserDetailsService;
    this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
  }

  @Override
  public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
    clients.withClientDetails(authClientDetailsService);
  }

  @Override
  public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
    endpoints
        .authenticationManager(authenticationManager)
        .accessTokenConverter(accessTokenConverter())
        .userDetailsService(authUserDetailsService)
        .tokenStore(tokenStore());

  }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }


  @Bean
  public JwtAccessTokenConverter accessTokenConverter() {
    final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setSigningKey(privateKey);
    converter.setVerifierKey(publicKey);
    return converter;
  }

}

public class GlobalAuthenticationConfig extends GlobalAuthenticationConfigurerAdapter {

  private final AuthUserDetailsService authUserDetailsService;

  @Autowired
  public GlobalAuthenticationConfig(final AuthUserDetailsService authUserDetailsService) {
    this.authUserDetailsService = authUserDetailsService;
  }

  @Override
  public void init(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .userDetailsService(authUserDetailsService)
        .passwordEncoder(new BCryptPasswordEncoder());
  }
}

  @Configuration
  @Order(-20)
  protected class LoginConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

      // @formatter:off
      http
          .requestMatchers().antMatchers(LOGIN, "/oauth/authorize", "/oauth/confirm_access")
            .and()
            .logout().permitAll()
            .and()
         .authorizeRequests().anyRequest().authenticated()
            .and()
         .formLogin().loginPage(LOGIN).permitAll();
      // @formatter:on
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.parentAuthenticationManager(authenticationManager);
    }
  }

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  private final AuthUserDetailsService authUserDetailsService;

  @Autowired
  public WebSecurityConfig(AuthUserDetailsService authUserDetailsService) {
    this.authUserDetailsService = authUserDetailsService;
  }

  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .userDetailsService(authUserDetailsService)
        .passwordEncoder(new BCryptPasswordEncoder());
  }

}

  

Что еще нужно сделать в Spring Boot 2.1.3.RELEASE, чтобы перенаправить пользователя обратно на исходную веб-страницу?

Комментарии:

1. Стив, когда я запускаю этот тест, используя ваш образец, HTML-шаблон не заполняется, например, в скрытом поле ввода по-прежнему указано name="${_csrf.parameterName}" , например. Это означает, что токен csrf не извлекается. После обхода этого вручную я получаю исключение весенней сессии. На самом деле, мне также пришлось удалить com.medzero тестовую зависимость из pom, а также несколько тестовых классов в domain каталоге. Я делаю что-то не так с вашим образцом, чтобы воспроизвести ошибку, которую вы пытаетесь исправить?

2. Что касается «как зарегистрироваться» с помощью ClientDetailsService , зарегистрированный uri должен вернуться как часть вашего запроса к вашему consumerRepository . Обратите внимание, что он возвращает Consumer , который расширяется ClientDetails . Однако обязательно обратите внимание, не заходили ли вы уже в эту область.

3. @jzhaux Переменные шаблона csrf заполняются при запуске того же теста в версии 1.5.13.RELEASE, поэтому либо у меня проблема с настройкой ThymeLeaf, либо мне нужно настроить csrf в версии 2.1.3.RELEASE. Посмотрим, что я найду. Спасибо!

4. Стив, удалось ли вам добиться какого-либо прогресса в приведении проекта GitHub в состояние, которое воспроизводит вашу проблему? Я рад взглянуть еще раз. Я полагаю, что я также могу помочь вам решить проблему с кодировщиком паролей, о которой вы подробно рассказали в readme.

5. Я пошел дальше и добавил PR в ваш репозиторий, который исправляет задачу loginSucceeds. Я ничего не изменил относительно двух упомянутых вами проблем, что заставляет меня задуматься, как это связано. Можете ли вы помочь мне разобраться в соединении? github.com/smitchell/spring-security-5-upgrade_sso-auth-server /…

Ответ №1:

Важно, чтобы клиенты OAuth 2.0 регистрировали a redirect_uri на серверах авторизации в качестве средства предотвращения открытого перенаправления. Таким образом, Spring Boot 2.1.x имеет это поведение по умолчанию, поэтому вы видите ошибку.

Вы можете сделать одну из двух вещей:

Добавьте redirect_uri ы, по одному для каждого клиента

В идеале, вы бы обновили свои клиенты, чтобы у каждого был зарегистрированный redirect_uri , который, вероятно, был бы получен в реализации ClientDetailsService :

 public class MyClientDetailsService implements ClientDetailsService {
    private final MyRespository myRepository;

    public ClientDetails loadClientByClientId(String clientId) {
        return new MyClientDetails(this.myRepository.getMyDomainObject(clientId));
    }

    private static class MyClientDetails extends MyDomainObject implements ClientDetails {
        private final MyDomainObject mine;

        public MyClientDetails(MyDomainObject delegate) {
            this.delegate = delegate;
        }

        // implement ClientDetails methods, delegating to your domain object

        public Set<String> getRegisteredRedirectUri() {
            return this.delegate.getRedirectUris();
        }
    }
}
  

Эта настройка с подклассом private — хотя и не обязательна — приятна, потому что она не привязывает объект домена напрямую к Spring Security.

Добавьте пользовательский RedirectResolver

Или вы можете настроить RedirectResolver , хотя это не защитило бы от открытых перенаправлений, что и было первоначальной причиной изменения.

 public MyRedirectResolver implements RedirectResolver {
    private final RedirectResolver delegate = new DefaultRedirectResolver();

    public String resolveRedirect(String redirectUri, ClientDetails clientDetails) {
        try {
            return this.delegate.resolveRedirect(redirectUri, clientDetails);
        } catch ( InvalidRequestException ire ) {
            // do custom resolution
        }
    }
}
  

Комментарии:

1. Я попытался добавить свой собственный RedirectResolver но, похоже, у меня возникли некоторые проблемы с его регистрацией. Я получаю NoClassDefFoundError и ClassNotFoundException . Итак, мы комментируем MyRedirectResolver ? Кроме того, мы добавляем его как redirectResolver(customRedirectResolver()) в configure метод `Authorisationserverconfigration? Не могли бы вы указать мне пример репозитория или публикации, где это произошло? или суть? @jzhaux

2. Я понял это, у меня возникли некоторые проблемы с classpath.