1. введение
OAuth – это открытый стандарт, описывающий процесс авторизации. Он может использоваться для авторизации доступа пользователей к API. Например, API REST может ограничить доступ только для зарегистрированных пользователей с соответствующей ролью.
Сервер авторизации OAuth отвечает за аутентификацию пользователей и выдачу маркеров доступа, содержащих пользовательские данные и соответствующие политики доступа.
В этом уроке мы реализуем простой сервер OAuth с помощью экспериментального модуля Spring Security OAuth Authorization Server .
В процессе мы создадим клиент-серверное приложение, которое будет извлекать список статей Baeldung из REST API. Как для клиентских, так и для серверных служб потребуется проверка подлинности OAuth.
2. Реализация Сервера Авторизации
Давайте начнем с рассмотрения конфигурации сервера авторизации OAuth. Он будет служить источником аутентификации как для ресурса статьи, так и для клиентских серверов.
2.1. Зависимости
Во-первых, нам нужно будет добавить несколько зависимостей к вашему pom.xml файл:
org.springframework.boot spring-boot-starter-web 2.4.3 org.springframework.boot spring-boot-starter-security 2.4.3 org.springframework.security.experimental spring-security-oauth2-authorization-server 0.1.0
2.2. Конфигурация
Теперь давайте настроим порт, на котором будет работать наш сервер аутентификации, установив свойство server.port в файле application.yml :
server: port: 9000
После этого мы можем перейти к конфигурации Spring beans. Во-первых, нам понадобится класс @Configuration и импорт конфигурации сервера авторизации OAuth . Внутри класса конфигурации мы создадим несколько компонентов, специфичных для OAuth. Первым из них будет репозиторий клиентских сервисов. В нашем примере у нас будет один клиент, созданный с помощью класса Registered Client builder:
@Configuration @Import(OAuth2AuthorizationServerConfiguration.class) public class AuthorizationServerConfig { @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("articles-client") .clientSecret("secret") .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .redirectUri("http://localhost:8080/login/oauth2/code/articles-client-oidc") .redirectUri("http://localhost:8080/authorized") .scope(OidcScopes.OPENID) .scope("articles.read") .build(); return new InMemoryRegisteredClientRepository(registeredClient); } }
Свойства, которые мы настраиваем, следующие:
- Идентификатор клиента – Spring будет использовать его для определения того, какой клиент пытается получить доступ к ресурсу
- Секретный код клиента – секрет, известный клиенту и серверу, который обеспечивает доверие между ними
- Метод аутентификации – в нашем случае мы будем использовать базовую аутентификацию, которая представляет собой просто имя пользователя и пароль
- Тип предоставления авторизации – мы хотим, чтобы клиент мог генерировать как код авторизации, так и токен обновления
- URI перенаправления – клиент будет использовать его в потоке на основе перенаправления
- Область действия – этот параметр определяет разрешения, которые могут быть у клиента. В нашем случае мы получим необходимые OidcScopes.OPENID и наши пользовательские статьи. читать
Каждый сервер авторизации нуждается в своем ключе подписи для токенов, чтобы поддерживать надлежащую границу между доменами безопасности. Давайте сгенерируем 2048-байтовый ключ RSA:
@Bean public JWKSourcejwkSource() { RSAKey rsaKey = generateRsa(); JWKSet jwkSet = new JWKSet(rsaKey); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } private static RSAKey generateRsa() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); return new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); } private static KeyPair generateRsaKey() { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); return keyPairGenerator.generateKeyPair(); }
За исключением ключа подписи, каждый сервер авторизации также должен иметь уникальный URL-адрес эмитента. Мы установим его как http://127.0.0.1 на порту 9000 создав Настройки поставщика bean:
@Bean public ProviderSettings providerSettings() { return new ProviderSettings().issuer("http://127.0.0.1:9000"); }
Наконец, мы включим модуль Spring web security с помощью @EnableWebSecurity аннотированного класса конфигурации:
@EnableWebSecurity public class DefaultSecurityConfig { @Bean SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated() ) .formLogin(withDefaults()); return http.build(); } // ... }
Здесь мы вызываем authorizeRequests.anyRequest().authenticated () , чтобы потребовать аутентификации для всех запросов, и мы предоставляем аутентификацию на основе формы, вызывая метод formLogin(defaults ()) .
Кроме того, мы определим набор примеров пользователей, которые мы будем использовать для тестирования. Для этого примера давайте создадим репозиторий только с одним пользователем-администратором:
@Bean UserDetailsService users() { UserDetails user = User.withDefaultPasswordEncoder() .username("admin") .password("password") .build(); return new InMemoryUserDetailsManager(user); }
3. Сервер ресурсов
Теперь мы создадим сервер ресурсов, который будет возвращать список статей из точки доступа. Конечные точки должны разрешать только запросы, аутентифицированные на нашем сервере OAuth.
3.1. Зависимости
Во-первых, давайте включим необходимые зависимости:
org.springframework.boot spring-boot-starter-web 2.4.3 org.springframework.boot spring-boot-starter-security 2.4.3 org.springframework.boot spring-boot-starter-oauth2-resource-server 2.4.3
3.2. Конфигурация
Прежде чем мы начнем с кода реализации, мы должны настроить некоторые свойства в файле application.yml . Первый – это порт сервера:
server: port: 8090
Далее, пришло время для настройки безопасности. Нам нужно настроить правильный URL-адрес для нашего сервера аутентификации с хостом и портом, который мы настроили в ProviderSettings bean ранее:
spring: security: oauth2: resourceserver: jwt: issuer-uri: http://127.0.0.1:9000
Теперь мы можем настроить нашу конфигурацию веб-безопасности. Опять же, мы хотим прямо сказать, что каждый запрос на ресурсы статей должен быть авторизован и иметь надлежащий articles.читать авторитет:
@EnableWebSecurity public class ResourceServerConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.mvcMatcher("/articles/**") .authorizeRequests() .mvcMatchers("/articles/**").access("hasAuthority('SCOPE_articles.read')") .and() .oauth2ResourceServer() .jwt(); return http.build(); } }
Как показано здесь, мы также вызываем метод oauth2 ResourceServer () , который настроит соединение с сервером OAuth на основе конфигурации application.yml .
3.3. Контроллер изделий
Наконец, мы создадим контроллер REST, который будет возвращать список статей в конечной точке GET/articles :
@RestController public class ArticlesController { @GetMapping("/articles") public String[] getArticles() { return new String[] { "Article 1", "Article 2", "Article 3" }; } }
4. Клиент API
В последней части мы создадим клиент REST API, который будет получать список статей с сервера ресурсов.
4.1. Зависимости
Для начала давайте включим необходимые зависимости:
org.springframework.boot spring-boot-starter-web 2.4.3 org.springframework.boot spring-boot-starter-security 2.4.3 org.springframework.boot spring-boot-starter-oauth2-client 2.4.3 org.springframework spring-webflux 5.3.4 io.projectreactor.netty reactor-netty 1.0.4
4.2. Конфигурация
Как и ранее, мы определим некоторые свойства конфигурации для целей аутентификации:
server: port: 8080 spring: security: oauth2: client: registration: articles-client-oidc: provider: spring client-id: articles-client client-secret: secret authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" scope: openid client-name: articles-client-oidc articles-client-authorization-code: provider: spring client-id: articles-client client-secret: secret authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/authorized" scope: articles.read client-name: articles-client-authorization-code provider: spring: issuer-uri: http://127.0.0.1:9000
Теперь давайте создадим экземпляр WebClient для выполнения HTTP-запросов к нашему серверу ресурсов. Мы будем использовать стандартную реализацию с одним добавлением фильтра авторизации OAuth:
@Bean WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); return WebClient.builder() .apply(oauth2Client.oauth2Configuration()) .build(); }
Веб-клиент требует Авторизованный клиентский менеджер OAuth2 как зависимость. Давайте создадим реализацию по умолчанию:
@Bean OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() .refreshToken() .build(); DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; }
Наконец, мы настроим веб-безопасность:
@EnableWebSecurity public class SecurityConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated() ) .oauth2Login(oauth2Login -> oauth2Login.loginPage("/oauth2/authorization/articles-client-oidc")) .oauth2Client(withDefaults()); return http.build(); } }
Здесь, как и на других серверах, нам потребуется аутентификация каждого запроса. Кроме того, нам необходимо настроить URL-адрес страницы входа в систему (определенный в .yml config) и клиент OAuth.
4.3. Статьи Клиентский контроллер
Наконец, мы можем создать контроллер доступа к данным. Мы будем использовать ранее настроенный Веб-клиент для отправки HTTP-запроса на наш сервер ресурсов:
@RestController public class ArticlesController { private WebClient webClient; @GetMapping(value = "/articles") public String[] getArticles( @RegisteredOAuth2AuthorizedClient("articles-client-authorization-code") OAuth2AuthorizedClient authorizedClient ) { return this.webClient .get() .uri("http://localhost:8090/articles") .attributes(oauth2AuthorizedClient(authorizedClient)) .retrieve() .bodyToMono(String[].class) .block(); } }
В приведенном выше примере мы берем маркер авторизации OAuth из запроса в виде Авторизованный клиент OAuth2 класс. Он автоматически привязывается к Spring с помощью аннотации @RegisterdOAuth2AuthorizedClient с надлежащей идентификацией. В нашем случае он извлекается из article-client-authorization-code , который мы настроили ранее в файле .yml .
Этот маркер авторизации далее передается в HTTP-запрос.
4.4. Доступ к Списку статей
Теперь, когда мы заходим в браузер и пытаемся получить доступ к http://localhost:8080/articles страница, будет автоматически перенаправлена на страницу входа в систему сервера OAuth в разделе http://127.0.0.1:9000/login URL:
После предоставления правильного имени пользователя и пароля сервер авторизации перенаправит нас обратно на запрошенный URL – адрес-список статей.
Дальнейшие запросы к конечной точке статьи не потребуют входа в систему, так как маркер доступа будет храниться в файле cookie.
5. Заключение
В этой статье мы узнали, как настроить, настроить и использовать сервер авторизации Spring Security OAuth.
Как всегда, весь исходный код доступен на GitHub .