Learning Microservices Series (9): Дизайн аутентификации безопасности интерфейса службы Springboot

Микросервисы

На этом этапе все сервисы, которые мы разрабатываем, предоставляют интерфейсы для внешнего мира.Проекты, включая структуру bs, отделены от внешнего и внутреннего интерфейса.Сервер обеспечивает бизнес-логику и поддержку данных для внешнего интерфейса через интерфейсы. В то же время несколько команд теперь работают вместе над разработкой сервисов, а данные передаются через интерфейсы. Затем нужно подумать о безопасности интерфейсных вызовов. Это требует, чтобы API использовал метод подписи (Sign) для аутентификации интерфейса. Каждый запрос должен включать информацию о подписи в запросе для проверки личности пользователя, иначе любой может вызвать наш интерфейс через скрипты, что приведет к угрозам безопасности.

Преимущества сертификации безопасности

  1. Проверка легитимности вызывающего абонента
  2. Защита от несанкционированного доступа
  3. предотвратить повторные атаки

Общая классификация сертификации безопасности

На основе класса динамической подписи

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

Идеи: Для каждого запроса по разным параметрам динамически генерируется соответствующая подпись по определенным правилам алгоритма.

шаг:

Звонящий в сервис:

  1. Отсортировать все запрошенные параметры лексикографически по имени параметра
  2. Объедините значения, соответствующие отсортированным параметрам, последовательно со строками + отметкой времени (используется для ограничения частоты запросов)
  3. Добавить appKey в конце (предоставляется поставщиком услуг)
  4. сделать md5
  5. Получите знак подписи и поместите его в заголовок запроса для доставки поставщику услуг

Поставщик услуг:

  1. Получите параметры запроса и получите разницу между отметкой времени и текущей отметкой времени, чтобы определить, находится ли запрос в пределах разницы во времени единицы.
  2. Зашифровать в соответствии с правилами шифрования и сравнить с переданным значением знака

Основной метод получает знак:

public String getSignToken(Map<String, String> map) {
        String result = "";
        try {
            List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(map.entrySet());
            // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
            Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {

                public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
                    return (o1.getKey()).toString().compareTo(o2.getKey());
                }
            });
            // 构造签名键值对的格式
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String> item : infoIds) {
                if (item.getKey() != null || item.getKey() != "") {
                    String key = item.getKey();
                    String val = item.getValue();
                    if (!(val == "" || val == null)) {
                        sb.append(val);
                    }
                }
            }
            result = sb.toString()+appKey;
            //进行MD5加密
            result = Md5Util.getMD5(result);
        } catch (Exception e) {
            return null;
        }
        return result;
}

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

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

Другими словами, HTTPS гарантирует, что пакеты, захваченные атаками «человек посередине», являются зашифрованными текстами, которые невозможно или трудно взломать. Тем не менее, сообщение все еще может быть отправлено повторно для формирования DDOS. В то же время, если подписи нет, используется только простая HTTP-аутентификация, а запросы могут инициироваться по желанию путем перехвата пакетов. Поэтому самый безопасный способ — совместить подпись HTTPS и API.

Класс аутентификации на основе входа пользователя

  • Проверка токена

При использовании метода аутентификации на основе токенов примерный процесс выглядит следующим образом:

  1. Клиент использует имя пользователя и пароль для запроса входа в систему.
  2. Сервер получает запрос на проверку имени пользователя и пароля
  3. После успешной проверки сервер выдаст токен, а затем отправит токен клиенту.
  4. После того, как клиент получит токен, он может его сохранить
  5. Каждый раз, когда клиент запрашивает ресурсы с сервера, ему необходимо принести токен, выданный сервером.
  6. Сервер получает запрос, а затем проверяет токен, переданный в клиентском запросе.Если проверка прошла успешно, он возвращает запрошенные данные клиенту.

В общем, после первого входа клиента, когда сервер снова получает http-запрос, он распознает только токен.Запросу нужно только приносить токен каждый раз.Сервер будет перехватывать все запросы, а затем проверять токен , Если это законно, оно будет выпущено, а если незаконно, оно вернет 401 (ошибка аутентификации). Springboot действительно проверяется по этому принципу.

Полное имя JWT: веб-токен json. Он шифрует информацию о пользователе в токен, и сервер не сохраняет никакую информацию о пользователе. Сервер проверяет правильность токена с помощью сохраненного ключа, если он правильный, проверка пройдена.

преимущества jwt:

  1. Краткость: его можно отправить через URL, параметры POST или в заголовке HTTP, потому что объем данных небольшой, а скорость передачи высокая;
  2. Автономность: загрузка может содержать информацию, необходимую пользователю, что позволяет избежать многократных запросов к базе данных;
  3. Поскольку токен хранится на клиенте в зашифрованном виде JSON, JWT является кросс-языковым, и в принципе поддерживается любая веб-форма;
  4. Нет необходимости сохранять информацию о сеансе на стороне сервера, что особенно удобно для распределенных микросервисов.

