Статьи

Защитите свой сервер Vert.x с помощью единого входа в Okta

«Я люблю писать код аутентификации и авторизации». Нет Java-разработчика. Надоело строить одни и те же экраны входа снова и снова? Попробуйте API Okta для размещенной аутентификации, авторизации и многофакторной аутентификации.

Vert.x является одним из самых быстрорастущих элементов в экосистеме Spring, и обеспечение безопасности сервера Vert.x может быть сложной задачей. Развертывание Okta позволяет вам добавить безопасный единый вход на ваш сервер и одновременно предоставить вам доступ к большому количеству информации о ваших пользователях. Из этого туториала вы узнаете, как подготовить новый сервер Vert.x и интегрировать его с Okta для безопасного управления пользователями.

Краткий обзор 3-х стороннего потока OAuth

В этом примере будет использоваться поток OAuth стороннего поставщика, известный как трехсторонний поток (основа единого входа). Перед запуском в код может быть полезно краткое обновление этого процесса.

Эта проблема

Я веб-сервер, и я хочу защитить свой сайт и требовать от пользователей входа в систему. Однако написание, поддержание и управление идентификационными данными пользователей — это большая работа.

Решение

Пусть кто-то еще с этим справится. Чтобы добиться этого, требуется небольшое сотрудничество между мной (веб-сервером), моим пользователем (возможно, в веб-браузере) и тем, кто обрабатывает авторизацию для меня (в данном примере Okta). Эти три сотрудничающих участника являются тремя «ножками» трехстороннего потока OAuth. Процесс, через который они проходят, является трехсторонним рукопожатием:

  1. Браузер пользователя запрашивает у меня защищенный ресурс, веб-сервер
  2. Я, веб-сервер, решаю, что пользователь должен войти в систему первым. Я отказываюсь обслуживать запрос и вместо этого возвращаю ответ перенаправления 302, который говорит браузеру вместо этого посетить Okta
  3. Браузер соответствует, вместо этого посещает Okta, и пользователь входит в систему. Затем Okta возвращает собственное перенаправление 302, сообщая браузеру, что нужно вернуться и посетить меня снова … но на этот раз с секретным кодом
  4. Браузер посещает меня еще раз, но на этот раз я вижу, что он несет с собой этот секретный код. Теперь я делаю свой прямой звонок в Okta, обмениваясь этим секретным кодом для получения конфиденциальной информации о пользователе, такой как его имя, адрес электронной почты или номер телефона.

Вышеуказанное рукопожатие — это то, что будет происходить за кулисами после завершения этого примера. Vert.x предоставляет удобную библиотеку OAuth, чтобы позаботиться обо всем этом процессе для вас — все, что вам нужно сделать, это настроить и зарегистрировать его соответствующим образом. Это то, что демонстрирует этот урок.

В качестве руководства для начинающих этот пост предполагает только базовое знакомство с Java и некоторыми основами Java, такими как Maven. Если у вас уже работает сервер Vert.x, не стесняйтесь переходить к хорошему: вы интегрируетесь с Okta в разделе « Настройка обработчика OAuth Vert.x ».

Завершенный пример кода, включая import и pom.xml, можно найти на Github .

Запустить новый сервер Vert.x

Чтобы начать, посетите страницу Vert.x Starter и создайте новый проект. В этом руководстве вы можете оставить все значения по умолчанию и включить Vert.x Web , OAuth и Vert.x Config :

После нажатия кнопки «Создать, загрузить и разархивировать локально» вы должны увидеть следующую простую структуру каталогов:

При запуске с mvn compile exec:java из каталога demo следует запустить сервер на порту 8080:

Посетив http://localhost:8080 в вашем браузере, вы получите обнадеживающий ответ:

На этом этапе правильным следующим шагом будет переключение на https перед продолжением добавления аутентификации. Однако, чтобы сохранить краткость и направленность учебника, эта часть будет пропущена, и в примерах будет по-прежнему использоваться режим незашифрованного http-сервера.

Внесите конфигурацию Vert.x

Vert.x предлагает универсальную конфигурационную библиотеку, хотя ее настройка немного больше, чем в Spring. В этом примере будет использовано несколько значений конфигурации, поэтому вы можете воспользоваться этой возможностью, чтобы добавить Vert.x Config в ваш проект. Зависимость уже должна присутствовать в вашем pom.xml если вы указали Vert.x Config при создании начального проекта.

<dependency>
   <groupId>io.vertx</groupId>
   <artifactId>vertx-config</artifactId>
   <version>${vertx.version}</version>
</dependency>

Создайте файл с именем src/main/application.json и добавьте следующее содержимое:

