Вам не нужен паспорт.js — руководство по аутентификации на node.js

Node.js
Вам не нужен паспорт.js — руководство по аутентификации на node.js

🛑 Вам не нужен паспорт.js — руководство по аутентификации на node.js ✌️

Введение

Сторонние сервисы аутентификации, такие как Google Firebase, AWS Cognito и Auth0, становятся все более и более популярными, а универсальные решения, такие как pass.js, стали отраслевым стандартом, но это обычная ситуация, когда разработчики не знают, какие части участвуют в полном процессе сертификации.

Эта серия статей об аутентификации в node.js предназначена для того, чтобы вы поняли такие понятия, как JSON Web Token (JWT), вход в учетную запись социальной сети (OAuth2), олицетворение пользователя (администратор может войти в систему как конкретный пользователь без пароля).

Разумеется, в конце статьи для вас также подготовлена ​​полная кодовая база процесса аутентификации node.js, которая размещена на GitHub, и вы можете использовать ее как основу для собственных проектов.

Необходимые знания ✍️

Прежде чем читать, нужно понять:

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

На момент написания этой статьи я считал Argon2 лучшим алгоритмом шифрования, пожалуйста, не используйте простые алгоритмы шифрования, такие как SHA256, SHA512 или MD5.

Если вы заинтересованы в этом, ознакомьтесь с этой замечательной статьейвыбор алгоритма хеширования пароля.

Напишите программу регистрации 🥇

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

Совет: вы можете перейти к предыдущей статье, чтобы понять структуру проекта node.js.Пуленепробиваемая архитектура проекта node.js 🛡️

import * as argon2 from 'argon2';

class AuthService {
  public async SignUp(email, password, name): Promise<any> {
    const passwordHashed = await argon2.hash(password);

    const userRecord = await UserModel.create({
      password: passwordHashed,
      email,
      name,
    });
    return {
      // 绝对不要返回用户的密码!!!!
      user: {
        email: userRecord.email,
        name: userRecord.name,
      },
    }
  }
}

В базе данных запись для этого пользователя выглядит так:

User record - Database MongoDB
Robo3T for MongoDB

Напишите другую программу входа 🥈

Sign-In Diagram

Когда пользователь хочет войти в систему, происходит следующее:

Клиент отправляет парныеОбщедоступная ID1IFICATIONиЗакрытый ключ

  • Сервер ищет в базе данных пользовательские записи на основе отправленных электронных писем.

  • Если он найден, сервер хеширует полученный пароль и сравнивает его с хешированным паролем в базе данных.

  • Если два значения хэша совпадают, сервер отправляет JSON Web Token (JWT).

Этот JWT является временным ключом, и клиент должен приносить этот токен каждый раз, когда отправляет запрос.

import * as argon2 from 'argon2';

class AuthService {
  public async Login(email, password): Promise<any> {
    const userRecord = await UserModel.findOne({ email });
    if (!userRecord) {
      throw new Error('User not found')
    } else {
      const correctPassword = await argon2.verify(userRecord.password, password);
      if (!correctPassword) {
        throw new Error('Incorrect password')
      }
    }

    return {
      user: {
        email: userRecord.email,
        name: userRecord.name,
      },
      token: this.generateJWT(userRecord),
    }
  }
}

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

Далее мы обсудим, как создать JWT.

Но что такое JWT? 👩‍🏫

Веб-токен JSON или JWT — это закодированный объект JSON, хранящийся в виде строки или токена.

Вы можете думать, что это альтернатива для cookie.

Токен имеет следующие три раздела (отмечены разными цветами)

JSON Web Token example

Данные в JWT могут быть без секретного ключа (Secret)илиПодпись** расшифровывается на стороне клиента.

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

Как создать JWT в node.js? 🏭

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

используяjsonwebtokenС помощью этой библиотеки (вы можете найти ее на npmjs.com) мы можем создать JWT.

import * as jwt from 'jsonwebtoken'
class AuthService {
  private generateToken(user) {

    const data =  {
      _id: user._id,
      name: user.name,
      email: user.email
    };
    const signature = 'MySuP3R_z3kr3t';
    const expiration = '6h';

    return jwt.sign({ data, }, signature, { expiresIn: expiration });
  }
}

Важно никогда не включать конфиденциальную информацию о пользователе в закодированные данные.

Приведенная выше переменная подписи на самом деле является секретом, используемым для создания JWT, и вам нужно убедиться, что подпись не будет утечек.

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