Недостатки jwt:

  1. Невозможно аннулировать выпущенные токены;
  2. Нелегко иметь дело с истечением срока действия данных.

Будьте осторожны и не пытайтесь заменить сеанс на jwt. В этом режиме традиционный механизм сеанса + cookie работает лучше. JWT не имеет состояния и распределен. На самом деле, пока он находится в пределах срока действия, он не может быть признан недействительным. Выход пользователя больше похож на выход клиента Токен на стороне сервера по-прежнему действителен, и вы по-прежнему можете войти в систему, пока используете этот токен. Другой проблемой является проблема с продлением. Использование токенов, несомненно, сделает обновление очень хлопотным. Конечно, вы также можете записать статус токена через Redis и обновить статус после того, как пользователь получит к нему доступ, но это делается для того, чтобы jwt не имел состояния. и их не нужно учитывать в традиционном механизме сеанса + cookie.

OAuth2-аутентификация

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

  1. Владелец ресурса: пользователь WeChat
  2. Сервис ресурсов: сервер WeChat
  3. Сторонние приложения: сервисные приложения, разработанные вами

Итак, мы видим, что Oauht2 использует сервер ресурсов для предоставления учетных данных доступа стороннему приложению, чтобы стороннее приложение могло получить учетную запись и пароль владельца ресурса, не зная учетной записи и пароля владельца ресурса на сервере ресурсов. Ресурс на сервере ресурсов. Давайте возьмем пример, который мы когда-то разработали для иллюстрации апплета WeChat:

  1. Пользователь WeChat 1 получает доступ к разработанному нами аплету, и апплет инициирует запрос авторизации на сервер авторизации WeChat, чтобы получить имя пользователя WeChat 1 на сервере WeChat.
  2. Сервер авторизации WeChat получает наш запрос на авторизацию, направляет пользователя для подтверждения авторизации и возвращает авторизацию нашему апплету.
  3. После того, как мы получим код авторизации, мы снова инициируем запрос токена доступа (содержащего app_id пользователя и т. д.) на сервер авторизации WeChat.
  4. Сервер авторизации WeChat проверяет нашу личность и код авторизации и выдает токен доступа access_code после прохождения проверки.
  5. После того, как мы получим токен доступа, мы инициируем запрос ресурсов на сервер ресурсов WeChat, то есть запрашиваем имя пользователя WeChat 1.

На основе класса шифрования и дешифрования

  • Симметричное шифрование

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

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

Общие алгоритмы: AES: длина ключа может быть 128, 192 и 256 бит, т. е. 16 байт, 24 байт и 32 байта. DES: Длина ключа 64 бита, 8 байт.

  • Асимметричное шифрование

Пара ключей состоит из открытого ключа и закрытого ключа. Закрытый ключ расшифровывает зашифрованные данные с открытым ключом, а открытый ключ расшифровывает зашифрованные данные с закрытым ключом (закрытый ключ и открытый ключ могут шифровать и расшифровывать друг друга).

  1. Минусы: медленно
  2. Плюсы: Безопасный

Общие алгоритмы: ЮАР Давайте посмотрим на класс инструментов, чтобы понять:

public class Demo {

    private static String src = "这是加密的字符串";

    private static RSAPublicKey rsaPublicKey;
    private static RSAPrivateKey rsaPrivateKey;

    static {
        // 1、初始化密钥
        KeyPairGenerator keyPairGenerator;
        try {
            keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(1024);// 64的整倍数
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
            rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
            System.out.println("Public Key : " + Base64.encodeBase64String(rsaPublicKey.getEncoded()));
            System.out.println("Private Key : " + Base64.encodeBase64String(rsaPrivateKey.getEncoded()));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    /**
     * 公钥加密,私钥解密
     */
    public static void pubEn2PriDe() throws Exception{
        //公钥加密
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] result = cipher.doFinal(src.getBytes());
        System.out.println("公钥加密,私钥解密 --加密: " + Base64.encodeBase64String(result));

        //私钥解密
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
        keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        result = cipher.doFinal(result);
        System.out.println("公钥加密,私钥解密 --解密: " + new String(result));
    }


    /**
     * 私钥加密,公钥解密
     */
    public static void priEn2PubDe() throws Exception{

        //私钥加密
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        byte[] result = cipher.doFinal(src.getBytes());
        System.out.println("私钥加密,公钥解密 --加密 : " + Base64.encodeBase64String(result));

        //公钥解密
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
        keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        result = cipher.doFinal(result);
        System.out.println("私钥加密,公钥解密   --解密: " + new String(result));
    }

    public static void main(String[] args) {
        try {
            pubEn2PriDe();  //公钥加密,私钥解密
            priEn2PubDe();  //私钥加密,公钥解密
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

результат: