Статьи

Безопасные сервисы JSON с Play Scala и SecureSocial

В ноябре прошлого года я отправился в Антверпен, чтобы выступить в Devoxx . После моего разговора по HTML5 с Play Scala ко мне подошел Маттиас Карлссон , и мы поговорили о том же выступлении на Jfokus в Стокгольме. Я согласился, и мы начали говорить подробности после того, как Триш и я вернулись в США.

Jfokus

Я написал эту статью на самолете между Денвером и Сиэтлом и буду прыгать через Северный полюс в Стокгольм через Исландию сегодня вечером. Последние пару недель я обновляю свой Play More! HTML5 / мобильное приложение, чтобы добавить некоторые новые функции. В частности, я хотел обновить до Play 2.0, создать службы JSON и добавить аутентификацию.

Обновление до Play 2.0
Моя попытка перейти на Play 2.0 заключалась в проверке исходного кода из GitHub , создании и установке снимка RC1. Когда я попытался обновить свое приложение и начал получать неудачные операции импорта, я обратился к Интернету (в частности, к StackOverflow), чтобы выяснить, хорошая ли это идея . Первый ответ на этот вопрос предложил мне остаться с 1.x.

Если это критически важный проект, который должен быть завершен до следующего марта 2012 года, я бы пошел с Play 1.x. Если это менее важный проект, который может быть отложен и который в любом случае не будет выпущен до марта 2012 года, попробуйте Play 2.0.

Пока я не планировал выпускать Play More! до Jfokus я решил, что обновление не добавило много к разговору. Кроме того, я не смог найти руководство по обновлению Play Scala 0.9.1 до Play 2.0, и у меня не было достаточно времени на его создание. Поэтому я решил придерживаться Play 1.2.4 и добавить несколько служб JSON для своего клиента iPhone.

Серверы JSON
Я нашел игру Мануэля Бернхардта ! Скала и JSON . Это привело меня к Джерксону , построенному теперь печально известной @coda . Я был в состоянии легко заставить вещи работать довольно быстро и написал следующий WorkoutService.scala:

package controllers.api

import play.mvc.Controller
import models._
import com.codahale.jerkson.Json._

object WorkoutService extends Controller {

  def workouts = {
    response.setContentTypeIfNotSet("application/json")
    generate(Workout.find().list())
  }
  def edit(id: Long) = {
    generate(Workout.byIdWithAthleteAndComments(id))
  }

  def create() = {
    var workout = params.get("workout", classOf[Workout])
    Workout.create(workout)
  }

  def save(id: Option[Long]) = {
    var workout = params.get("workout", classOf[Workout])
    Workout.update(workout)
  }

  def delete(id: Long) = {
    Workout.delete("id={id}").on("id" -> id).executeUpdate()
  }
}

Затем я добавил маршруты для моего нового API в conf / routs :

GET     /api/workouts               api.WorkoutService.workouts
GET     /api/workout/{id}           api.WorkoutService.edit
POST    /api/workout                api.WorkoutService.create
PUT     /api/workout/{id}           api.WorkoutService.save
DELETE  /api/workout/{id}           api.WorkoutService.delete

Затем я создал класс ApiTest.scala, который проверяет, работает ли первый метод должным образом.

import play.test.FunctionalTest
import play.test.FunctionalTest._
import org.junit._

class ApiTests extends FunctionalTest {
  
    @Test
    def testGetWorkouts() {
        var response = GET("/api/workouts");
        assertStatus(200, response);
        assertContentType("application/json", response)
        println(response.out)
    }
}

Я запустил «play test», открыл свой браузер на http: // localhost: 9000 / @ tests и щелкнул ApiTests -> Start, чтобы убедиться, что он работает. Все зеленые сделали меня счастливым.

Играть больше тестов API

Наконец, я написал несколько CoffeeScript и jQuery, чтобы пользователи могли удалять тренировки и убедиться, что функция удаления работает.