Защита конечных точек и проверка JWT ⚔️

Теперь интерфейс должен приносить JWT с каждым запросом на доступ к защищенной конечной точке.

Хорошая практика включена в заголовок запроса JWT, обычно в заголовке авторизации (заголовок авторизации).

Теперь нам нужно создать промежуточное ПО Express в серверной части.

промежуточное ПО isAuth

import * as jwt from 'express-jwt';

// 我们假定 JWT 将会在 Authorization 请求头上,但是它也可以放在 req.body 或者 query 参数中,你只要根据业务场景选个合适的就好
const getTokenFromHeader = (req) => {
  if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
    return req.headers.authorization.split(' ')[1];
  }
}

export default jwt({
  secret: 'MySuP3R_z3kr3t', // 必须和上一节的代码的 signature 一样

  userProperty: 'token', // this is where the next middleware can find the encoded data generated in services/auth:generateToken -> 'req.token'

  getToken: getTokenFromHeader, // 从 request 中获取到 auth token 的方法
})

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

export default (req, res, next) => {
 const decodedTokenData = req.tokenData;
 const userRecord = await UserModel.findOne({ _id: decodedTokenData._id })

 req.currentUser = userRecord;

 if(!userRecord) {
   return res.status(401).end('User not found')
 } else {
   return next();
 }
}

Теперь вы можете перейти к маршруту, запрошенному пользователем

  import isAuth from '../middlewares/isAuth';
  import attachCurrentUser from '../middlewares/attachCurrentUser';
  import ItemsModel from '../models/items';

  export default (app) => {
    app.get('/inventory/personal-items', isAuth, attachCurrentUser, (req, res) => {
      const user = req.currentUser;

      const userItems = await ItemsModel.find({ owner: user._id });

      return res.json(userItems).status(200);
    })
  }

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

Почему JWT безопасен?

Когда вы читаете это, вы обычно думаете об этом вопросе:

В: Если данные JWT могут быть декодированы в клиенте, могут ли другие изменить идентификатор пользователя или другие данные?

A: Хотя вы можете легко декодировать JWT, невозможно закодировать новые измененные данные без секрета, когда JWT был сгенерирован.

Также по этой причине никогда не раскрывайте секрет.

Наш сервер будет вIsAuthэто использовалосьexpress-jwtПроверьте ключ в промежуточном программном обеспечении библиотеки.

Теперь, когда мы понимаем, как работают JWT, давайте перейдем к интересной функции.

Как выдать себя за пользователя 🕵️

Олицетворение пользователя — это метод входа в систему в качестве определенного пользователя без необходимости ввода пароля пользователя.

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

Нет необходимости знать пароль пользователя, просто создайте JWT с правильным ключом и необходимой информацией о пользователе.

Давайте создадим путь для создания JWT, который имитирует создание входа для определенного пользователя. Этот путь может использоваться только учетной записью суперадминистратора.

Прежде всего, нам нужно создать роль более высокого уровня для суперадминистратора, способов много, самый простой — зайти напрямую в базу и добавить поле «роль» в запись о пользователе.

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

export default (requiredRole) => {
  return (req, res, next) => {
    if(req.currentUser.role === requiredRole) {
      return next();
    } else {
      return res.status(401).send('Action not allowed');
    }
  }
}

Это промежуточное ПО должно быть размещено вisAuthиattachCurrentUserпосле.

Наконец, этот путь сгенерирует JWT, способный олицетворять пользователя.

  import isAuth from '../middlewares/isAuth';
  import attachCurrentUser from '../middlewares/attachCurrentUser';
  import roleRequired from '../middlwares/roleRequired';
  import UserModel from '../models/user';

  export default (app) => {
    app.post('/auth/signin-as-user', isAuth, attachCurrentUser, roleRequired('super-admin'), (req, res) => {
      const userEmail = req.body.email;

      const userRecord = await UserModel.findOne({ email: userEmail });

      if(!userRecord) {
        return res.status(404).send('User not found');
      }

      return res.json({
        user: {
          email: userRecord.email,
          name: userRecord.name
        },
        jwt: this.generateToken(userRecord)
      })
      .status(200);
    })
  }

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

Конечно, поскольку пароль не требуется, безопасность этого пути должна гарантироваться промежуточным программным обеспечением roleRequired.

Вывод

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

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

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

Вот пример репозитория 🔬

использованная литература

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


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