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

Как работает аутентификация?

У нас есть конечный пользователь (реальный человек), браузер и веб-приложение. В большинстве случаев конечный пользователь должен заполнить форму входа, указав имя пользователя и пароль. Браузер отправляет учетные данные веб-приложению через HttpRequest в заголовках или в теле запроса.

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

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

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

Мы добавили исправления для большинства из них, но на самом деле у обычного пользователя около 90 учетных записей, некоторые с общим паролем (вы не можете контролировать, как другие будут хранить его), и, в конце концов, полностью отказались от паролей. начинает звучать как очень хорошая идея.

Что мы можем использовать для замены паролей?

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

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

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

WebAuthentication заменяет пароли учетными данными на основе открытого ключа, которые:

  • strong - специальное устройство (Authenticator) создает набор пары закрытый ключ / открытый ключ. Закрытый ключ никогда не покидает устройства, а открытый ключ отправляется веб-приложению во время церемонии регистрации.
  • attested - подтвердить происхождение аутентификатора и передаваемых им данных.
  • scoped - только приложение, создавшее учетные данные, может получить к ним доступ позже.

Как работает WebAuthentication?

Вместо того, чтобы один вызов отправлял учетные данные (общий секрет) в ваше приложение, браузер запускает 2 запроса:

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

Как это помогает пользователю?

Им не нужно запоминать пароль, но им нужен доступ к аутентификатору. К счастью, некоторые устройства могут иметь встроенные аутентификаторы (аутентификатор платформы) или они могут подключаться к удаленному устройству (роуминг-аутентификатор) через USB, NFC, Bluetooth и т. Д.

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

Перенос приложения Spring Boot

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

Первый шаг - включить библиотеку в ваш pom:

После того, как библиотека загружена в ваш проект (возможно, вам нужно запустить mvn install, чтобы он был доступен в пути к классам), вам нужно активировать магию Spring с помощью @ EnableWebAuthn.

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

Вторая важная вещь - активировать WebAuthnConfigurer для автоматического предоставления нескольких конечных точек, необходимых в церемониях WebAuthnetication (регистрация, аутентификация, восстановление, добавление устройства). Этот метод аутентификации работает поверх ваших существующих методов (в этом примере: formLogin)

Третья важная вещь - предоставить некоторую информацию о вашем приложении. Они используются для охвата учетных данных только вашим доменом (ами). Вы также предоставляете набор источников для приема запросов аутентификации только от известных источников.

Если вам не нравятся конечные точки, добавленные в ваше приложение, вы можете переименовать их, чтобы они соответствовали дизайну вашего API. Наконец, вы можете применить определенные алгоритмы, используемые в процессе. Если есть дополнительные вещи, которые вы хотите настроить, просто откройте вопрос здесь.

На данный момент в библиотеке также есть некоторые предопределенные ресурсы внешнего интерфейса, которые помогут вам быстро освоиться.

Они используют эти прекрасные веб-компоненты webauthn, которые нам просто нужно раскрыть:

application.yaml
-------
web:
  resources:
    static-locations: classpath:/META-INF/resources/webauthn

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

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

Также доступны новые страницы webauthn с белыми ярлыками. Например, мы можем создать новую учетную запись без необходимости установки пароля на странице /register.html.

Когда мы нажимаем кнопку «Регистрация», браузер взаимодействует с API WebAuthentication (расширением API управления учетными данными) для создания новых учетных данных. Вы можете видеть, что конечная точка / api / registration / start также была вызвана для создания учетной записи и набора ограничений, используемых аутентификатором для определения новых учетных данных.

Конечный пользователь должен выбрать один доступный аутентификатор, и после нажатия отпечатка пальца (в данном случае с Touch ID) в вашем приложении регистрируются новые учетные данные.

Мы только что завершили процесс регистрации, и мы перенаправлены на страницу входа.

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

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

Сообщение отправляется на / api / registration / finish, и наше приложение может проверить его действительность.

Если вам интересно, вы можете декодировать свойство clientDataJson, чтобы узнать, что там. Например, поле происхождения описывает, какое приложение запускает запрос аутентификации. Его следует найти в значениях свойств webauthn.relying-party-origins.

{
"type":"webauthn.get",
"challenge":"xoQuX8I1343QvBwmndVatCHKyaQOYxpTSCmb9Z5YYd0",
"origin":"http://localhost:8080",
"crossOrigin":false
}

Наконец, мы можем увидеть файл cookie sessionId, сохраненный в нашем ответе, и теперь мы вошли в систему.

Представление аутентифицированного пользователя в webauthn-spring-boot-starter имеет несколько свойств:

"user": {
    "id": 31,
    "username": "demo",
    "enabled": true,
    "recoveryToken": "oNQE4W9Fb10t2IHeScMf3A==",
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true
  },

Мы можем проверить, какие данные хранятся в базе данных (мы все еще используем spring-data jpa, помните, и мы можем получить доступ к консоли h2, чтобы увидеть больше информации)

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

.apply(new WebAuthnConfigurer()
        .userSupplier(() -> {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication == null) {
                return null;
            }
            return webAuthnUserRepository.findByUsername(authentication.getName())
                    .orElseGet(() -> {
                        WebAuthnUser newUser = new WebAuthnUser();
                        newUser.setUsername(authentication.getName());
                        newUser.setEnabled(true);
                        return webAuthnUserRepository.save(newUser);
                    });
        })
);

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

Если вы хотите реагировать, когда некоторые пользователи зарегистрированы, вы можете использовать разные методы, доступные в настроенном:

.apply(new WebAuthnConfigurer()
        .registerSuccessHandler(user -> {
           log.info("new user registered: {}", user);
})

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

Если вы нажмете кнопку регистрации, вы будете следовать процессу регистрации, чтобы связать новые учетные данные на основе открытого ключа для существующего пользователя. После этого просто используйте страницу /login.html с предыдущим именем пользователя и без пароля.

Если вы достигли этого шага, Поздравляем 🥳🥳🥳. Наконец-то вы смогли добавить новый метод аутентификации без пароля для своих пользователей, и они могут забыть все свои пароли. Теперь вы можете решить, когда удалить метод входа с помощью формы, или, может быть, вы можете перед этим поэкспериментировать с WebAuthentication для mFA.

В конце концов, вам решать, как вы его используете, потому что вы лучше всех знаете, что делает ваше приложение и что ему нужно.

Исходный код доступен здесь, спасибо за внимание.

Другие статьи о Java и Spring Boot, которые могут вам понравиться