Я приучил себя к базовой аутентификации HTTP .

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


Мое понимание до сих пор:

После некоторого чтения я понимаю, что:

  • сервер может запросить авторизацию для ресурса, вернув 401 (unauthorised)
  • WWW-Authenticate заголовок ответа определяет, что аутентификация, которая будет использоваться для доступа к этому ресурсу, будет Basic
  • HTTP Basic Authentication требует либо этого:
  • а) пользователь отправляет имя пользователя и пароль через консоль, созданную браузером; или это
  • б) после успешной отправки вручную такая же комбинация имени пользователя и пароля будет автоматически отправляться через Authorization заголовок запроса , добавляемый к каждому HTTP-запросу.

Все идет нормально.


Что нужно знать:

Я также понимаю, что с базовой аутентификацией HTTP возникли некоторые проблемы, которые со временем изменились, например:

  • некоторые браузеры больше не принимают синтаксис URL, например https://mylogin:mypassword@example.com/my-resource.html
  • если PHP запускается через CGI или FastCGI , тогда отправленные учетные данные для авторизации не будут переданы в $_SERVER['HTTP_AUTHORIZATION'], если не будет развернут взлом - наиболее распространенной рекомендацией является перезапись URL с помощью .htaccess mod_rewrite

И другие проблемы, которые сохраняются с самого начала, например:

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

Недостающий кусок головоломки:

Однако я все еще сбит с толку, потому что даже если пользователь (или заголовок запроса Authorization) отправил действительные учетные данные для аутентификации ... как сервер узнает, что они действительны?

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


Вопрос:

Как на самом деле аутентифицируются представленные учетные данные?

Где сервер сравнивает отправленные учетные данные с ... чем-нибудь ?


Бонусный вопрос :

NB Это связано с моим основным вопросом непосредственно выше, потому что использование мной параметров .htaccess и queryString для передачи учетных данных (см. ниже) приводит к развертыванию HTTP Базовая аутентификация полностью избыточна - если я пойду по этому пути, я могу передать учетные данные, используя только параметры .htaccess и queryString, и мне не нужно развертывать базовую аутентификацию HTTP вообще.

Чтобы обойти проблему CGI / FastCGI , я часто вижу варианты этих цитируемых строк .htaccess:

RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

Или

RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]

Или

SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0 

Хотя все мои попытки получить любой из этих методов для заполнения переменных среды PHP учетными данными оказались безуспешными.

Вместо этого я успешно развернул следующее (используя параметры queryString вместо переменных среды):

# WRITE HTTP BASIC AUTHENTICATION CREDENTIALS TO QUERY STRING
RewriteCond %{HTTP:Authorization} [NC]
RewriteCond %{QUERY_STRING} ^basicauth=login$ [NC]
RewriteRule ^my-document.php https://example.com/my-document.php?basicauth=login-submitted&credentials=%{HTTP:Authorization} [NC,L]

Который добавляет учетные данные как параметры queryString.

Я не недоволен своим собственным решением mod_rewrite, приведенным выше, но я озадачен тем, что вообще не могу заставить работать переменные среды.

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

(И если да, то какие записи мне нужно будет зарегистрировать в PHPInfo, чтобы подтвердить, что они действительно включены и восприимчивы к значениям, переданным им через mod_rewrite?)

1
Rounin 11 Окт 2021 в 14:03

2 ответа

Лучший ответ

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

Имя пользователя и пароль отправляются вам в виде открытого текста *. Так же, как если бы кто-то отправлял форму входа с полями username и password, вы можете решить, что с ними делать.

*: Да, они закодированы в base64. Но я хотел сказать, что они не хешированы, не зашифрованы или что-то в этом роде.

Итак, вы можете ...

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

Пример того, как это может выглядеть на сервере (в этом примере предполагается использование node.js, Koa, koa-router, Mongoose и bcrypt, и в целях упрощения предполагается, что ни имя пользователя, ни пароль не могут содержать двоеточие):

router.get('/protectedPage', async ctx => {
  const [authMethod, authData] = ctx.get('authorization')?.split(' ') ?? []
  
  if (authMethod === 'Basic') {
    try {
      const decoded = Buffer.from(authData, 'base64').toString()
      const [username, password] = decoded.split(':')
      
      // Find user in database and verify password
      const user = await User.findOne({ username })
      if (user && await bcrypt.compare(password, user.encryptedPassword)) {
        // User is authenticated now
        ctx.state.user = user
      }
    } catch (e) {
      console.error(`Failed to process auth header "${authData}"`, e)
    }
  }
  
  if (ctx.state.user) {
    return ctx.render('protectedPage')
  } else {
    return ctx.throw(401, null, {
      headers: { 'WWW-Authenticate': 'Basic realm="Protected Page"' }
    })
  }
})

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

(О выходе: я обычно перенаправляю людей на https://_logout_@example.com или что-то в этом роде, так что с этого момента запросы будут использовать это недопустимое имя пользователя и пустой пароль, снова вызывая 401. Учтите, что это не сработает, если страница находится в кеше, потому что кешированная версия все равно будет доставлена, поэтому в этом случае этот аспект может потребовать дополнительного рассмотрения - вероятно, аутентифицированные страницы все равно не следует кэшировать.)

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


О том, как перенаправить заголовок авторизации в PHP: Если что-то еще не настроено странным образом, установка CGIPassAuth on в конфигурации Apache должна быть всем, что нужно.

Из документации:

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

После этого переменная $_SERVER['HTTP_AUTHENTICATION'] должна быть установлена ​​правильно, и тогда PHP также может автоматически проанализирует заголовок и предоставит $_SERVER['PHP_AUTH_USER'] и $_SERVER['PHP_AUTH_PW'].

1
CherryDT 11 Окт 2021 в 15:12

Так что это только из моего понимания, но я довольно уверен в этом ...: Если вы отправляете базовую аутентификацию HTTP, вы указываете имя пользователя и пароль, которые автоматически зашифровываются в base64 и отправляются в заголовке, например: Basic ZG9kb3BhbmE6YXV0bw ==, который при получении в заголовке на другой стороне находится в значении авторизации заголовков, и он по-прежнему выглядит так: Базовый YmFzaWM6YXV0bw == :) => из этого вернется быстрое декодирование только base64: dodopana: auto - которые являются имя пользователя: пароль, который я дал на сообщение.

Все идет нормально?! :) Теперь основная аутентификация отправлена ​​и получена. следующий шаг - это не шаг сервера ... это само программное обеспечение API или конечной точки, которое, поскольку оно требует токен или имя пользователя и пароль ... должно проверять расшифрованную аутентификацию перед учетными данными поставщиков API.

Он просто проходит через зашифрованную строку, и сравнение и / или проверка указанного заголовка теперь находится в руках программного обеспечения API, или я должен сказать - программист должен проверить его ...

1
Shlomtzion 11 Окт 2021 в 11:23