Как горизонтально масштабировать подпружиненный сервер oauth2 с реализацией JDBC - PullRequest
0 голосов
/ 23 октября 2019

У меня есть сервер oauth2 с весенней загрузкой, который использует реализацию JDBC. Он настроен как сервер авторизации с @ EnableAuthorizationServer.

Я бы хотел масштабировать это приложение по горизонтали, но, похоже, оно не работает должным образом.

Я могу подключиться, только если у меня есть один экземпляр (модуль) сервера.

Я использую грант autorisation_code_client из другой клиентской службы для получения токена. Итак, сначала клиентская служба перенаправляет пользователя в форму сервера oauth2, затем, как только пользователь аутентифицирован, он должен быть перенаправлен на клиентскую службу с кодом, прикрепленным к URL, и, наконец, клиент использует этот код для запроса сервера oauth2. снова и получите токен.

Здесь пользователь вообще не перенаправляется, если у меня есть несколько экземпляров oauth2-сервера. С одним экземпляром он работает хорошо.

Когда я проверяю журнал двух экземпляров в режиме реального времени, я вижу, что аутентификация работает на одном из них. У меня нет конкретной ошибки, пользователь просто не перенаправлен.

Есть ли способ настроить oauth2-сервер, чтобы он не имел состояния, или другой способ исправить эту проблему?

Вот моя конфигурация, реализация AuthorizationServerConfigurerAdapter.

@Configuration
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {


    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource oauthDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Bean
    public JdbcClientDetailsService clientDetailsSrv() {
        return new JdbcClientDetailsService(oauthDataSource());
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(oauthDataSource());
    }

    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(oauthDataSource());
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(oauthDataSource());
    }

    @Bean
    public TokenEnhancer tokenEnhancer() {

        return new CustomTokenEnhancer();
    }

    @Bean
    @Primary
    public AuthorizationServerTokenServices tokenServices() {


        DefaultTokenServices tokenServices = new DefaultTokenServices();

        tokenServices.setTokenStore(tokenStore());

        tokenServices.setTokenEnhancer(tokenEnhancer());

        return tokenServices;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.withClientDetails(clientDetailsSrv());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer)  {

        oauthServer
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();

    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)  {
        endpoints
                .authenticationManager(authenticationManager)
                .approvalStore(approvalStore())
                //.approvalStoreDisabled()
                .authorizationCodeServices(authorizationCodeServices())
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancer());
    }

}

Основной класс

@SpringBootApplication
@EnableResourceServer
@EnableAuthorizationServer
@EnableConfigurationProperties
@EnableFeignClients("com.oauth2.proxies")
public class AuthorizationServerApplication {


    public static void main(String[] args) {

        SpringApplication.run(AuthorizationServerApplication.class, args);

    }

}

Конфигурация веб-безопасности

@Configuration
@Order(1)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {


    @Bean
    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return new JdbcUserDetails();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception { // @formatter:off

        http.requestMatchers()
                .antMatchers("/",
                        "/login",
                        "/login.do",
                        "/registration",
                        "/registration/confirm/**",
                        "/registration/resendToken",
                        "/password/forgot",
                        "/password/change",
                        "/password/change/**",
                        "/oauth/authorize**")
                .and()
                .authorizeRequests()//autorise les requetes
                .antMatchers(
                        "/",
                        "/login",
                        "/login.do",
                        "/registration",
                        "/registration/confirm/**",
                        "/registration/resendToken",
                        "/password/forgot",
                        "/password/change",
                        "/password/change/**")
                .permitAll()
                .and()
                .requiresChannel()
                .anyRequest()
                .requiresSecure()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/login.do")
                .usernameParameter("username")
                .passwordParameter("password")
                .and()
                .userDetailsService(userDetailsServiceBean());


    } // @formatter:on


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsServiceBean()).passwordEncoder(passwordEncoder());
    }


}

Клиентская сторона WebSecurityConfigurerAdapter

@EnableOAuth2Sso
@Configuration
public class UiSecurityConfig extends WebSecurityConfigurerAdapter {

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

        http.antMatcher("/**")
                .authorizeRequests()
                .antMatchers(
                        "/",
                        "/index.html",
                        "/login**",
                        "/logout**",
                        //resources
                        "/assets/**",
                        "/static/**",
                        "/*.ico",
                        "/*.js",
                        "/*.json").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().csrfTokenRepository(csrfTokenRepository())
                .and()
                .addFilterAfter(csrfHeaderFilter(), SessionManagementFilter.class);
    }

}

конфигурация oauth2свойства

oauth2-сервер - это имя службы (балансировщик нагрузки) в kubernetes, а также путь к серверу, поэтому он появляется дважды.