$('#delete').click ->
  $.ajax
    type: 'POST'
    url: $(this).attr('rel')
    error: ->
      alert('Delete failed, please try again.')
    success: (data) ->
      location.href = "/more"

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

В пятницу перед отъездом в Devoxx я увидел запрос на регистрацию модуля для SecureSocial .

SecureSocial с Play Scala
от README от SecureSocial :

SecureSocial позволяет добавить интерфейс аутентификации в ваше приложение, которое работает со службами на основе гибридных протоколов OAuth1, OAuth2, OpenID и OpenID + OAuth.

Он также предоставляет механизм имени пользователя и пароля для пользователей, которые не хотят использовать существующие учетные записи в других сетях.

В этом выпуске поддерживаются следующие службы:

  • Twitter (OAuth1)
  • Facebook (OAuth2)
  • Google (OpenID + OAuth Hybrid)
  • Yahoo (OpenID + OAuth Hybrid)
  • LinkedIn (OAuth1)
  • Foursquare (OAuth2)
  • MyOpenID (OpenID)
  • WordPress (OpenID)
  • Имя пользователя и пароль

Другими словами, это звучало как мечта, и я решил попробовать, как только найду время. Это время застало меня прошлым вечером в понедельник, и я отправил прямое сообщение @jaliss (автору модуля) через Twitter.

Работает ли Secure Social с Play Scala? Я хотел бы использовать его в моей игре «Больше»! проект.

Хорхе ответил 16 минут спустя, сказав, что он не использовал Play Scala и ему нужно будет провести некоторое исследование. В 8 часов вечера (через 1,5 часа после моего оригинального DM) Хорхе сработал образец и отправил его мне по электронной почте. 10 минут спустя я добавлял черту Secure в свой проект.

package controllers

import play.mvc._
import controllers.securesocial.SecureSocial

/*
 * @author Jorge Aliss <jaliss@gmail.com> of Secure Social fame.
 */
trait Secure {
  self: Controller =>

  @Before def checkAccess() {
    SecureSocial.DeadboltHelper.beforeRoleCheck()
  }

  def currentUser = {
    SecureSocial.getCurrentUser
  }
}

Я настроил Twitter и Username + Password в качестве моих провайдеров, добавив следующее в conf / application.conf .

securesocial.providers=twitter,userpass

Мне также пришлось настроить ряд свойств securesocial.twitter. *. Затем я удостоверился, что мои маршруты были осведомлены о SecureSocial, добавив следующее в начало conf / route :

  *       /auth               module:securesocial

Затем я указал это как зависимость в conf / dependencies.yml и запустил «play deps».

    - play -> securesocial 0.2.4

После добавления «with Secure» в мой контроллер Profile.scala я попытался получить доступ к его маршруту и ​​получил приглашение войти в систему. Мне сразу же показали ошибку об отсутствующем файле jQuery 1.5.2 в моей папке «javascripts», поэтому я добавил его и обрадовался, когда мне показали экран входа в систему. Мне пришлось добавить приложение в Twitter, чтобы использовать его серверы OAuth, но я был закачан, когда работала аутентификация по имени пользователя и паролю (в комплекте с регистрацией!), А также по Twitter.

Единственная проблема, с которой я столкнулся с SecureSocial, заключалась в том, что он не нашел реализацию по умолчанию UserService.Service SecureSocial при работе в режиме prod. Я смог обойти это, добавив реализацию SecureService.scala в мой проект и написав ее для общения с моей моделью спортсмена. Я не удосужился создать нового пользователя, когда он вошел в систему из Twitter, но это то, что я хочу сделать в будущем. Я также был рад узнать, что настройка представлений SecureSocial была очень легкой. Я просто скопировал их из модуля в представления моего приложения и вуаля!

package services

import play.db.anorm.NotAssigned
import play.libs.Codec
import collection.mutable.{SynchronizedMap, HashMap}
import models.Athlete
import securesocial.provider.{ProviderType, UserService, SocialUser, UserId}

