Статьи

Защитите свое Java-приложение с помощью Spring Security, Thymeleaf и Okta

Never Build Auth Again — Любите строить управление пользователями? С Okta вы можете за считанные минуты добавить в приложение социальную регистрацию, многофакторную аутентификацию и поддержку OpenID Connect. Создайте бесплатный аккаунт разработчика сегодня.

Когда вы создаете свое Java-приложение, управление пользователями является критическим фактором. Для приложений и API характерно разделять доступ к различным частям приложения в зависимости от ролей, назначенных пользователю — это управление доступом на основе ролей (RBAC).

Вот тут и приходит Okta — Okta упрощает это, управляя ролями внутри групп (пользователи могут принадлежать к одной или нескольким группам). Благодаря интеграции Okta Spring Security этот процесс становится автоматизированным благодаря использованию общих аннотаций, которые сопоставляют группы с конкретными ролями и разрешают или запрещают доступ. Это делается с помощью обычных аннотаций Spring Security, которые мы описали ниже.

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

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

Если вы уже хотите погрузиться и начать работать со встроенным RBAC для вашего Java-приложения, просто подключите своего клиента Okta к приложению Spring Boot здесь .

Если у вас есть какие-либо вопросы, свяжитесь со службой поддержки Okta здесь.

Шаг первый: Настройте свою учетную запись Okta

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

  1. Создать группу admins
  2. Создать группу users
  3. Создать пользователя, который принадлежит к группам users
  4. Создайте второго пользователя, который принадлежит к обеим группам
  5. Создать приложение OpenID Connect (OIDC)
  6. Добавьте две группы в приложение
  7. Настройте сервер авторизации по умолчанию для включения членства в группах в токены доступа

Давайте пройдемся по тому, как это выглядит:

Настройка групп

В меню панели инструментов администратора Okta найдите « Users и нажмите « Groups . Нажмите « Add Group введите admins в поле « Name и добавьте описание группы, например: «Администраторы». Нажмите Add Group чтобы завершить этот шаг.

Следуйте тому же процессу, чтобы добавить группу users .

Настройка пользователей

Снова перейдите к « Users на панели инструментов администратора Okta, но на этот раз нажмите « People . Нажмите Add User и заполните форму с информацией о пользователе. (Используйте реальный адрес электронной почты для основного или дополнительного адреса электронной почты — и тот, к которому у вас есть доступ — чтобы вы могли проверить почту позже.) В поле Groups добавьте этих пользователей в группу users вы создали ранее. Убедитесь, что вы установили флажок « Send user activation email now а затем нажмите «Сохранить и добавить другого».

Повторите шаги, описанные выше, только на этот раз добавьте второго пользователя в группы users и admins .

Перейдите на свою электронную почту и проверьте эти адреса электронной почты. Нажмите на ссылки для обоих пользователей, чтобы активировать их.

Создание приложения OIDC

Теперь пришло время настроить слой аутентификации.

На информационной панели администратора Okta выберите Applications в меню, а затем нажмите Add Application .

Выберите Web и нажмите Next .

Когда вам будет предложено заполнить форму, используйте эти значения:

| Field               | Value                          |
| ------------------- | ------------------------------ |
| Name                | Fun with Spring Security Roles |
| Base URIs           | http://localhost:8080/         |
| Login redirect URIs | http://localhost:8080/         |
| Group assignments   | `admins` and `users`           |
| Grant type allowed  | Check: `Implicit`              |

Когда вы будете готовы, нажмите `Done`, и вы увидите получившуюся страницу ниже:

Наконечник
Указанные URI являются значениями по умолчанию для Spring Boot. Вы можете легко изменить их позже.

Прежде чем перейти к следующему шагу, прокрутите вниз и запишите Client ID . Он понадобится вам для настройки приложения Spring Boot.

Настройка вашего сервера авторизации

Затем найдите API в меню панели администратора Okta и нажмите « Authorization Servers чтобы запустить следующее:

Обратите внимание на Issuer URI . Это также понадобится вам позже для настройки приложения Spring Boot.

Нажмите «По default и выберите вкладку « Claims ». Нажмите Add Claim и заполните поля следующим образом:

| Field                 | Value        |
| --------------------- | ------------ |
| Name                  | groups       |
| Include in token type | Access Token |
| Value type            | Groups       |
| Filter                | Regex .*     |
| Include in            | Any scope    |

Нажмите Create чтобы завершить этот шаг.

Создание этого утверждения гарантирует, что информация о членстве в группе будет включена в токен доступа при аутентификации пользователя. Этот шаг очень важен для подключения к ролям и механизмам безопасности Spring Security

Шаг второй: настройте приложение Spring Boot

Сначала клонируем по этой ссылке.

Откройте проект в выбранной вами IDE или редакторе. Скриншоты ниже отсюда.

Сделайте копию файла application.yml.sample и назовите его application.yml

Помните те значения, которые мы пометили, чтобы сохранить ранее? Обновите свою информацию с теми. Вот наш пример:

| Name        | Value                                             |
| ----------- | ------------------------------------------------- |
| baseUrl     | https://dev-237330.oktapreview.com                |
| issuer      | https://dev-237330.oktapreview.com/oauth2/default |
| audience    | api://default                                     |
| clientId    | 0oacdldhkydGGruON0h7                              |
| rolesClaim  | groups                                            |
| redirectUri | http://localhost:8080/                            |

Прежде чем мы перейдем к коду, давайте посмотрим на приложение в действии.

Запустите это из командной строки, чтобы начать:

mvn spring-boot:run

Смотрите приложение в действии

Перейдите на домашнюю страницу и нажмите « Login .

Чтобы войти, используйте учетные данные для пользователя, который принадлежит к группе Users вы создали на первом шаге. Оттуда вы увидите, что приложение отображает вашу пользовательскую информацию и отображает ряд кнопок под ней.

Они соответствуют разрешениям на доступ в приложении. Члены группы users смогут видеть страницу при нажатии Users Only . Это тот же случай для пользователей группы admins , которые могут видеть страницу, когда Admins Only admins .

Посмотрим, как это работает.

Нажмите Users Only . Вы увидите страницу, которая показывает, что вы являетесь членом группы users .
Нажмите Back и затем нажмите Admins Only . На этот раз вы получите страницу 403 Unauthorized потому что вы не являетесь членом группы admins .
Нажмите Logout и войдите снова, но на этот раз как пользователь, который принадлежит к обеим группам (второй пользователь, которого вы создали на первом шаге).
Нажмите Admins Only .

На этот раз вы увидите, что вы находитесь в группах admins и users .

Довольно просто! Теперь давайте прыгнем в код …

Шаг третий: проверка кода безопасности Spring

В этом разделе описывается, как группы Okta связываются с ролями Spring Security.

Демо- приложение использует следующее:

  1. Весенний ботинок
  2. Spring Security
  3. Spring Security OAuth2
  4. Okta Spring Security Starter
  5. Шаблоны с тимьяном
  6. Дополнения Thymeleaf для Spring Security 4
  7. Okta Sign-In Widget

Мы зависим от okta-spring-security-starter (из pom.xml). Вот как происходит закулисная магия:

...
<dependency>
    <groupId>com.okta.spring</groupId>
    <artifactId>okta-spring-security-starter</artifactId>
    <version>0.1.0</version>
</dependency>
...

Давайте начнем с Javascript Okta Sign-In Widget и посмотрим, как он соединяет клиентскую сторону с Spring Boot.

Настройка виджета входа в Okta

Вот как мы настроили виджет входа в Okta в шаблоне Thymeleaf `login.html`:

$( document ).ready(function() {
    var data = {
        baseUrl: [[${appProperties.baseUrl}]],
        clientId: [[${appProperties.clientId}]],
        redirectUri: [[${appProperties.redirectUri}]],
        authParams: {
            issuer: [[${appProperties.issuer}]],
            responseType: ['token']
        }
    };
    window.oktaSignIn = new OktaSignIn(data);
 
    // Check if we already have an access token
    var token = oktaSignIn.tokenManager.get('token');
    if (token) {
        window.location.href = "/authenticated";
    } else {
        renderWidget();
    }
});

В строках 3–8 вы увидите, что мы встроили все параметры для подключения к нашему клиенту Okta в качестве встроенных переменных шаблона Thymeleaf. Эти значения передаются из контроллера Spring Boot как часть модели. Делая это, вы должны указать эти настройки только один раз — теперь и серверная, и клиентская стороны могут убедиться в них. Ниже мы рассмотрим, как управлять этими настройками (все они взяты из файла application.yml ).

После того, как мы настроили и создали экземпляр Okta Sign-In Widget, следующий шаг — проверить, вошел ли пользователь в систему. Затем мы предпринимаем одно из двух действий. Если они есть, мы отправляем их на страницу /authenticated . Если нет, мы отображаем виджет, который дает пользователю возможность войти в систему.

Вот функция renderWidget :

function renderWidget() {
    oktaSignIn.renderEl(
        {el: '#okta-login-container'},
        function (response) {
 
            // check if success
            if (response.status === 'SUCCESS') {
 
                // for our example we have the id token and the access token
                oktaSignIn.tokenManager.add('token', response[0]);
 
                if (!document.location.protocol.startsWith('https')) {
                    console.log(
                        'WARNING: You are about to pass a bearer token in a cookie over an insecure\n' +
                        'connection. This should *NEVER* be done in a production environment per\n' +
                        'https://tools.ietf.org/html/rfc6750'
                    );
                }
                document.cookie = 'access_token=' + oktaSignIn.tokenManager.get('token').accessToken;
                document.location.href = "/authenticated";
            }
        },
        function (err) {
            // handle any errors
            console.log(err);
        }
    );
}

Как только виджет отображается на странице, внутренняя логика вступает во владение на основе ваших настроек, когда пользователь входит в систему. В этом случае вы используете этот поток и получите только токен доступа, указанный параметром responseType конфигурации ,

При успешном входе в систему вы вводите функцию обратного вызова с объектом response . Объект ответа имеет ваш (или в данном случае ваш пользователь) токен доступа.

Строка 19 устанавливает cookie с токеном доступа, а строка 20 отправляет (теперь аутентифицированного) пользователя в /authenticated конечную точку.

На этом этапе Spring Security может распознать прошедшего проверку пользователя.

Прежде чем мы рассмотрим роли Spring Security, давайте посмотрим, как Spring Security работает с токеном доступа.

Spring Security Token Extractor

По умолчанию плагин Spring Security OAuth 2.0 обрабатывает токены доступа, входящие в заголовок Authorization как токен канала-носителя. Это хорошо для приложений, которые создают ответы RESTful для клиентов, таких как клиент Angular.

Для этого примера я сохранил архитектуру и количество Javascript минимальными, поэтому я хотел полных переходов страницы. Это немного старая школа, но она держит небольшой пример кода.

Чтобы Spring Security распознала, что пользователь прошел аутентификацию, нам нужно, чтобы он мог обрабатывать токен, поступающий в cookie.

К счастью, Spring Security позволяет довольно просто переопределить поведение по умолчанию, установив TokenExtractor . Вот код, который делает это происходящим из OktaSpringSecurityRolesExampleApplication :

@Bean
protected ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() {
    return new ResourceServerConfigurerAdapter() {
 
        ...
 
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.tokenExtractor(new TokenExtractor() {
 
                @Override
                public Authentication extract(HttpServletRequest request) {
                    String tokenValue = findCookie(ACCESS_TOKEN_COOKIE_NAME, request.getCookies());
                    if (tokenValue == null) { return null; }
 
                    return new PreAuthenticatedAuthenticationToken(tokenValue, "");
                }
 
                ...
            });
        }
    };
}

Для этого необходимо извлечь токен доступа из списка файлов cookie на входящий запрос, если это возможно. Затем анализ и проверка выполняется автоматически. Вуаля!

Установление контроля доступа на основе ролей

В настройках приложения вы определяете, какие пути открыты. Для всех других путей требуется как минимум аутентифицированный сеанс.

Вот еще одна выдержка из приложения OktaSpringSecurityRolesExampleApplication :

@Bean
protected ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() {
    return new ResourceServerConfigurerAdapter() {
 
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .antMatchers("/", "/login", "/images/**").permitAll()
                .and()
                .exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);
        }
        ...
    };
}

В этом случае вы говорите Spring Security разрешить любому не прошедшему проверку подлинности пользователю доступ к домашней странице ( / ), странице входа ( /login ) и всему, что происходит из папки статических изображений. Это означает, что любой другой путь автоматически ограничен по умолчанию.

