«1. Обзор
В этом руководстве мы обсудим, как реализовать SSO — единый вход — с использованием Spring Security OAuth и Spring Boot, используя Keycloak в качестве сервера авторизации.
Мы будем использовать 4 отдельных приложения:
-
Сервер авторизации — центральный механизм аутентификации Сервер ресурсов — поставщик Foos Два клиентских приложения — приложения, использующие SSO
Очень проще говоря, когда пользователь пытается получить доступ к ресурсу через одно клиентское приложение, он сначала будет перенаправлен для аутентификации через сервер авторизации. Keycloak выполнит вход пользователя в систему, и, пока он все еще находится в первом приложении, если доступ ко второму клиентскому приложению осуществляется с помощью того же браузера, пользователю не нужно будет снова вводить свои учетные данные.
Мы собираемся использовать тип предоставления кода авторизации из OAuth2 для делегирования аутентификации.
Мы будем использовать стек OAuth в Spring Security 5. Если вы хотите использовать устаревший стек Spring Security OAuth, ознакомьтесь с этой предыдущей статьей: Simple Single Sign-On with Spring Security OAuth2 (устаревший стек) ~~ ~ Согласно руководству по миграции:
Хорошо, давайте сразу приступим.
Spring Security refers to this feature as OAuth 2.0 Login while Spring Security OAuth refers to it as SSO
2. Сервер авторизации
Ранее стек Spring Security OAuth предлагал возможность настроить сервер авторизации в качестве приложения Spring.
Однако стек OAuth устарел в Spring, и теперь мы будем использовать Keycloak в качестве нашего сервера авторизации.
Итак, на этот раз мы настроим наш сервер авторизации как встроенный сервер Keycloak в приложении Spring Boot.
В нашей предварительной конфигурации мы определим двух клиентов, ssoClient-1 и ssoClient-2, по одному для каждого клиентского приложения.
3. Сервер ресурсов
Далее нам нужен сервер ресурсов или REST API, который предоставит нам Foos, которые будет потреблять наше клиентское приложение.
По сути, это то же самое, что мы использовали для наших клиентских приложений Angular ранее.
4. Клиентские приложения
Теперь давайте посмотрим на наше клиентское приложение Thymeleaf; мы, конечно же, будем использовать Spring Boot, чтобы минимизировать конфигурацию.
Имейте в виду, что нам понадобится 2 из них, чтобы продемонстрировать функциональность единого входа.
4.1. Зависимости Maven
Во-первых, нам понадобятся следующие зависимости в нашем pom.xml:
Чтобы включить всю поддержку клиента, которая нам потребуется, включая безопасность, нам просто нужно добавить spring-boot-starter- oauth2-клиент. Кроме того, поскольку старый RestTemplate будет объявлен устаревшим, мы будем использовать WebClient, поэтому мы добавили spring-webflux и Reactor-Netty.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
4.2. Конфигурация безопасности
Следующая, самая важная часть, конфигурация безопасности нашего первого клиентского приложения:
Основной частью этой конфигурации является метод oauth2Login(), который используется для включения Spring Security. Поддержка входа OAuth 2.0. Поскольку мы используем Keycloak, который по умолчанию является решением единого входа для веб-приложений и веб-служб RESTful, нам не нужно добавлять какую-либо дополнительную конфигурацию для единого входа.
@EnableWebSecurity
public class UiSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/")
.permitAll()
.anyRequest()
.authenticated()
.and()
.oauth2Login();
}
@Bean
WebClient webClient(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository,
authorizedClientRepository);
oauth2.setDefaultOAuth2AuthorizedClient(true);
return WebClient.builder().apply(oauth2.oauth2Configuration()).build();
}
}
Наконец, мы также определили bean-компонент WebClient, который действует как простой HTTP-клиент для обработки запросов, отправляемых на наш сервер ресурсов.
А вот и application.yml:
Здесь spring.security.oauth2.client.registration — это корневое пространство имен для регистрации клиента. Мы определили клиента с пользовательским регистрационным идентификатором. Затем мы определили его client-id, client-secret, scope, author-grant-type и redirect-uri, которые, конечно же, должны быть такими же, как и для нашего сервера авторизации.
spring:
security:
oauth2:
client:
registration:
custom:
client-id: ssoClient-1
client-secret: ssoClientSecret-1
scope: read,write
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8082/ui-one/login/oauth2/code/custom
provider:
custom:
authorization-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/auth
token-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/token
user-info-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/userinfo
user-name-attribute: preferred_username
thymeleaf:
cache: false
server:
port: 8082
servlet:
context-path: /ui-one
resourceserver:
api:
project:
url: http://localhost:8081/sso-resource-server/api/foos/
После этого мы определили нашего поставщика услуг или сервер авторизации, снова с тем же идентификатором custom, и перечислили его разные URI для использования Spring Security. Это все, что нам нужно определить, и фреймворк без проблем выполняет весь процесс входа в систему, включая перенаправление на Keycloak.
Также обратите внимание, что в нашем примере мы развернули свой сервер авторизации, но, конечно, мы также можем использовать других сторонних поставщиков, таких как Facebook или GitHub.
4.3. Контроллер
«Теперь давайте реализуем наш контроллер в клиентском приложении, чтобы запрашивать Foos у нашего сервера ресурсов:
Как мы видим, у нас есть только один метод, который передает ресурсы шаблону foos. Нам не нужно было добавлять какой-либо код для входа.
@Controller
public class FooClientController {
@Value("${resourceserver.api.url}")
private String fooApiUrl;
@Autowired
private WebClient webClient;
@GetMapping("/foos")
public String getFoos(Model model) {
List<FooModel> foos = this.webClient.get()
.uri(fooApiUrl)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<FooModel>>() {
})
.block();
model.addAttribute("foos", foos);
return "foos";
}
}
4.4. Внешний интерфейс
Теперь давайте взглянем на внешний вид нашего клиентского приложения. Мы не собираемся сосредотачиваться на этом здесь, в основном потому, что мы уже рассмотрели на сайте.
Наше клиентское приложение имеет очень простой интерфейс; вот index.html:
И foos.html:
<a class="navbar-brand" th:href="@{/foos/}">Spring OAuth Client Thymeleaf - 1</a>
<label>Welcome !</label> <br /> <a th:href="@{/foos/}">Login</a>
Страница foos.html требует аутентификации пользователей. Если пользователь, не прошедший проверку подлинности, попытается получить доступ к foos.html, он сначала будет перенаправлен на страницу входа в Keycloak.
<a class="navbar-brand" th:href="@{/foos/}">Spring OAuth Client Thymeleaf -1</a>
Hi, <span sec:authentication="name">preferred_username</span>
<h1>All Foos:</h1>
<table>
<thead>
<tr>
<td>ID</td>
<td>Name</td>
</tr>
</thead>
<tbody>
<tr th:if="${foos.empty}">
<td colspan="4">No foos</td>
</tr>
<tr th:each="foo : ${foos}">
<td><span th:text="${foo.id}"> ID </span></td>
<td><span th:text="${foo.name}"> Name </span></td>
</tr>
</tbody>
</table>
4.5. Второе клиентское приложение
Мы настроим второе приложение, Spring OAuth Client Thymeleaf -2, используя другой client_id ssoClient-2.
В основном это будет то же самое, что и первое приложение, которое мы только что описали.
Файл application.yml будет отличаться тем, что в файле spring.security.oauth2.client.registration будет указан другой client_id, client_secret и redirect_uri:
И, конечно же, нам нужен другой порт сервера для это также, чтобы мы могли запускать их параллельно:
spring:
security:
oauth2:
client:
registration:
custom:
client-id: ssoClient-2
client-secret: ssoClientSecret-2
scope: read,write
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8084/ui-two/login/oauth2/code/custom
Наконец, мы настроим интерфейсные HTML-коды, чтобы иметь заголовок Spring OAuth Client Thymeleaf — 2 вместо — 1, чтобы мы можем различать их.
server:
port: 8084
servlet:
context-path: /ui-two
5. Тестирование поведения единого входа
Чтобы протестировать поведение единого входа, давайте запустим наши приложения.
Для этого нам потребуются все 4 наших загрузочных приложения — сервер авторизации, сервер ресурсов и оба клиентских приложения.
Теперь давайте откроем браузер, скажем, Chrome, и войдем в Client-1, используя учетные данные [email protected]/123. Затем в другом окне или вкладке нажмите URL-адрес Client-2. Нажав кнопку входа, мы сразу же будем перенаправлены на страницу Foos, минуя этап аутентификации.
Точно так же, если пользователь сначала входит в Клиент-2, ему не нужно вводить свое имя пользователя/пароль для Клиента-1.
6. Заключение
В этом руководстве мы сосредоточились на реализации единого входа с использованием Spring Security OAuth2 и Spring Boot с использованием Keycloak в качестве поставщика удостоверений.
Как всегда, полный исходный код можно найти на GitHub.
«