{
   "clientId": "{okta-client-id}",
   "clientSecret": "{okta-client-secret}",
   "issuer": "https://{yourOktaDomain}/oauth2/default",
   "callbackUrl": "http://localhost:8080/login",
   "port": 8080
}

Вы будете обновлять эти значения в ближайшее время. Теперь в src/main/java/com/example/demo/MainVerticle.java замените содержимое метода start() следующим, который загрузит конфигурацию. Обратите внимание, что по завершении он вызывает метод startServer() который еще не существует. Вы добавите это в следующем разделе.

@Override
public void start() throws Exception {
   ConfigStoreOptions fileStore = new ConfigStoreOptions()
       .setType("file")
       .setConfig(new JsonObject().put("path", "src/main/application.json"));
   ConfigRetrieverOptions options = new ConfigRetrieverOptions()
       .addStore(fileStore);
   ConfigRetriever retriever = ConfigRetriever.create(vertx, options);
   retriever.getConfig(ar -> {
       if (ar.failed()) {
           System.err.println("failed to retrieve config.");
       } else {
           config().mergeIn(ar.result());
           startServer();
       }
   });
}

Включите маршрутизатор Vert.x

Используя маршрутизатор Vert.x , вы сможете легко перехватывать вызовы к чувствительным конечным точкам и применять предварительную аутентификацию. Чтобы сделать это, вы теперь реализуете метод startServer() в src/main/java/com/example/demo/MainVerticle.java :

void startServer() {
 
    Router router = Router.router(vertx);
 
    router.route("/private/secret")
        .handler(ctx -> {
        ctx.response().end("Hi");
    });
 
    vertx.createHttpServer()
        .requestHandler(router::accept)
        .listen(config().getInteger("port"));
}

В приведенном выше примере вы создали новую конечную точку в /private/secret с целью скорейшей защиты каждой конечной точки по пути /private . Но сначала нужно настроить обработчик OAuth Vert.x.

Создать учетную запись Okta и собрать учетные данные

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

  • Идентификатор клиента — например: oot9wrjjararhfaa
  • Секрет клиента — (держите это в секрете!)
  • Эмитент — например: https://dev-123123.oktapreview.com/oauth2/default… обязательно укажите путь / oauth2 / default !
  • URL обратного вызова — это будет http: // localhost: 8080 / login, если вы следовали инструкциям выше.

Эти значения теперь можно использовать в вашем файле src/main/application.json .

Настройте обработчик OAuth Vert.x

Vert.x поставляется с готовым OAuth-менеджером, который прекрасно интегрируется с Okta в качестве провайдера идентификации. Чтобы сохранить порядок, вы создадите отдельный фабричный метод в src/main/java/com/example/demo/MainVerticle.java который создает настроенный обработчик OAuth. Добавьте следующее к классу MainVerticle , заменив приведенную ниже информацию о клиенте на данные своей учетной записи, полученные из панели инструментов разработчика Okta:

AuthHandler getOAuthHandler(Router router) {
    OAuth2Auth oauth2 = OAuth2Auth.create(vertx, OAuth2FlowType.AUTH_CODE, new OAuth2ClientOptions()
        .setClientID(config().getString("clientId"))
        .setClientSecret(config().getString("clientSecret"))
        .setSite(config().getString("issuer"))
        .setTokenPath("/v1/token")
        .setAuthorizationPath("/v1/authorize")
        .setUserInfoPath("/v1/userinfo")
        .setUseBasicAuthorizationHeader(false)
    );
 
    OAuth2AuthHandler authHandler = OAuth2AuthHandler.create(oauth2, config().getString("callbackUrl"));
    authHandler.extraParams(new JsonObject("{\"scope\":\"openid profile email\"}"));
    authHandler.setupCallback(router.route());
    return authHandler;
}

В приведенном выше примере обратите внимание на три запрошенные области: openid , profile и email . В будущих публикациях будут рассмотрены дополнительные области действия и уровни авторизации, но на данный момент эти три будут содержать основные сведения (такие как имя пользователя и адрес электронной почты). Используя адрес электронной почты, вы также можете напрямую запросить у API Okta дополнительную информацию о пользователе и выполнить задачи по управлению аккаунтом.

Перехват и авторизация защищенных конечных точек

Теперь, когда AuthHandler подготовлен, он должен предшествовать обработке запросов любой защищенной конечной точки и аутентифицировать пользователя. Используя подстановочный знак, чтобы зарегистрировать его в качестве обработчика верхнего уровня для всех путей ниже /private/ , его нужно будет обработать только один раз для всех будущих обработчиков, которые вы можете создать. Любой обработчик запроса ниже /private/ path может быть гарантирован, что при его вызове он будет только от правильно аутентифицированного пользователя.

