2016-10-05 4 views
0

Я пытаюсь добавить аутентификацию в одно приложение для платформы Play Framework.Play Framework Аутентификация в одностраничном приложении

То, что я хотел бы иметь что-то вроде:

def unsecured = Action { 
    Ok("This action is not secured") 
} 

def secured = AuthorizedAction { 
    // get the authenticated user's ID somehow 
    Ok("This action is secured") 
} 

Для традиционного веб-приложение, я уже сделал это, следуя Play Framework документы:

def authenticate = Action { implicit request => 
    loginForm.bindFromRequest.fold(
    formWithErrors => BadRequest(views.html.login(formWithErrors)), 
    user => { 
     Redirect(routes.Application.home).withSession(Security.username -> user._1) 
    } 
) 
} 

def logout = Action { 
    Redirect(routes.Auth.login).withNewSession.flashing(
    "success" -> "You are now logged out." 
) 
} 

и Авторизованный Действие расширяя ActionBuilder следующим образом:

object AuthorizedAction extends ActionBuilder[Request] with Results { 

    /** 
    * on auth success: proceed with the request 
    * on auth failure: redirect to login page with flash 
    */ 
    def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = { 
    // TODO: is "isDefined" enough to determine that user is logged in? 
    if(request.session.get("username").isDefined) { 
     block(request) 
    } 
    else { 
     Future.successful(Redirect(routes.Auth.login).flashing(
     "failure" -> "You must be logged in to access this page." 
    )) 
    } 
    } 
} 

Для одностраничных приложений h Тем не менее, этот подход больше не работает.

В этой статье Джеймс Уорд объясняет, как новый подход должен быть разработан и включает в себя реализацию Java: Securing SPA and rest services

Реализация была переделана в Scala Мариуса Soutier: Securing SPA in Scala

В своем примере, он реализует признак безопасности:

trait Security { self: Controller => 

    val cache: CacheApi 

    val AuthTokenHeader = "X-XSRF-TOKEN" 
    val AuthTokenCookieKey = "XSRF-TOKEN" 
    val AuthTokenUrlKey = "auth" 

    /** Checks that a token is either in the header or in the query string */ 
    def HasToken[A](p: BodyParser[A] = parse.anyContent)(f: String => Long => Request[A] => Result): Action[A] = 
    Action(p) { implicit request => 
     val maybeToken = request.headers.get(AuthTokenHeader).orElse(request.getQueryString(AuthTokenUrlKey)) 
     maybeToken flatMap { token => 
     cache.get[Long](token) map { userid => 
      f(token)(userid)(request) 
     } 
     } getOrElse Unauthorized(Json.obj("err" -> "No Token")) 
    } 

} 

Функции теперь обеспеченное как это вместо простого действия:

def ping() = HasToken() { token => userId => implicit request => 
    user.findByID (userId) map { user => 
    Ok(Json.obj("userId" -> userId)).withToken(token -> userId) 
    } getOrElse NotFound (Json.obj("err" -> "User Not Found")) 
} 

где .withToken определяется как:

implicit class ResultWithToken(result: Result) { 
    def withToken(token: (String, Long)): Result = { 
    cache.set(token._1, token._2, CacheExpiration) 
    result.withCookies(Cookie(AuthTokenCookieKey, token._1, None, httpOnly = false)) 
    } 

    def discardingToken(token: String): Result = { 
    cache.remove(token) 
    result.discardingCookies(DiscardingCookie(name = AuthTokenCookieKey)) 
    } 
} 

Я не нравится, как комплекс функция «пинг» выше стал, и предпочел бы использовать построитель Action (как в первом примере) , где аут-аут обнаруживается и обрабатывается в одной точке. (на данный момент, если я хочу защитить функции ping2 и ping3, каждый из них должен проверить, найден ли пользователь и что он имеет дело с «не найденным» случаем)

Я попытался собрать вдохновителя, вдохновленного по реализации Marius, в частности его использование cacheApi, которое необходимо.

Однако AuthorizedAction является объектом, а cacheApi необходимо вводить (поэтому необходимо изменить объект на одноэлементный класс) или не может быть объявлен в объекте без определения.

Я также чувствую, что AuthorizedAction должен оставаться объектом, для того, чтобы использоваться в качестве:

def secured = AuthorizedAction { 

ли кто-нибудь, пожалуйста, прояснить путаницу, и, возможно, помочь с некоторыми деталями реализации?

спасибо

ответ

1

Самый простой способ, на мой взгляд, это пойти с ActionBuilder. Вы можете определить конструктор действий как класс (и передать ему некоторые зависимости) или как объект.

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

// You can add other useful information here 
case class AuthorizedRequest[A](request: Request[A], user: User) extends WrappedRequest(request) 

Теперь определите ActionBuilder

class AuthorizedAction(userService: UserService) extends ActionBuilder[AuthorizedRequest] { 

    override def invokeBlock[A](request: Request[A], block: (AuthorizedRequest[A]) ⇒ Future[Result]): Future[Result] = { 
    request.headers.get(AuthTokenHeader).orElse(request.getQueryString(AuthTokenUrlKey)) match { 
     case Some(token) => userService.findByToken(token).map { 
     case Some(user) => 
      val req = AuthorizedRequest(request, user) 
      block(req) 
     case None => Future.successful(Results.Unauthorized) 
     } 
     case None => Future.successful(Results.Unauthorized) 
    } 

    } 
} 

Теперь вы можете использовать его в контроллере :

val authorizedAction = new AuthorizedAction(userService) 
def ping = authorizedAction { request => 
    Ok(Json.obj("userId" -> request.user.id)) 
} 
+0

благодарит за ответ vdebergue. В моем случае метод «findByToken» находится в однопользовательском классе UserService, который необходимо ввести в «AuthorizedAction». Однако, если я это сделаю, Play рассчитывает указать userService в вызове authorizedAction: val authorizedAction = new AuthorizedAction (/ * указать службу пользователя здесь * /). Любая идея, как инъекция зависимости будет работать с вашим подходом? Также есть какая-либо причина для круглых скобок «()« следующий »class AuthorizedAction()»? – Khorkhe

+0

Я обновил свой пример, введя userService в 'AuthorizedAction'. Я не использую 'Guice', но если вы это сделаете, было бы легко адаптировать код для его использования. Скобки после 'AuthorizedAction' не нужны в первом примере. – vdebergue

+0

принято, хотя модифицировано для моего конкретного случая/нравится. большое спасибо! – Khorkhe

Смежные вопросы