Метод проверки подлинности с использованием BasicAuth

#scala #spray

#scala #спрей

Вопрос:

Распылять сложно!! Теперь я знаю, что моих знаний о протоколе HTTP недостаточно, а разработка API непростая. Тем не менее, я все еще очень хочу, чтобы мое практическое приложение работало. Я пишу эту аутентификацию для POST/PUT/DELETE метода. Похоже, что есть по крайней мере два способа сделать это: BasicAuth или написать пользовательскую директиву.

Я нашел эту статью:

BasicAuth:https://github.com/jacobus/s4/blob/master/src/main/scala/s4/rest/S4Service.scala

Я пробую это, потому что это выглядит просто.

Этапы компиляции и запуска выполнены нормально, и сервер запущен. Однако, когда я пытаюсь отправить запрос PUT для тестирования реализации (используя Httpie на Python: http PUT 127.0.0.1:8080/sec-company/casper username=username token=123 ), обратная связь: HTTP/1.1 404 Not Found

Вот мой маршрут:

 pathPrefix("sec-company") {
        path("casper") {
          //first examine username and token
          authenticate(BasicAuth(CustomUserPassAuthenticator, "company-security")) {userProfile =>
            post { ctx =>
              entity(as[Company.Company]) { company =>
                complete {
                  company
                }
              }
            }
   }
  

Вот моя реализация UserPassAuthenticator :

 object CustomUserPassAuthenticator extends UserPassAuthenticator[UserProfile] {
    def apply(userPass: Option[UserPass]) = Promise.successful(
      userPass match {
        case Some(UserPass(user, token)) => getUserProfile(user, token)
        case _ => None
      }
    ).future
  }
  

Прежде всего, является ли это правильным способом реализации аутентификации? Во-вторых, где UserPassAuthenticator найти имя пользователя и пароль?? Могу ли я отправить лучший HTTP-заголовок, отличный от 404 , для указания неудачной аутентификации?

Если это далеко от правильного, есть ли какое-либо руководство по аутентификации, которому я могу следовать? Шаблоны Spray от TypeSafe больше касаются общих шаблонов и меньше функциональности Spray!

Спасибо!

Ответ №1:

У меня была та же проблема, даже после просмотраhttps://github.com/spray/spray/wiki/Authentication-Authorization (в котором говорится, что он предназначен для более старой версии Akka, но, похоже, он все еще применяется) Я пришел к следующему:

 trait Authenticator {
  def basicUserAuthenticator(implicit ec: ExecutionContext): AuthMagnet[AuthInfo] = {
    def validateUser(userPass: Option[UserPass]): Option[AuthInfo] = {
      for {
        p <- userPass
        user <- Repository.apiUsers(p.user)
        if user.passwordMatches(p.pass)
      } yield AuthInfo(user)
    }

    def authenticator(userPass: Option[UserPass]): Future[Option[AuthInfo]] = Future { validateUser(userPass) }

    BasicAuth(authenticator _, realm = "Private API")
  }
}
  

Я добавляю эту черту к субъекту, который запускает маршруты, а затем вызываю ее следующим образом:

 runRoute(
  pathPrefix("api") {
    authenticate(basicUserAuthenticator) { authInfo =>
      path("private") {
        get {
          authorize(authInfo.hasPermission("get") {
            // ... and so on and so forth
          }
        }
      }
    }
  }
}
  

AuthInfo Объект, возвращаемый validateUser методом, передается в качестве параметра замыканию, заданному authorize методу. Вот он:

 case class AuthInfo(user: ApiUser) {
  def hasPermission(permission: String) = user.hasPermission(permission)
}
  

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

Что касается вашего вопроса, при использовании БАЗОВОЙ аутентификации HTTP ( BasicAuth объекта) учетные данные передаются в запросе в Authorization: заголовке. Ваша HTTP-библиотека должна позаботиться о создании этого для вас. Согласно стандарту HTTP, сервер должен возвращать код состояния 401, если аутентификация была неправильной или не была предоставлена, или код состояния 403, если аутентификация была правильной, но у пользователя нет разрешений на просмотр содержимого.

Комментарии:

1. Repository.apiUsers(login: String) это то, как я получаю свой пользовательский объект из своей базы данных, учитывая их логин. В вашем случае вам нужно будет перейти в базу данных или где бы вы ни хранили учетные данные своих пользователей и получить их оттуда. user.passwordMatches — это метод в моем классе User, который проверяет, соответствует ли данный пароль паролю пользователя (поскольку он хранится в хэшированном и соленом виде). Я надеюсь, что это понятно!

2. Большое вам спасибо! Это намного понятнее. Но как AuthInfo должно выглядеть? Как я могу также предоставить пользователю возможность отправлять имя пользователя и пароль (или просто токен аутентификации) из тела СООБЩЕНИЯ или запроса GET?

3. Я пропустил это… Я отредактировал ответ, чтобы добавить информацию о AuthInfo . Теперь, когда я смотрю на это, я действительно должен превратить это в сообщение в блоге 🙂

4. Вы должны опубликовать ссылку здесь! Я прочитаю это! Это действительно важная тема. Кстати, я попробовал это решение, но таким образом, если аутентификация не удалась, Spray не возвращает 401 ответ, и вы должны сделать это самостоятельно, используя complete(401, "failed")

5. Вот сообщение. Наслаждайтесь 🙂 mcamou.github.io/blog/2014/07/07 /…