«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. Запуск UAA

Наконец, давайте приступим:

$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.

uaac token client get admin -s adminsecret

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

(Конечно, нам определенно нужно изменить эту учетную запись — через oauth-clients .xml — перед отправкой!)

По сути, мы можем прочитать эту команду как: «Дайте мне токен, используя учетные данные клиента с client_id admin и секретом adminsecret».

Successfully fetched token via client credentials grant.

Если все пойдет хорошо, мы увидим сообщение об успешном завершении:

Токен сохранен в состоянии uaac.

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

Теперь, работая от имени администратора, мы можем зарегистрировать клиента с именем webappclient с помощью client add:

uaac user add appuser -p appusersecret --emails [email protected]

А также мы можем зарегистрировать пользователя с именем appuser с помощью user add:

uaac group add resource.read
uaac group add resource.write

Далее, мы добавим две группы — resource.read и resource.write — используя с добавлением группы:

uaac member add resource.read appuser
uaac member add resource.write appuser

И, наконец, мы назначим эти группы для appuser с помощью добавления члена:

    Фух ! Итак, что мы сделали на данный момент:

Установлен и настроен UAA Установлен uaac Добавлен демо-клиент, пользователи и группы

Итак, давайте запомним эту информацию и перейдем к следующему шагу.

4. Клиент OAuth 2.0

В этом разделе мы будем использовать Spring Boot для создания клиентского приложения OAuth 2.0.

4.1. Настройка приложения

<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 Initializr и создания веб-приложения Spring Boot. Мы выбираем только компоненты Web и OAuth2 Client:


В этом примере мы использовали версию 2.1.3 Spring Boot.

Далее нам нужно зарегистрировать наш клиент webappclient.

#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

Проще говоря, нам нужно указать приложению client-id, client-secret и UAA issuer-uri. Мы также укажем области OAuth 2.0, которые этот клиент хочет, чтобы пользователь ему предоставил:

Для получения дополнительной информации об этих свойствах мы можем взглянуть на документацию Java для компонентов регистрации и поставщика.

server.port=8081

И поскольку мы уже используем порт 8080 для UAA, давайте запустим это на 8081:

4.2. Логин

Теперь, если мы получим доступ к пути /login, у нас должен быть список всех зарегистрированных клиентов. В нашем случае у нас есть только один зарегистрированный клиент:

Щелчок по ссылке перенаправит нас на страницу входа в UAA:

Здесь давайте войдем с помощью appuser/appusersecret.

Отправка формы должна перенаправить нас на форму утверждения, где пользователь может разрешить или запретить доступ к нашему клиенту:

Затем пользователь может предоставить те привилегии, которые он хочет. Для наших целей мы выберем все, кроме resource:write.

Все, что проверит пользователь, будет областью действия в результирующем токене доступа.

{
  "jti": "f228d8d7486942089ff7b892c796d3ac",
  "sub": "0e6101d8-d14b-49c5-8c33-fc12d8d1cc7d",
  "scope": [
    "resource.read",
    "openid",
    "profile"
  ],
  "client_id": "webappclient"
  // more claims
}

Чтобы доказать это, мы можем скопировать токен, указанный в пути индекса, http://localhost:8081, и декодировать его с помощью отладчика JWT. Мы должны увидеть области, которые мы проверили, на странице утверждения:

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

Теперь приложение, которое не показывает никаких данных, не очень полезно, поэтому нашим следующим шагом будет запуск сервера ресурсов, на котором есть данные пользователя, и подключение к нему клиента.

Готовый сервер ресурсов будет иметь два защищенных API: для одного требуется область resource.read, а для другого требуется resource.write.

Мы увидим, что клиент, используя предоставленные нами области видимости, сможет вызывать API для чтения, но не для записи.

5. Сервер ресурсов

На сервере ресурсов размещаются защищенные ресурсы пользователя.

Он аутентифицирует клиентов через заголовок Authorization и консультируется с сервером авторизации — в нашем случае это UAA.

5.1. Настройка приложения

<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>

Чтобы создать наш сервер ресурсов, мы снова будем использовать Spring Initializr для создания веб-приложения Spring Boot. На этот раз мы выберем компоненты Web и OAuth2 Resource Server:

«

spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/uaa/oauth/token

Как и в случае с клиентским приложением, мы используем версию 2.1.3 Spring Boot.

server.port=8082

Следующим шагом является указание местоположения запущенного CF UAA в файле application.properties:

Конечно, давайте выберем и здесь новый порт. 8082 будет работать нормально:

И все! У нас должен быть работающий сервер ресурсов, и по умолчанию для всех запросов потребуется действительный токен доступа в заголовке авторизации.

5.2. Защита API Resource Server

@GetMapping("/read")
public String read(Principal principal) {
    return "Hello write: " + principal.getName();
}

@GetMapping("/write")
public String write(Principal principal) {
    return "Hello write: " + principal.getName();
}

Далее давайте добавим некоторые конечные точки, которые стоит защитить.

@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();
   }
}

Мы добавим RestController с двумя конечными точками, одна из которых авторизована для пользователей с областью действия resource.read, а другая — для пользователей с областью действия resource.write:

Далее мы переопределим Spring по умолчанию. Конфигурация загрузки для защиты двух ресурсов:

Обратите внимание, что области, предоставленные в токене доступа, имеют префикс SCOPE_, когда они транслируются в Spring Security GrantedAuthority.

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;
}

5.3. Запрос защищенного ресурса от клиента

Из клиентского приложения мы будем вызывать два защищенных ресурса с помощью RestTemplate. Перед выполнением запроса мы извлекаем токен доступа из контекста и добавляем его в заголовок Authorization:

@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);
}

Обратите внимание, что мы можем удалить этот шаблон, если будем использовать WebClient вместо RestTemplate.

Затем мы добавим два вызова к конечным точкам сервера ресурсов:

Как и ожидалось, вызов /read API будет успешным, но не /write. Статус HTTP 403 говорит нам, что пользователь не авторизован.

6. Заключение