Рубрики
Без рубрики

Безопасность с использованием Spring и JWT

Настройка приложения Spring с использованием Spring Security и JWT. С тегами java, spring, gwt, security.

Вступление

Я наконец-то смог опубликовать эту статью. Это заняло так много времени по двум основным причинам; во-первых, за последние несколько недель я был вовлечен в очень интересный проект. Было действительно приятно, когда мы развернули его, провели тестовый запуск и увидели, что все заинтересованные стороны были довольны тем, как продукт соответствует ожидаемым критериям и желаемым функциям. Во-вторых, и это печально, мой жесткий диск разбился, и процесс восстановления был довольно сложным. (Я все еще оцениваю то, что я потерял до сих пор, особенно мои записи и документы). Но, к счастью, большинство кодов моего проекта находились в соответствующих репозиториях git, и разница была не так уж велика с тем, что было на моем ноутбуке. Именно поэтому я хотел бы воспользоваться этой возможностью, чтобы еще раз напомнить нам, разработчикам, что мы всегда должны как можно чаще обращаться к нашим репозиториям кода в текущей рабочей ветке. Даже если задача выполнена не полностью или код не компилируется после работы над ним с течением времени. Это не обязательно должно быть каждый день, но, по крайней мере, убедитесь, что вы взяли на себя обязательство и продвигаетесь в течение недели. Этот толчок может заставить вас фонтанировать позже в будущем.

Теперь, когда мы покончили с этим, давайте сосредоточимся на том, о чем я действительно хочу поговорить в этой статье. В моих последних двух статьях здесь и здесь я продемонстрировал, как создавать REST API и интегрировать их с внешними API, используя шаблоны проектирования и лучшие практики разработки приложений. В этой статье я продемонстрирую, как реализовать безопасность с помощью JWT для защиты наших REST API. Я также остановлюсь на других стратегиях обеспечения безопасности нашего приложения, особенно в отношении данных. Я знаю, что это может быть знакомая тема, но обычно они пишутся изолированно от общего контекста разработки приложения. Поэтому мы будем опираться на то, что мы разработали в предыдущих статьях, которые я написал. Я по-прежнему буду использовать тот же подход с точки зрения ссылок на классы, компоненты и методы, поэтому вы можете захотеть получить доступ к репозиторию здесь а также взгляните на предыдущие статьи, чтобы немного поиграть в догонялки.

Реализация кода

Представьте, что вы консультант и собираетесь записаться на прием к своему новому клиенту. Вы знаете, что процедура заключается в том, чтобы сначала зарегистрироваться у клиента в качестве консультанта, где вы предоставляете все свои данные, которые будут храниться в их записях. Это похоже на процесс Регистрации , который мы выполняем онлайн, когда вы хотите использовать приложение в первый раз. После регистрации вы переходите к записи на прием на согласованную дату. По прибытии в помещение вы идентифицируете себя на стойке регистрации, и если все проверки завершены, вам выдают бирку или карточку посетителя, которая дает вам доступ к определенным частям или этажам здания. Этот процесс может быть сопоставлен с процессом входа в приложение с использованием ваших учетных данных при регистрации. Это процесс Аутентификации , и если ваши учетные данные действительны, вам предоставляется доступ к приложению. В то же время вам также выдается JWT (токен) (или для вас создается сеанс в веб-приложении с отслеживанием состояния или файл cookie для браузера). Этот токен представляет собой тег посетителя, который был выдан вам. И так же, как и тег, токен предоставит вам контролируемый доступ к ресурсам в домене приложения на основе ролей, назначенных токену. Это используется в процессе Авторизации , чтобы определить, можете ли вы получить доступ к ресурсу или нет, основываясь на вашей роли (ролях) (точно так же, как тег посетителя предоставит вам доступ только к определенным этажам в здании). Теперь, как и тег, который можно использовать только в течение этого дня, токен также имеет срок годности и становится недействительным, что приводит к сбоям авторизации и отказу в доступе к ресурсам. Однако в некоторых случаях, предполагая, что вы не закончили свою консультацию на этот день, тег может быть повторно использован на следующий день по договоренности. В том же духе токен также может быть обновлен по запросу, и для него устанавливается новая дата истечения срока действия после проверки старого токена. В том случае, если вы закончили свою консультацию, вы должны отправить бирку перед выходом из здания. Аналогично, когда вы закончите использовать приложение, вам необходимо выйти из системы, чтобы токен был признан недействительным. И как только вы выйдете из здания или выйдете из приложения, вам придется повторить процесс идентификации на стойке регистрации или процесс входа в систему снова, чтобы получить доступ к объекту или приложению для выполнения ваших действий. Теперь, когда у нас есть представление о том, как аутентификация, Авторизация, Генерация и управление токенами работают вместе, давайте соединим точки, используя Spring Security Framework и JWT:

