Как заставить Сиро работать в среде Scala + Akka + Spray?
Я думаю, что я не правильно понимаю рабочий процесс. Я пишу веб-сервис в Scala с Apache Shiro и Stormpath. Мой процесс аутентификации пользователя выглядит так:
1) Получить пользовательские данные из запроса POST, проверить их с помощью Stormpath и, если все в порядке, перенаправить на какую-либо страницу:
pathPrefix("api") {
path("login") {
post {
AuthToken.fromRequest { (token: AuthToken) =>
stormpathAuth(token) { subj =>
log.info("Subj {}", subj.getPrincipal.toString)
redirect("/some/page", StatusCodes.Found)
}
}
}
}
В журналах все в порядке, Широ вернул мне правильный субъект с учетной записью Stormpath. Далее я хочу извлечь тему, чтобы использовать ее в коде:
pathPrefix("some") {
loggedInUser { subject =>
path("page") {
get {
complete {
html.render(Page.model)
}
}
} ..... other routes
loggedInUser
директива должна извлекать тему и проверять, аутентифицирована ли она, в противном случае перенаправить на форму входа. И проблема в том, что он всегда перенаправляет меня в форму входа, хотя в логах SubjectUtils.getSubject.getPrincipal
показывает правильный аккаунт.
обновленный
На самом деле спрей построен на вершине Акка. Так что я думаю, что проблема позади getSubject
реализация, которая в настоящее время зависит от среды ThreadLocal. Я искал по темам Shiro + Akka, но не нашел никакой полезной информации.
2 ответа
Это определенно возможно, но вы должны убедиться, что Subject
доступно для компонентов (актеров) Akka, так как они обрабатывают сообщения.
Я знаком с архитектурой Akka (модель актера / обмена сообщениями), но я сам не использовал Akka, поэтому, пожалуйста, примите следующий ответ в качестве наиболее вероятного ответа:
В традиционных приложениях и / или веб-приложениях на основе Shiro что- то отвечает за создание Subject
экземпляр, который отражает текущий вызывающий и / или запрос, а затем связывает его с выполняющимся в данный момент Thread
, Это гарантирует, что любые последующие вызовы во время выполнения этого потока SecurityUtils.getSubject()
функционировать правильно. Все это задокументировано в предметной документации Shiro (см. Разделы Subject.Builder и Thread Association).
Например, в веб-приложении ShiroFilter
выполняет эту настройку / привязывать / отменять логику автоматически для ServletRequest. Я подозреваю, что что-то (некоторый "каркасный" код или компонент) в приложении на основе Akka будет выполнять ту же логику setup/bind/unbind.
Теперь, с Akka, я вполне уверен, что вы могли бы использовать традиционный подход, основанный на потоках, как описано выше в документации (я думаю, что пользователи Play! Сделали это с успехом). Но другой интересный подход может быть доступен с неизменными сообщениями Akka:
Когда сообщение составлено, вы можете прикрепить к сообщению информацию, специфичную для субъекта (например, "заголовок" сообщения) с такими вещами, как Shiro PrincipalCollection и состояние аутентификации (аутентифицировано или нет) и всем остальным (состояние runAs, что угодно).
Затем, когда сообщение получено, эта информация будет использоваться в качестве входных данных для
Subject.Builder
создатьSubject
экземпляр, и этот экземпляр субъекта используется во время обработки сообщений. ShiroSubject
экземпляры очень легковесны и, как ожидается, будут создаваться и уничтожаться за запрос (или даже несколько раз за запрос, если необходимо), поэтому вам не нужно беспокоиться о накладных расходах Builder.Когда
Subject
встроен, вы можете либо связать, а затем отсоединить его с текущим выполняющимся потоком, или каждый субъект, который обрабатывает сообщение, может пройти через эту же логику в "рамочном" виде. Этот последний подход вообще не требует привязки к потоку, поскольку состояние субъекта теперь поддерживается на уровне сообщений, а не на уровне потоков.
Как свидетельство этого альтернативного (не основанного на потоках) подхода, новый плагин Shiro для ActiveMQ использует состояние соединения для хранения состояния Shiro, а не потоков. Точно так же, состояние сообщения может использоваться так же легко.
Просто отметьте, что при подходах, не основанных на потоках, вызывающие абоненты не могут вызывать SecurityUtils.getSubject()
приобрести Subject
пример. Им придется получить это по-другому.
Опять же, это мой лучший анализ того, как это может работать в средах обмена сообщениями (например, Akka), не используя сам Akka. Надеемся, что это даст вам достаточно информации, чтобы помочь вам решить эту проблему таким образом, который подходит для ваших случаев использования!
В продолжение этого вопроса, который был весьма полезен, когда я столкнулся с той же проблемой. Основная цель - предоставить дополнительную информацию о предлагаемой реализации Les Hazlewood. Другим хорошим альтернативным источником информации также является эта тема https://groups.google.com/forum/
Широ в настоящее время основан на потоках, что означает, что он привязывает информацию о субъекте к текущему потоку. Это проблема для Akka, поскольку она использует пулы потоков диспетчеров и рабочих.
По мере повторного использования потоков субъект просачивается из одного запроса в другой, что приводит к тому, что запрос, не прошедший проверку подлинности, обрабатывается как в подлинном, так и в обратном направлении.
Как и предполагал Les, возможное решение этой проблемы - отказаться от привязки потока и сохранить тему в сообщениях Akka. Чтобы это работало, это означает, что статические методы, предоставленные в SecurityUtils Широ, не могут быть использованы. Операции должны быть выполнены непосредственно над Субъектом. Кроме того, эта тема должна быть построена с использованием Subject.Builder.
Для этого вы можете обернуть ваши сообщения с темой,
case class ActorMessage(subject:Subject, value: Any)
object MessageSender {
def ? (actorRef: ActorRef, message: Any)(implicit subject: Subject): Future[Any] = {
val resultFuture = if (!message.isInstanceOf[ActorMessage]) {
val actorMessage = ActorMessage(subject, message)
actorRef ask actorMessage
} else actorRef ask message
for (result <- resultFuture) yield {
if (result.isInstanceOf[ActorMessage]) {
val actorMessageResp = result.asInstanceOf[ActorMessage]
actorMessageResp.value
} else result
}
}
}
и разверните их на актере, когда он получит сообщение. Или инициализировать тему, если это был акт записи запроса.
abstract class ShiroActor extends Actor {
implicit var shiroSubject: Subject = (new Subject.Builder).buildSubject
override def aroundReceive(receive: Actor.Receive, msg: Any): Unit = {
if (msg.isInstanceOf[ActorMessage]) {
val actorMessage = msg.asInstanceOf[ActorMessage]
shiroSubject = actorMessage.subject
receive.applyOrElse(actorMessage.value, unhandled)
} else {
shiroSubject = (new Subject.Builder).buildSubject
receive.applyOrElse(msg, unhandled)
}
}
}
Теперь, чтобы использовать его, реализованные акторы должны будут расширить ShiroActor и обмениваться сообщениями между акторами, вам придется использовать MessageSender вместо методов ActorRef ask или tell.
Для входа в тему, проверки прав доступа, ролей и т. Д. Теперь вы можете использовать тему, доступную на актера. Как это,
shiroSubject.login(new AuthenticationToken(principal, credentials))
Предполагается, что вход в систему выполняется для субъекта ввода запроса, а тема просто используется для проверки прав доступа для последующих участников. Но я уверен, что он может быть легко адаптирован для обновления сюрообъекта на актеров в двух направлениях.
Надеюсь, что это может быть полезным ресурсом, так как почти невозможно найти пример интеграции между этими двумя фреймворками.