class SecureService extends UserService.Service {
  val activations = new HashMap[String, SocialUser] with SynchronizedMap[String, SocialUser]

  def find(userId: UserId): SocialUser = {
    val user = Athlete.find("email={email}").on("email" -> userId.id).first()

    user match {
      case Some(user) => {
        val socialUser = new SocialUser
        socialUser.id = userId
        socialUser.displayName = user.firstName
        socialUser.email = user.email
        socialUser.isEmailVerified = true
        socialUser.password = user.password
        socialUser
      }
      case None => {
        if (!userId.provider.eq(ProviderType.userpass)) {
          var socialUser = new SocialUser
          socialUser.id = userId
          socialUser
        } else {
          null
        }
      }
    }
  }

  def save(user: SocialUser) {
    if (find(user.id) == null) {
      val firstName = user.displayName
      val lastName = user.displayName
      Athlete.create(Athlete(NotAssigned, user.email, user.password, firstName, lastName))
    }
  }

  def createActivation(user: SocialUser): String = {
    val uuid: String = Codec.UUID()
    activations.put(uuid, user)
    uuid
  }

  def activate(uuid: String): Boolean = {
    val user: SocialUser = activations.get(uuid).asInstanceOf[SocialUser]
    var result = false

    if (user != null) {
      user.isEmailVerified = true
      save(user)
      activations.remove(uuid)
      result = true
    }

    result
  }

  def deletePendingActivations() {
    activations.clear()
  }
}

 

Хорхе оказал большую помощь в удовлетворении моих потребностей в аутентификации, и он даже написал черту BasicAuth.scala для реализации базовой аутентификации в моих службах JSON.

package controllers

import _root_.securesocial.provider.{UserService, ProviderType, UserId}
import play._
import play.mvc._
import play.libs.Crypto

import controllers.securesocial.SecureSocial

/*
 * @author Jorge Aliss <jaliss@gmail.com> of Secure Social fame.
 */
trait BasicAuth {
  self: Controller =>

  @Before def checkAccess = {
    if (currentUser != null) {
      // this allows SecureSocial.getCurrentUser() to work.
      renderArgs.put("user", currentUser)
      Continue
    }

    val realm =
      Play.configuration.getProperty("securesocial.basicAuth.realm", "Unauthorized")

    if (request.user == null || request.password == null) {
      Unauthorized(realm)
    } else {
      val userId = new UserId
      userId.id = request.user
      userId.provider = ProviderType.userpass
      val user = UserService.find(userId)

      if (user == null ||
        !Crypto.passwordHash(request.password).equals(user.password)) {
        Unauthorized(realm)
      } else {
        // this allows SecureSocial.getCurrentUser() to work.
        renderArgs.put("user", user)
        Continue
      }
    }
  }

  def currentUser = {
    SecureSocial.getCurrentUser()
  }
}

Резюме
Мой последний опыт разработки с использованием Scala и использования Play для создания моего приложения был очень увлекательным. Несмотря на то, что возникали проблемы с перезагрузкой классов, а также версий Scala и Scalate , я смог добавить нужные функции. Я не смог обновиться до Play 2.0, но я не старался изо всех сил и решил, что лучше подождать, пока не будет опубликовано руководство по обновлению.

Я рад рассказать о своем последнем опыте разработчикам из Jfokus на этой неделе. Кроме того, на конференции обсуждались вопросы Play 2.0 , CoffeeScript , HTML5 , Scala и Scalate . Я надеюсь посетить многие из них и научиться новым трюкам, чтобы улучшить свои навыки и мое приложение.

Обновление: разработчики Delving написали статью о
миграции в Play 2 . Хотя он не предоставляет конкретных деталей о том, что им нужно было изменить, у него есть хорошая информация о том, сколько времени это заняло и на что нужно обратить внимание.

 

От http://raibledesigns.com/rd/entry/secure_json_services_with_play