Сначала мы создаем наш Собственный класс Manager для обработки генерации, проверки и извлечения информации из нашего токена. Затем мы создаем наш класс CustomUserDetails . Этот класс будет хранить сведения о пользователе или участнике, прошедшем проверку подлинности Spring. Путем реализации org.springframework.security.core.userdetails. UserDetails интерфейс, он может получить доступ к такой информации, как имя пользователя, пароль и полномочия, предоставленные участнику, через назначенную роль (роли) пользователя. Далее мы создаем наш Пользовательский класс UserDetailsService . Этот класс определяет пользовательскую реализацию метода loadUserByUsername в org.springframework.security.core.userdetails. UserDetailsService интерфейс, который он реализует и на основе результата возвращает экземпляр CustomUserDetails, который мы определили ранее. В этом случае мы запрашиваем базу данных с указанным именем пользователя, чтобы узнать, существует оно или нет.

Затем мы создаем наш класс JwtAuthenticationFilter . Этот класс фильтрует каждый входящий запрос по защищенному ресурсу для проверки и проверки токена, который отправляется с использованием метода validateToken , определенного в нашем классе JwtManager. Если он действителен, он извлекает имя пользователя (тему) и роли из утверждений токена, используя получить Имя Пользователя Из JWT и получаем роли из методов Jwt соответственно, которые были определены в нашем собственном классе Manager и используют его для создания допустимого контекста безопасности для пользователя, чтобы пользователь оставался аутентифицированным в приложении. Мы могли бы в равной степени использовать второй подход, используя только имя пользователя и метод loadUserByUsername класса CustomUserDetailsService, чтобы достичь этого с помощью этого фрагмента кода

        String jwt = getJwtFromRequest(request);

            if (StringUtils.hasText(jwt) && jwtManager.validateToken(jwt)) {
                String username = jwtManager.getUsernameFromJWT(jwt);

        //Using an injected CustomUserDetailsService instance
        CustomUserDetails customUserDetails = customUserDetailsService.loadUserByUsername(username);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customUserDetails.getPrincipal(), "", customUserDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }else {
                SecurityContextHolder.getContext().setAuthentication(null);
            }

Недостатком второго подхода является то, что при использовании метода loadUserByUsername для каждого запроса, в отличие от первого подхода, будет снижаться производительность базы данных С другой стороны, второй подход позволяет в режиме реального времени изменять С другой стороны, второй подход позволяет в режиме реального времени изменять

С другой стороны, второй подход позволяет в режиме реального времени изменять и Пользовательские Rest AuthenticationEntryPoint классы. Пользовательские Rest AuthenticationEntryPoint классы. AccessDeniedHandler и org.springframework.security.web. AuthenticationEntryPoint интерфейсы соответственно. Метод handle в пользовательском классе Rest Access Denied предназначен для запуска запросов, в которых пользователи прошли проверку подлинности, но не имеют допустимой роли для доступа к ресурсам, и возвращает статус HTTP 403, в то время как метод begin

Теперь давайте взглянем на наш класс конфигурации безопасности, который представляет собой класс SecurityConfig . Класс SecurityConfig, который расширяет org.springframework.security.config.annotation.web.configuration. WebSecurityConfigurerAdapter – это то, что объединяет все предыдущие компоненты и классы безопасности, которые мы определили до сих пор. Это определяет поведение и роли, которые они играют в нашей реализации безопасности приложений. Аннотации secure Enabled, jsr250Enabled и prePostEnabled позволяют нам определять, какие роли должны иметь доступ к определенному ресурсу. Это можно настроить либо для методов, к которым вы хотите применить эти ограничения (например, в контроллерах), либо добавить его в antMatchers , как видно из метода configure в SecurityConfig Класс CustomUserDetailsService, который мы определили ранее, вводится и указывается для использования в качестве нашего компонента аутентификации в переопределенном public void configure(AuthenticationManagerBuilder AuthenticationManagerBuilder) методе. Мы также настраиваем компонент Password Encoder для использования Spring BCryptPasswordEncoder для проверки пароля пользователя также в этом методе. В переопределенном protected void configure(HttpSecurity http Security) методе мы настроили обработчики исключений для сценариев несанкционированного доступа и отказа в доступе на использование введенных классов CustomRestAuthenticationEntryPoint и CustomRestAccessDenied соответственно. Мы отключили csrf, чтобы запросы из других доменов могли получать доступ к нашим конечным точкам, а затем мы объявляем управление сеансами без состояния, чтобы соответствовать спецификациям REST. Затем мы настраиваем наш введенный класс JwtAuthenticationFilter в качестве фильтра, который будет вызываться для каждого запроса к любому из наших защищенных ресурсов. И, наконец, мы отключаем нашу безопасность для конечных точек в/api/auth/route, поскольку это будет вызываться пользователями, не прошедшими проверку подлинности, во время регистрации и входа в систему.