security:
    oauth2:
        client:
            clientId: **********
            clientSecret: *******
            accessTokenUri: https://oauth2-server/oauth2-server/oauth/token
            userAuthorizationUri: https://oauth2.mydomain.com/oauth2-server/oauth/authorize
        resource:
            userInfoUri: https://oauth2-server/oauth2-server/me

Здесь важная деталь, значение userAuthorizationUriэто адрес для доступа к oauth2-серверу снаружи кластера k8s. Служба клиента отправляет этот адрес обратно в ответ с кодом http 302, если пользователь не подключен и пытается получить доступ к пути / login службы клиента. затем пользователь перенаправляется на путь / login сервера oauth2.
https://oauth2.mydomain.com предназначается для контроллера входа Nginx, который обрабатывает перенаправление на службу балансировки нагрузки.

Ответы [ 2 ]

2 голосов
/ 23 октября 2019

По умолчанию используется в памяти TokenStore .

По умолчанию InMemoryTokenStore отлично подходит для одного сервера

Если вам нужно несколько модулей, вам, вероятно, следует выбрать JdbcTokenStore

JdbcTokenStore - это JDBC-версия той же вещи, которая хранит данные токена в реляционной базе данных. Используйте версию JDBC, если вы можете совместно использовать базу данных между серверами, либо масштабировать экземпляры одного и того же сервера , если имеется только один, или Серверы авторизации и ресурсов, если имеется несколько компонентов. Чтобы использовать JdbcTokenStore, вам нужен "spring-jdbc" на пути к классам.

Источник Spring Security: Руководство для разработчиков OAuth 2

1 голос
/ 24 октября 2019

Вот решение проблемы. Это совсем не проблема Spring, а неправильная конфигурация контроллера Nginx Ingress.

Процесс аутентификации выполняется в несколько этапов:

1 - пользователь нажимает кнопку входа в систему, которая предназначена для/ login путь клиент-сервер

2 - клиент-сервер, если пользователь еще не аутентифицирован, отправляет в браузер ответ с HTTP-кодом 302, чтобы перенаправить пользователя на oauth2-сервер,значение перенаправления состоит из значения свойства security.oauth2.client.userAuthorizationUri и URL-адреса перенаправления, который будет использоваться браузером пользователя, чтобы клиент-сервер мог получить токен, как толькоПользователь аутентифицирован. Этот URL выглядит следующим образом:

h*tps://oauth2.mydomain.com/oauth2-server/oauth/authorize?client_id=autorisation_code_client&redirect_uri=h*tps://www.mydomain.com/login&response_type=code&state=bSWtGx

3 - пользователь перенаправляется на предыдущий URL

4 - сервер oauth2 отправляет в браузер код http 302 с URL-адресом входа в систему:oauth2-сервер, h * tps: //oauth2.mydomain.com/oauth2-server/login

5 - пользователь отправляет свои учетные данные, и токен создается, если они верны.

6 - пользователь перенаправляется на тот же адрес, что и на втором шаге, и oauth-сервер добавляет информацию к значению redirect_uri

7 - пользователь перенаправляется на клиент-сервер. Часть перенаправления ответа выглядит следующим образом:

location: h*tps://www.mydomain.com/login?code=gnpZ0r&state=bSWtGx

8 - клиент-сервер связывается с oauth2-сервером и получает токен из кода и состояния, которое его аутентифицирует. Не имеет значения, отличается ли экземпляр сервера oauth2 от того, который используется пользователем для аутентификации. Здесь клиент-сервер использует значение security.oauth2.client.accessTokenUri для получения токена, это внутренний адрес службы балансировки нагрузки, который предназначен для серверных модулей oauth2, поэтому он не проходит через какой-либо контроллер Ingress.

Таким образом, на этапах с 3 по 6 пользователь должен обмениваться данными с тем же экземпляром oauth2-сервера через контроллер Ingress перед службой балансировки нагрузки.

Это возможно, настроив контроллер Nginx Ingress с помощью нескольких аннотаций:

"annotations": {
  ...
  "nginx.ingress.kubernetes.io/affinity": "cookie",
  "nginx.ingress.kubernetes.io/session-cookie-expires": "172800",
  "nginx.ingress.kubernetes.io/session-cookie-max-age": "172800",
  "nginx.ingress.kubernetes.io/session-cookie-name": "route"
}

Таким образом, мы гарантируем, что пользователь будет перенаправлен на те же pods / экземпляр oauth2-сервераво время процесса аутентификации, пока он идентифицируется с тем же cookie.

Механизм аффинного сеанса - отличный способ масштабировать сервер аутентификации, а также клиент-сервер. После аутентификации пользователя он всегда будет использовать один и тот же экземпляр клиента и хранить информацию о своем сеансе.

Спасибо Кристиану Альтамирано Айала за помощь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...