Измените метод startServer() класса MainVerticle как указано ниже, чтобы сгенерировать и зарегистрировать обработчик:

public void startServer() {
 
    Router router = Router.router(vertx);
 
    //create and register the auth handler to intercept all
    //requests below the /private/ URI:
    AuthHandler authHandler = getOAuthHandler(router);
    router.route("/private/*").handler(authHandler);
 
    router.route("/private/secret")
        .handler(ctx -> {
        ctx.response().end("Hi");
    });
 
    vertx.createHttpServer()
        .requestHandler(router::accept)
        .listen(config().getString(“port”));
}

Это было бы отличным временем, чтобы снова запустить сервер и убедиться, что все работает как положено. mvn compile java:exec снова mvn compile java:exec и нажав http://localhost:8080/private/secret из вашего браузера, вы автоматически будете перенаправлены на страницу входа Okta. После входа вы должны быть перенаправлены обратно на ваш сайт, чтобы продолжить отвечать на первоначальный /private/secret запрос.

Извлечь информацию о пользователе из JWT

Теперь, когда звонящие в ваши /private/ API-интерфейсы вошли в систему, вам нужно будет узнать их информацию. Он поставляется в виде веб-токена JSON , который необходимо извлечь и декодировать. OAuth-обработчик Vert.x скрывает это как именованный элемент строкового объекта JSON, называемого принципалом , который сам является компонентом пользовательского объекта контекста. Этот закодированный токен затем декодируется и проверяется с использованием выбранной вами библиотеки JWT. В этом примере используется библиотека верификатора JWT от Okta .

access_token и access_token и id_token , но это руководство будет декодировать только id_token . Существует аналогичная функция для декодирования access_token если это необходимо. Для этого включите зависимость lib в Okta JWT в ваш pom.xml :

<dependency>
    <groupId>com.okta.jwt</groupId>
    <artifactId>okta-jwt-verifier</artifactId>
    <version>0.2.0</version>
</dependency>

… И добавьте следующую новую функцию в класс MainVertical :

Map<String, Object> getIdClaims(RoutingContext ctx) {
    try {
        JwtVerifier jwtVerifier = new JwtHelper()
            .setIssuerUrl(config().getString(“issuer”))
            .setAudience("api://default")
            .setClientId(config().getString("clientId"))
            .build();
 
        Jwt idTokenJwt = jwtVerifier.decodeIdToken(ctx.user().principal().getString("id_token"), null);
        return idTokenJwt.getClaims();
    } catch (Exception e) {
        //do something with the exception...
        return new HashMap<>();
    }
}

Вот и все! Теперь вы можете получить доступ к пользовательской информации в вашем обработчике запросов. Чтобы продемонстрировать, обработчик /private/secret может быть обновлен для получения заявок из JWT, как показано ниже:

void startServer() {
 
    Router router = Router.router(vertx);
    AuthHandler authHandler = getOAuthHandler(router);
    router.route("/private/*").handler(authHandler);
    router.route("/private/secret").handler(ctx -> {
 
        Map claims = getIdClaims(ctx);
        ctx.response().end("Hi " +
                            claims.get("name") +
                            ", the email address we have on file for you is: "+
                            claims.get("email"));
    });
 
    vertx.createHttpServer().requestHandler(router::accept).listen(config().getString(“port”));
}

С этим последним изменением, перезагружая ваш сервер и снова нажимая http://localhost:8080/private/secret , ваш браузер должен теперь отобразить сообщение с информацией вашего аутентифицированного пользователя!

Вперед и вверх

Поздравляем, теперь у вас есть высокопроизводительный сервер Vert.x, защищенный современной системой управления безопасностью и идентификацией Okta! Okta предоставляет Java SDK для дальнейшего взаимодействия с пользователями и учетными записями, включая добавление пользовательских данных и атрибутов для ваших пользователей.

Спасибо за чтение, и, как всегда, пожалуйста, напишите нам в комментариях ниже с вопросами. Мы бы хотели, чтобы вы следили за нами в Твиттере @OktaDev или читали более интересный контент Java из нашего блога:

«Я люблю писать код аутентификации и авторизации». Нет Java-разработчика. Надоело строить одни и те же экраны входа снова и снова? Попробуйте API Okta для размещенной аутентификации, авторизации и многофакторной аутентификации.

Добавление единого входа на сервер Vert.x с Okta было первоначально опубликовано в блоге разработчиков Okta 11 января 2018 года.