Давайте теперь создадим компоненты, которые позволят пользователям регистрироваться и входить в систему в нашем приложении. Сначала мы создаем наш объект класса User , который будет сопоставляться с нашей таблицей users в базе данных. Мы также создаем объект Role class, который будет сопоставляться с roles таблица. Затем мы вставляем в таблицу ролей следующие роли: ROLE_USER и ROLE_ADMIN. Затем мы создаем классы UserRepository и User Service для управления нашей пользовательской сущностью и RoleRepository и Служба ролей классы для управления нашей ролевой сущностью. Для обеспечения целостности мы создаем Тип роли перечисление, которое сопоставляется с ролями, которые мы только что создали в нашей таблице ролей. Затем мы создаем наш класс AuthController для обработки регистрации и входа в систему. Как вы можете видеть, конечная точка регистрации довольно проста. Мы создаем пользователя с назначенными ролями и сохраняем в базе данных. Для конечной точки входа в систему вы указываете зарегистрированное имя пользователя и пароль, и если аутентификация с помощью Spring Security framework прошла успешно, с помощью объекта Spring Authencation устанавливается допустимый контекст безопасности для пользователя. Метод generateToken

Тестовый запуск

Теперь все настроено, и мы можем запустить наше приложение spring с помощью mvn spring-boot:run а затем мы пытаемся зарегистрироваться, нажав на конечную точку, используя нашу структуру класса Sign Up Request dto:

    POST    http://localhost:8080/api/auth/register
    {

    "name":"user1",
    "username":"username1",
    "email":"username1@email.com",
    "password":"password1"
    }

       Response:
    {
        "status": 0,
        "message": "User registered successfully",
        "result": null
    }

Повторите описанные выше действия для другого пользователя для создания и администрирования

    POST    http://localhost:8080/api/auth/register/admin
    {

    "name":"user2",
    "username":"username2",
    "email":"username2@email.com",
    "password":"password2"
    }

        Response:
    {
        "status": 0,
        "message": "Admin registered successfully",
        "result": null
    }

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

    POST http://localhost:8080/api/auth/login

    {
    "username":"username1",
    "password":"password1"
    }

        Response:
    {
        "status": 0,
        "message": "Token generated successfully",
        "result": {
        "accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VybmFtZTEiLCJyb2xlcyI6WyJST0xFX0FETUlOIl0sImlhdCI6MTU2MDk4MDU1MywiZXhwIjoxNTYwOTgyMzUzfQ.boqHQh3gLPgNWBP0GiATBGg-25bwMfg33-zn5BFK9AiFuhYcWcmSSFp_isjlhL_xJ9WxMW6A7UWaVOXMdx94ng"
        }
    }

Вызов любой из наших конечных точек без токена или недопустимого или истекшего токена даст вам ответ, инициируемый нашим обработчиком CustomRestAuthenticationEntryPoint с кодом состояния HTTP 401

    {
        "timestamp": "2019-06-19T22:51:37.189",
        "message": "Unauthorized user",
        "status": 401
    }

При вызове конечной точки с неавторизованной ролью скажите

http://localhost:8080/card-scheme/stats?start=1&limit=10

сгенерированный пользователем 1 токен выдаст ответ, вызванный нашим пользовательским Rest AccessDeniedHandler с кодом состояния HTTP 403, поскольку доступ к нему могут получить только роли администратора.

    {
        "timestamp": "2019-06-19T22:51:57.213",
        "message": "Access Denied",
        "status": 403
    }

Модульное тестирование

