1. Обзор
Cloud Foundry User Account and Authentication (CF UAA) — это служба управления идентификацией и авторизации. Точнее, это провайдер OAuth 2.0, позволяющий выполнять аутентификацию и выдавать токены клиентским приложениям.
В этом уроке мы рассмотрим основы настройки сервера CF UAA. Затем мы рассмотрим, как использовать его для защиты приложений Resource Server.
Но прежде давайте проясним роль UAA в структуре авторизации OAuth 2.0 .
2. Cloud Foundry UAA и OAuth 2.0
Начнем с понимания того, как UAA соотносится со спецификацией OAuth 2.0.
Спецификация OAuth 2.0 определяет четырех участников , которые могут подключаться друг к другу: владелец ресурса, сервер ресурсов, клиент и сервер авторизации.
Как поставщик OAuth 2.0, UAA играет роль сервера авторизации.
Это означает , что его основной целью является выдача токенов доступа для клиентских
приложений и проверка этих токенов для серверов ресурсов
.
Чтобы разрешить взаимодействие этих участников, нам нужно сначала настроить сервер UAA, а затем реализовать еще два приложения: одно в качестве клиента, а другое в качестве сервера ресурсов.
Мы будем использовать поток предоставления авторизации_кода
с клиентом. И мы будем использовать авторизацию токена Bearer с сервером ресурсов. Для более безопасного и эффективного рукопожатия мы будем использовать подписанные JWT в качестве токенов доступа .
3. Настройка сервера UAA
Во- первых, мы установим UAA и заполним его демонстрационными данными.
После установки мы зарегистрируем клиентское приложение с именем webappclient.
Затем мы создадим пользователя с именем appuser
с двумя ролями, resource.read
и resource.write
.
3.1. Монтаж
UAA — это веб-приложение Java, которое можно запускать в любом совместимом контейнере сервлетов. В этом уроке мы будем использовать Tomcat .
Давайте продолжим и загрузим войну UAA и разместим ее в нашем развертывании Tomcat :
wget -O $CATALINA_HOME/webapps/uaa.war \
https://search.maven.org/remotecontent?filepath=org/cloudfoundry/identity/cloudfoundry-identity-uaa/4.27.0/cloudfoundry-identity-uaa-4.27.0.war
Однако, прежде чем мы запустим его, нам нужно настроить его источник данных и пару ключей JWS.
3.2. Требуемая конфигурация
По умолчанию UAA считывает конфигурацию из uaa.yml
в своем пути к классам. Но, поскольку мы только что загрузили военный
файл, нам будет лучше указать UAA пользовательское местоположение в нашей файловой системе.
Мы можем сделать это, установив свойство UAA_CONFIG_PATH
:
export UAA_CONFIG_PATH=~/.uaa
В качестве альтернативы мы можем установить CLOUD_FOUNDRY_CONFIG_PATH.
Или мы можем указать удаленное местоположение с помощью UAA_CONFIG_URL.
Затем мы можем скопировать требуемую конфигурацию UAA в наш путь конфигурации:
wget -qO- https://raw.githubusercontent.com/cloudfoundry/uaa/4.27.0/uaa/src/main/resources/required_configuration.yml \
> $UAA_CONFIG_PATH/uaa.yml
Обратите внимание, что мы удаляем последние три строки, потому что вскоре заменим их.
3.3. Настройка источника данных
Итак, настроим источник данных, в котором UAA будет хранить информацию о клиентах.
Для целей этого руководства мы будем использовать HSQLDB:
export SPRING_PROFILES="default,hsqldb"
Конечно, поскольку это приложение Spring Boot, мы также можем указать это в uaa.yml
как свойство spring.profiles
.
3.4. Настройка пары ключей JWS
Поскольку мы используем JWT, UAA должен иметь закрытый ключ для подписи каждого JWT, который выдает UAA.
OpenSSL делает это просто:
openssl genrsa -out signingkey.pem 2048
openssl rsa -in signingkey.pem -pubout -out verificationkey.pem
Сервер авторизации подпишет
JWT с помощью закрытого ключа, а наш клиент и сервер ресурсов проверят
эту подпись с помощью открытого ключа.
Мы экспортируем их в JWT_TOKEN_SIGNING_KEY
и JWT_TOKEN_VERIFICATION_KEY
:
export JWT_TOKEN_SIGNING_KEY=$(cat signingkey.pem)
export JWT_TOKEN_VERIFICATION_KEY=$(cat verificationkey.pem)
Опять же, мы могли бы указать их в uaa.yml
через свойства jwt.token.signing-key
и jwt.token.verification-key
.
3.5. Запуск УАА
Наконец, давайте начнем:
$CATALINA_HOME/bin/catalina.sh run
На данный момент у нас должен быть работающий сервер UAA, доступный по адресу http://localhost:8080/uaa
.
Если мы перейдем по адресу http://localhost:8080/uaa/info
, то увидим базовую информацию о запуске.
3.6. Установка клиента командной строки UAA
Клиент командной строки CF UAA является основным инструментом для администрирования UAA , но для его использования нам нужно сначала установить Ruby :
sudo apt install rubygems
gem install cf-uaac
Затем мы можем настроить uaac так
, чтобы он указывал на наш работающий экземпляр UAA:
uaac target http://localhost:8080/uaa
Обратите внимание: если мы не хотим использовать клиент командной строки, мы, конечно, можем использовать HTTP-клиент UAA.
3.7. Заполнение клиентов и пользователей с помощью UAAC
Теперь, когда у нас установлен uaac
, давайте заполним UAA некоторыми демонстрационными данными. Как минимум, нам понадобятся: клиент
, пользователь
и группы resource.read
и resource.write
.
Таким образом, для любого администрирования нам потребуется пройти аутентификацию самостоятельно. Мы выберем администратора по умолчанию, который поставляется с UAA и имеет разрешения на создание других клиентов, пользователей и групп:
uaac token client get admin -s adminsecret
(Конечно, нам обязательно нужно изменить эту учетную запись — через файл oauth-clients.xml — перед отправкой!)
По сути, мы можем прочитать эту команду как: «Дайте мне токен,
используя учетные данные клиента
с
client_id admin
и секретом adminsecret
».
Если все пойдет хорошо, мы увидим сообщение об успешном завершении:
Successfully fetched token via client credentials grant.
Токен хранится в состоянии uaac
.
Теперь, работая от имени администратора
, мы можем зарегистрировать клиента с именем webappclient
с добавлением клиента:
uaac client add webappclient -s webappclientsecret \
--name WebAppClient \
--scope resource.read,resource.write,openid,profile,email,address,phone \
--authorized_grant_types authorization_code,refresh_token,client_credentials,password \
--authorities uaa.resource \
--redirect_uri http://localhost:8081/login/oauth2/code/uaa
А также мы можем зарегистрировать пользователя с именем appuser
с добавлением пользователя:
uaac user add appuser -p appusersecret --emails appuser@acme.com
Далее мы добавим две группы — resource.read
и resource.write
— используя group add:
uaac group add resource.read
uaac group add resource.write
И, наконец, мы назначим эти группы для appuser
с добавлением участников:
uaac member add resource.read appuser
uaac member add resource.write appuser
Фу! Итак, что мы сделали до сих пор:
- Установил и настроил УАА
- Установлен
UAAC
- Добавлен демонстрационный клиент, пользователи и группы
Итак, давайте запомним эти фрагменты информации и перейдем к следующему шагу.
4. Клиент OAuth 2.0
В этом разделе мы будем использовать Spring Boot для создания клиентского приложения OAuth 2.0 .
4.1. Настройка приложения
Начнем с доступа к Spring Initializr и создания веб-приложения Spring Boot. Мы выбираем только компоненты Web
и OAuth2 Client
:
<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>
В этом примере мы использовали Spring Boot версии 2.1.3 .
Далее нам нужно зарегистрировать наш клиент webapp
client .
Проще говоря, нам нужно предоставить приложению client-id,
client-secret
и UAA issuer-uri
. Мы также укажем области действия OAuth 2.0, которые этот клиент хочет предоставить пользователю:
#registration
spring.security.oauth2.client.registration.uaa.client-id=webappclient
spring.security.oauth2.client.registration.uaa.client-secret=webappclientsecret
spring.security.oauth2.client.registration.uaa.scope=resource.read,resource.write,openid,profile
#provider
spring.security.oauth2.client.provider.uaa.issuer-uri=http://localhost:8080/uaa/oauth/token
Для получения дополнительной информации об этих свойствах мы можем взглянуть на документацию по Java для компонентов регистрации и поставщиков .
И поскольку мы уже используем порт 8080 для UAA, давайте запустим это на 8081:
server.port=8081
4.2. Авторизоваться
Теперь, если мы получим доступ к пути /login
, у нас должен быть список всех зарегистрированных клиентов. В нашем случае у нас есть только один зарегистрированный клиент:
Клик по ссылке перенаправит нас на страницу входа в UAA:
Здесь давайте войдем с помощью appuser/appusersecret
.
Отправка формы должна перенаправить нас на форму утверждения, где пользователь может разрешить или запретить доступ к нашему клиенту:
Затем пользователь может предоставить те привилегии, которые он хочет. Для наших целей мы выберем все , кроме resource:write.
Все, что проверит пользователь, будет областью действия в результирующем токене доступа.
Чтобы доказать это, мы можем скопировать токен, указанный в пути индекса, http://localhost:8081
, и декодировать его с помощью отладчика JWT . Мы должны увидеть области, которые мы проверили, на странице утверждения:
{
"jti": "f228d8d7486942089ff7b892c796d3ac",
"sub": "0e6101d8-d14b-49c5-8c33-fc12d8d1cc7d",
"scope": [
"resource.read",
"openid",
"profile"
],
"client_id": "webappclient"
// more claims
}
Как только наше клиентское приложение получит этот токен, оно сможет аутентифицировать пользователя, и он получит доступ к приложению.
Теперь приложение, которое не показывает никаких данных, не очень полезно, поэтому нашим следующим шагом будет запуск сервера ресурсов, на котором есть данные пользователя, и подключение к нему клиента.
Готовый сервер ресурсов будет иметь два защищенных API: для одного требуется область resource.read
, а для другого требуется resource.write.
Мы увидим, что клиент, используя предоставленные нами области действия, сможет вызывать API для чтения , но не для
записи.
5. Сервер ресурсов
На сервере ресурсов размещаются защищенные ресурсы пользователя.
Он аутентифицирует клиентов через заголовок Authorization
и консультируется с сервером авторизации — в нашем случае это UAA.
5.1. Настройка приложения
Чтобы создать наш сервер ресурсов, мы снова будем использовать Spring Initializr для создания веб-приложения Spring Boot. На этот раз мы выберем компоненты Web
и OAuth2 Resource Server
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Как и в случае с клиентским приложением, мы используем версию 2.1.3 Spring Boot.
Следующим шагом будет указание расположения работающего CF UAA в файле application.properties :
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/uaa/oauth/token
Конечно, давайте выберем и здесь новый порт. 8082 будет работать нормально:
server.port=8082
Вот и все! У нас должен быть работающий сервер ресурсов, и по умолчанию для всех запросов потребуется действительный токен доступа в заголовке авторизации .
5.2. Защита API-интерфейсов Resource Server
Затем давайте добавим некоторые конечные точки, которые стоит защитить.
Мы добавим RestController
с двумя конечными точками, одна авторизована для пользователей с областью resource.read
, а другая — для пользователей с областью resource.write:
@GetMapping("/read")
public String read(Principal principal) {
return "Hello write: " + principal.getName();
}
@GetMapping("/write")
public String write(Principal principal) {
return "Hello write: " + principal.getName();
}
Далее мы переопределим конфигурацию Spring Boot по умолчанию, чтобы защитить два ресурса:
@EnableWebSecurity
public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/read/**").hasAuthority("SCOPE_resource.read")
.antMatchers("/write/**").hasAuthority("SCOPE_resource.write")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
Обратите внимание, что области, предоставленные в токене доступа, имеют префикс SCOPE_
, когда они транслируются в Spring Security GrantedAuthority
.
5.3. Запрос защищенного ресурса от клиента
Из клиентского приложения мы будем вызывать два защищенных ресурса с помощью RestTemplate.
Перед выполнением запроса мы извлекаем токен доступа из контекста и добавляем его в заголовок Authorization
:
private String callResourceServer(OAuth2AuthenticationToken authenticationToken, String url) {
OAuth2AuthorizedClient oAuth2AuthorizedClient = this.authorizedClientService.
loadAuthorizedClient(authenticationToken.getAuthorizedClientRegistrationId(),
authenticationToken.getName());
OAuth2AccessToken oAuth2AccessToken = oAuth2AuthorizedClient.getAccessToken();
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + oAuth2AccessToken.getTokenValue());
// call resource endpoint
return response;
}
Однако обратите внимание, что мы можем удалить этот шаблон, если будем использовать WebClient
вместо RestTemplate
.
Затем мы добавим два вызова к конечным точкам сервера ресурсов:
@GetMapping("/read")
public String read(OAuth2AuthenticationToken authenticationToken) {
String url = remoteResourceServer + "/read";
return callResourceServer(authenticationToken, url);
}
@GetMapping("/write")
public String write(OAuth2AuthenticationToken authenticationToken) {
String url = remoteResourceServer + "/write";
return callResourceServer(authenticationToken, url);
}
Как и ожидалось, вызов API / read
будет успешным, но не вызов / write
. Статус HTTP 403 говорит нам, что пользователь не авторизован.
6. Заключение
В этой статье мы начали с краткого обзора OAuth 2.0, поскольку он является базовой основой для UAA, сервера авторизации OAuth 2.0. Затем мы настроили его для выдачи маркеров доступа для клиента и защиты приложения сервера ресурсов.
Полный исходный код примеров доступен на Github .