На этом этапе вы также определяете пользовательский обработчик отказа в доступе.

@Controller
public class SecureController {
 
    @Autowired
    protected AppProperties appProperties;
 
    @RequestMapping("/authenticated")
    public String authenticated(Model model) {
        model.addAttribute("appProperties", appProperties);
        return "authenticated";
    }
 
    @RequestMapping("/users")
    @PreAuthorize("hasAuthority('users')")
    public String users() {
        return "roles";
    }
 
    @RequestMapping("/admins")
    @PreAuthorize("hasAuthority('admins')")
    public String admins() {
        return "roles";
    }
 
    @RequestMapping("/403")
    public String error403() {
        return "403";
    }
}

В этом контроллере мы определили четыре пути, которые требуют минимум аутентификации пользователя.

Реальная ценность /admins путях /users и /admins . Обратите внимание, что у них обоих есть аннотация @PreAuthorize . Это означает, что следующее выражение должно быть удовлетворено до того, как метод будет введен. Функция hasAuthority подтверждает, принадлежит ли аутентифицированный пользователь к этим ролям. В этом примере они автоматически сопоставляются с группами Okta, которые мы создали ранее, поэтому было важно включить утверждение groups в маркер доступа от Okta.

Хотя это требует некоторой настройки, теперь у вас есть контроль доступа на основе ролей всего с одной строкой кода!

Сквозная конфигурация

На стороне клиента у нас есть набор HTML-страниц в форме шаблонов Thymeleaf, которые подаются из самого приложения. Из-за этого имеет смысл иметь единый источник значений конфигурации, требуемый как клиентом, так и сервером.

Это легко с аннотациями Spring @Component и @ConfigurationProperties .

Вот класс AppProperties :

@Component
@ConfigurationProperties("okta.oauth")
public class AppProperties {
    private String issuer;
    private String audience;
    private String clientId;
    private String rolesClaim;
    private String baseUrl;
    private String redirectUri;
 
    ... getters and setters ...
}

@ConfigurationProperties сообщает Spring, чтобы он @ConfigurationProperties все свойства из файла application.yml , принадлежащего okta.oauth .

Аннотация @Component заставляет Spring создать экземпляр этого объекта и сделать его доступным для автоматического подключения в другом месте.

Взгляните на этот фрагмент из HomeController :

@Controller
public class HomeController {
 
    @Autowired
    protected AppProperties appProperties;
 
    ...
 
    @RequestMapping("/login")
    public String login(Model model) {
        model.addAttribute("appProperties", appProperties);
        return "login";
    }
}

Перед возвратом представления login в login при достижении конечной точки /login объект AppProperties (автоматически подключенный в строках 4 и 5) добавляется в модель.

Вот что делает его доступным для шаблона Thymeleaf, как вы видели ранее:

<script th:inline="javascript">
    /*<![CDATA[*/
    $( document ).ready(function() {
        var data = {
            baseUrl: [[${appProperties.baseUrl}]],
            clientId: [[${appProperties.clientId}]],
            redirectUri: [[${appProperties.redirectUri}]],
            authParams: {
                issuer: [[${appProperties.issuer}]],
                responseType: ['token']
            }
        };
        window.oktaSignIn = new OktaSignIn(data);
        ...
    });
    ...
    /*]]>*/
</script>

Начните кодировать!

Вот и все! Попробуй и дай мне знать, как это происходит! Я в твиттере.

Хотя я рассказал о преимуществах использования механизма групп Okta с контролем доступа Spring на основе ролей, команда разработчиков Java-приложений Okta также усердно работает над SDK следующего поколения и интеграциями. Следите за будущими выпусками новой Okta Java Spring Boot Integration, которая будет иметь поддержку других рабочих процессов OIDC, включая code а также размещенные, настраиваемые представления входа и регистрации.

Этот пост был адаптирован отсюда.

Never Build Auth Again — Любите строить управление пользователями? С Okta вы можете за считанные минуты добавить в приложение социальную регистрацию, многофакторную аутентификацию и поддержку OpenID Connect. Создайте бесплатный аккаунт разработчика сегодня.