Давайте теперь посмотрим, как мы настроим написание наших тестов. Поскольку мы обеспокоены проверкой подлинности и авторизацией, мы будем ограничивать наши области тестирования, чтобы они не выходили за пределы уровня контроллера, поскольку именно здесь устанавливаются эти ограничения. Для достижения этой цели я создал класс Security Test и снабдил его аннотацией @WebMvcTest . Затем я высмеял все зависимости, которые необходимы на уровне контроллера, за исключением Jwt Manager, который я подсмотрел. Причина в том, что я на самом деле буду вызывать реальные методы в классе Jwt Manager в нашем тесте для генерации и проверки токенов. Затем я создал App Test Config класс конфигурации и установите базовое сканирование пакета на уровне контроллера. Однако мне пришлось создать компонент EntityManagerFactory , который будет использовать базу данных H2 в памяти для запуска и начальной загрузки. В моем методе настройки я затем добавил экземпляр JwtAuthenticationFilter в качестве фильтра для проверки токена. И, наконец, я включил Spring security с помощью .apply(spring Security()) . Выполнение тестов даст нам ожидаемые результаты, определенные в нашем тестовом классе.

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

Шифрование данных

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

В современном мире шифрование данных основано на двух различных формах криптографии:

  1. Симметричный ключ
  2. Симметричный ключ

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

Хотя на самом деле не было никакого варианта использования для реализации какой-либо из этих форм шифрования в нашем приложении, вы можете взглянуть на RSAencryption , AESEncryption и Encryption And Sign Test классы, которые я создал, чтобы увидеть доступные служебные методы и то, как они используются для выполнения различных операций. Обратите внимание, как в классе RSAencryption вы можете либо динамически генерировать ключи, вызывая метод generateKeyPair , либо загружать из файла хранилища ключей, вызывая метод getKeyPairFromKeyStore . Файл хранилища ключей может быть сгенерирован с помощью утилиты Java keytool. Одним из руководящих правил при определении того, какую форму использовать (RSA или AES), является размер данных для шифрования. Для больших объемов данных рекомендуется использовать форму AES, поскольку она быстрее и требует меньше вычислительных затрат, в то время как RSA можно использовать для меньших объемов данных.

Шифрование данных может выполняться как в состоянии покоя, так и в движении.

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

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

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

В случае шифрования данных в состоянии покоя это обычно включает защиту данных в наших компонентах или системах хранения данных, таких как наши базы данных (будь то СУБД, NOSQL или даже наши системы кэширования) и даже значения наших файлов конфигурации. Другими словами, используя классы утилит шифрования, которые я создал ранее, мы можем хранить наши данные в зашифрованном формате и расшифровывать их только тогда, когда они будут использоваться в приложении. Такой подход сделает данные в нашем хранилище данных зашифрованными и нечитаемыми, если они попадут в чужие руки. Мы также можем выбрать шифрование наших данных, не ожидая их расшифровки, если мы знаем алгоритм, который мы использовали при их шифровании в первую очередь. Это намного безопаснее, чем шифрование, и известно как Хеширование .

Классическим примером хеширования здесь является хранение паролей пользователей в нашей базе данных (недавно был протест против того, что Facebook хранит пароли пользователей Instagram в виде обычного текста). Мы достигли этого здесь, используя реализацию хеширования, которая является однонаправленной. Другими словами, в отличие от метода шифрования, обсуждавшегося ранее, он не может быть расшифрован (или практически невозможен) после того, как он был хэширован. Принцип здесь заключается в том, что хеширование определенной последовательности строк с помощью определенного алгоритма всегда приведет к получению одной и той же зашифрованной строки (за исключением случая Соления, который я вскоре рассмотрю). Здесь мы использовали метод BCryptPasswordEncoder encode для достижения этой цели, как видно из нашего класса SecurityConfig, где он используется в качестве нашего PasswordEncoder и используется при создании наших пользователей во время процесса регистрации.

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

В более старых реализациях Spring предоставлял класс MessageDigestPasswordEncoder , который позволял нам предоставлять нашу собственную строку соли для хэширования нашего пароля. Эффективное использование этого состояло в том, чтобы предоставить другую строку соли для каждой хэшируемой строки пароля (например, время в миллисекундах в момент регистрации). Однако это устарело в пользу BCryptPasswordEncoder, который мы в настоящее время используем в нашем приложении. BCryptPasswordEncoder не только хэширует ваш пароль, но и внутренне генерирует для нас случайную строку соли в процессе, как можно увидеть, если посмотреть на его внутреннюю реализацию. Эта реализация избавляет нас от накладных расходов, связанных с управлением генерацией строк соли, поскольку это имеет решающее значение для целостности наших паролей. В результате этого каждый раз, когда вы хэшируете одну и ту же строку пароля, создается другая хэшированная строка. Теперь возникает вопрос, как мы определяем, сравниваем и сопоставляем пароли? Реализация BCryptPasswordEncoder разбивает хэшированную строку на строку соли, параметр стоимости и хэшированное значение, все из которых могут быть получены из хэшированной строки, и поскольку соль определяет сгенерированный хэш, ее можно использовать для определения того, совпадают ли предоставленные пароли или нет с тем, что у нас есть в хранилище.

