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 .