#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 /…