И, наконец, я хотел бы поговорить о защите файла конфигурации нашего приложения, который обычно называется application.properties file или application.yaml как в нашем случае. Хотя в нашем случае вы заметите, что конфиденциальная информация, такая как учетные данные базы данных, в настоящее время извлекается из переменных среды, я видел случаи, когда эти сведения хранятся в файле в виде обычного текста. Несмотря на то, что использование переменных окружения в некоторой степени снижает риск их наличия в файле в виде обычного текста, другим подходом будет использование Jasypt Spring boot starter module . Этот модуль позволяет нам хранить значения в зашифрованном виде, а затем расшифровывать их для использования во время выполнения.

Чтобы реализовать это в нашем приложении, нам сначала нужно добавить jasypt-spring-boot-starter в ваш pom.xml


    com.github.ulisesbocchio
    jasypt-spring-boot-starter
    2.1.1

Затем мы выполняем приведенную ниже команду

java -cp $M2_REPO/org/jasypt/jasypt/1.9.2/jasypt-1.9.2.jar  org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="yourpassword" password=jasypt_password algorithm=PBEWithMD5AndDES

где

  • $M2_REPO – это ваш каталог репозитория maven
  • ввод аргумент – это пароль, который вам нужно зашифровать, скажем, например ваш пароль к базе данных
  • пароль аргумент – это пароль или ключ Jasypt для шифрования, который вы будете указывать при запуске своего приложения
  • алгоритм – это выбранный вами алгоритм шифрования на основе пароля (PBE), предоставляемый Jasypt ( PBEWITHMD5ANDDES, PBEWITHMD5ANDTRIPLEDES, PBEWITHSHA1ANDDESEDE, PBEWITHSHA1ANDRC2_40 )

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

Затем вы устанавливаете свойства или значения переменных среды с этими зашифрованными значениями в этом формате ENC(encrypted_value) например, в нашем yaml вы можете изменить значение пароля следующим образом

.
.
.
spring:
  profiles: development
  application:
    name: binList
  datasource:
     password: ENC(encrypted_password_from_jasypt_operation)
     .
     .
     .

или вы просто оставляете файл yaml как есть и устанавливаете с ним переменную окружения SPRING_DATASOURCE_PASSWORD

export SPRING_DATASOURCE_PASSWORD=ENC(encrypted_password_from_jasypt_operation)

Затем вы либо запускаете свое приложение следующим образом

mvn -Djasypt.encryptor.password=jasypt_password spring-boot:run

или вы экспортируете переменную окружения JASYPT_ENCRYPTOR_PASSWORD и просто запускаете mvn spring-boot: бежать

export JASYPT_ENCRYPTOR_PASSWORD=jasypt_password

Чтобы еще больше усилить безопасность на нашем сервере приложений, вы можете последовательно выполнить следующие действия

  1. Экспортируйте и установите все переменные среды, упомянутые ранее, с помощью пакетного скрипта (.sh или .bat в зависимости от вашей ОС).
  2. Запустите приложение в качестве фоновой службы, как этот mvn spring-boot:run & или как служба Windows
  3. сбросьте все переменные среды с помощью пакетного скрипта, поскольку эти значения запрашиваются только один раз при запуске приложения.
  4. Удалите пакетный сценарий (ы), если у вас есть копия в другом месте.

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

Итак, вот оно у вас есть. Мы защитили наше приложение, хотя, как мы все знаем, безопасность никогда не может быть полной, но она должна замедляться и требовать больше усилий, чтобы кто-то злонамеренно использовал наше приложение. Есть и другие стратегии безопасности, которые я здесь не упомянул, особенно на уровне объектов доступа к данным, такие как параметризация ввода запроса для SQL-инъекции из-за отсутствия реализаций SQL или JQL в нашем приложении. Вы можете получить доступ к обновленному исходному коду этой статьи здесь .

В моей следующей статье, если позволит время, я продемонстрирую, как достичь Масштабируемость и Доступность с вашим приложением, использующим Docker и некоторый рефакторинг кода. Также я надеюсь восстановить Laravel и Node (гнезда) реализации того, что мы сделали до сих пор, после сбоя моего ноутбука и поделитесь им. Вот и все, ребята! Счастливого Кодирования.

Оригинал: “https://dev.to/charles1303/security-using-spring-and-jwt-aai”