Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Play2.x Authentication and Authorization module

License

NotificationsYou must be signed in to change notification settings

t2v/play2-auth

Repository files navigation

![Gitter](https://badges.gitter.im/Join Chat.svg)

This module offers Authentication and Authorization features to Play2.x applications

Scaladoc

  • play2-auth scaladoc
  • play2-auth-social scaladoc
  • play2-auth-test scaladoc

Target

This module targets theScala version ofPlay2.x.

Motivation

More secure than Play2.x's Security trait

The existingSecurity trait in Play2.x API does not define an identifier that identifies a user.

If you use an Email or a userID as an identifier,users can not invalidate their session if the session cookie is intercepted.

This module creates a unique SessionID using a secure random number generator.Even if the sessionId cookie is intercepted, users can invalidate the session by logging in again.Your application can expire sessions after a set time limit.

Flexiblity

Since theSecurity trait in Play2.x API returnsAction,complicated action methods wind up deeply nested.

Play2x-Auth provides a way of composition.

Previous Version

for Play2.3.x, Please seeprevious version 0.13.5 README

for Play2.2.x, Please seeprevious version 0.11.1 README

Attention

Since Play2.3'sSimpleResult is renamed toResult, The play2.auth trait signatures are changed at version 0.12.0

Since Play2.2'sResult is deprecated, The play2.auth trait signatures are changed at version 0.11.0

Installation

Add dependency declarations into yourBuild.scala orbuild.sbt file:

  • for Play2.4.x

      "jp.t2v" %% "play2-auth"        % "0.14.2",  "jp.t2v" %% "play2-auth-social" % "0.14.2", // for social login  "jp.t2v" %% "play2-auth-test"   % "0.14.2" % "test",  play.sbt.Play.autoImport.cache // only when you use default IdContainer

For example yourBuild.scala might look like this:

valappDependencies=Seq("jp.t2v"%%"play2-auth"%"0.14.2","jp.t2v"%%"play2-auth-social"%"0.14.2","jp.t2v"%%"play2-auth-test"%"0.14.2"%"test",    play.sbt.Play.autoImport.cache  )

Usage

  1. First create a trait that extendsjp.t2v.lab.play2.auth.AuthConfig inapp/controllers.

    // Exampleimportjp.t2v.lab.play2.auth._traitAuthConfigImplextendsAuthConfig {/**   * A type that is used to identify a user.   * `String`, `Int`, `Long` and so on.*/typeId=String/**   * A type that represents a user in your application.   * `User`, `Account` and so on.*/typeUser=Account/**   * A type that is defined by every action for authorization.   * This sample uses the following trait:   *   * sealed trait Role   * case object Administrator extends Role   * case object NormalUser extends Role*/typeAuthority=Role/**   * A `ClassTag` is used to retrieve an id from the Cache API.   * Use something like this:*/validTag:ClassTag[Id]= classTag[Id]/**   * The session timeout in seconds*/valsessionTimeoutInSeconds:Int=3600/**   * A function that returns a `User` object from an `Id`.   * You can alter the procedure to suit your application.*/defresolveUser(id:Id)(implicitctx:ExecutionContext):Future[Option[User]]=Account.findById(id)/**   * Where to redirect the user after a successful login.*/defloginSucceeded(request:RequestHeader)(implicitctx:ExecutionContext):Future[Result]=Future.successful(Redirect(routes.Message.main))/**   * Where to redirect the user after logging out*/deflogoutSucceeded(request:RequestHeader)(implicitctx:ExecutionContext):Future[Result]=Future.successful(Redirect(routes.Application.login))/**   * If the user is not logged in and tries to access a protected resource then redirect them as follows:*/defauthenticationFailed(request:RequestHeader)(implicitctx:ExecutionContext):Future[Result]=Future.successful(Redirect(routes.Application.login))/**   * If authorization failed (usually incorrect password) redirect the user as follows:*/overridedefauthorizationFailed(request:RequestHeader,user:User,authority:Option[Authority])(implicitcontext:ExecutionContext):Future[Result]= {Future.successful(Forbidden("no permission"))  }/**   * A function that determines what `Authority` a user has.   * You should alter this procedure to suit your application.*/defauthorize(user:User,authority:Authority)(implicitctx:ExecutionContext):Future[Boolean]=Future.successful {    (user.role, authority)match {case (Administrator, _)=>truecase (NormalUser,NormalUser)=>truecase _=>false    }  }/**   * (Optional)   * You can custom SessionID Token handler.   * Default implementation use Cookie.*/overridelazyvaltokenAccessor=newCookieTokenAccessor(/*     * Whether use the secure option or not use it in the cookie.     * Following code is default.*/    cookieSecureOption= play.api.Play.isProd(play.api.Play.current),    cookieMaxAge=Some(sessionTimeoutInSeconds)  )}
  2. Next create aController that defines both login and logout actions.ThisController mixes in thejp.t2v.lab.play2.auth.LoginLogout trait andthe trait that you created in first step.

    objectApplicationextendsControllerwithLoginLogoutwithAuthConfigImpl {/** Your application's login form.  Alter it to fit your application*/valloginForm=Form {    mapping("email"-> email,"password"-> text)(Account.authenticate)(_.map(u=> (u.email,"")))      .verifying("Invalid email or password", result=> result.isDefined)  }/** Alter the login page action to suit your application.*/deflogin=Action {implicit request=>Ok(html.login(loginForm))  }/**   * Return the `gotoLogoutSucceeded` method's result in the logout action.   *   * Since the `gotoLogoutSucceeded` returns `Future[Result]`,   * you can add a procedure like the following.   *   *   gotoLogoutSucceeded.map(_.flashing(   *     "success" -> "You've been logged out"   *   ))*/deflogout=Action.async {implicit request=>// do something...    gotoLogoutSucceeded  }/**   * Return the `gotoLoginSucceeded` method's result in the login action.   *   * Since the `gotoLoginSucceeded` returns `Future[Result]`,   * you can add a procedure like the `gotoLogoutSucceeded`.*/defauthenticate=Action.async {implicit request=>    loginForm.bindFromRequest.fold(      formWithErrors=>Future.successful(BadRequest(html.login(formWithErrors))),      user=> gotoLoginSucceeded(user.get.id)    )  }}
  3. Lastly, mixjp.t2v.lab.play2.auth.AuthElement trait and the trait that was created in the first stepinto your Controllers:

    objectMessageextendsControllerwithAuthElementwithAuthConfigImpl {// The `StackAction` method//    takes `(AuthorityKey, Authority)` as the first argument and//    a function signature `RequestWithAttributes[AnyContent] => Result` as the second argument and//    returns an `Action`// The `loggedIn` method//     returns current logged in userdefmain=StackAction(AuthorityKey->NormalUser) {implicit request=>valuser= loggedInvaltitle="message main"Ok(html.message.main(title))  }deflist=StackAction(AuthorityKey->NormalUser) {implicit request=>valuser= loggedInvaltitle="all messages"Ok(html.message.list(title))  }defdetail(id:Int)=StackAction(AuthorityKey->NormalUser) {implicit request=>valuser= loggedInvaltitle="messages detail"Ok(html.message.detail(title+ id))  }// Only Administrator can execute this action.defwrite=StackAction(AuthorityKey->Administrator) {implicit request=>valuser= loggedInvaltitle="write message"Ok(html.message.write(title))  }}

Test

play2.auth provides test module at version 0.8

You can useFakeRequest with logged-in status.

packagetestimportorg.specs2.mutable._importplay.api.test._importplay.api.test.Helpers._importcontrollers.{AuthConfigImpl,Messages}importjp.t2v.lab.play2.auth.test.Helpers._classApplicationSpecextendsSpecification {objectconfigextendsAuthConfigImpl"Messages" should {"return list when user is authorized" innewWithApplication {valresult=Messages.list(FakeRequest().withLoggedIn(config)(1))      contentType(result) must equalTo("text/html")    }  }}
  1. Importjp.t2v.lab.play2.auth.test.Helpers._

  2. Define instance what is mixed-inAuthConfigImpl

     object config extends AuthConfigImpl
  3. CallwithLoggedIn method onFakeRequest

    • first argument:AuthConfigImpl instance.
    • second argument: user ID of the user who is logged-in at this request

It makes enable to test controllers with play2.auth

Advanced usage

Changing the authorization depending on the request parameters.

For example, a Social networking application has a function to edit messages.

A user must be able to edit their own messages but not other people's messages.

To achieve this you could defineAuthority as aFunction:

traitAuthConfigImplextendsAuthConfig {// Other setup is omitted.typeAuthority=User=>Future[Boolean]defauthorize(user:User,authority:Authority)(implicitctx:ExecutionContext):Future[Boolean]= authority(user)}
objectApplicationextendsControllerwithAuthElementwithAuthConfigImpl {privatedefsameAuthor(messageId:Int)(account:Account):Future[Boolean]=Message.getAutherAsync(messageId).map(_== account)defedit(messageId:Int)=StackAction(AuthorityKey-> sameAuthor(messageId)) {implicit request=>valuser= loggedInvaltarget=Message.findById(messageId)Ok(html.message.edit(messageForm.fill(target)))  }}

Returning to the originally requested page after login

When an unauthenticated user requests access to page requiring authentication,you first redirect the user to the login page, then, after the user successfully logs in, you redirect the user to the page they originally requested.

To achieve this changeauthenticationFailed andloginSucceeded:

traitAuthConfigImplextendsAuthConfig {// Other settings are omitted.defauthenticationFailed(request:RequestHeader)(implicitctx:ExecutionContext):Future[Result]=Future.successful(Redirect(routes.Application.login).withSession("access_uri"-> request.uri))defloginSucceeded(request:RequestHeader)(implicitctx:ExecutionContext):Future[Result]= {valuri= request.session.get("access_uri").getOrElse(routes.Message.main.url.toString)Future.successful(Redirect(uri).withSession(request.session-"access_uri"))  }}

Changing the display depending on whether the user is logged in

If you want to display the application's index differently to non-logged-in usersand logged-in users, you can useOptionalAuthElement instead ofAuthElement:

objectApplicationextendsControllerwithOptionalAuthElementwithAuthConfigImpl {// maybeUser is an instance of `Option[User]`.// `OptionalAuthElement` dont need `AuthorityKey`defindex=StackAction {implicit request=>valmaybeUser:Option[User]= loggedInvaluser:User= maybeUser.getOrElse(GuestUser)Ok(html.index(user))  }}

For action that doesn't require authorization

you canAuthenticationElement instead ofAuthElement for authentication without authorization.

objectApplicationextendsControllerwithAuthenticationElementwithAuthConfigImpl {defindex=StackAction {implicit request=>valuser:User= loggedInOk(html.index(user))  }}

Return 401 when a request is sent by Ajax

Normally, you want to return a login page redirection at a authentication failed.Although, when the request is sent by Ajax you want to instead return 401, Unauthorized.

You can do it as follows.

defauthenticationFailed(request:RequestHeader)(implicitctx:ExecutionContext)=Future.successful {  request.headers.get("X-Requested-With")match {caseSome("XMLHttpRequest")=>Unauthorized("Authentication failed")case _=>Redirect(routes.Application.login)  }}

Action composition

play2.auth usestackable-controller

Suppose you want to validate a token at every action in order to defeat aCross Site Request Forgery attack.

Since it is impractical to perform the validation in all actions, you would define a trait like this:

importjp.t2v.lab.play2.stackc.{RequestWithAttributes,StackableController}importscala.concurrent.Futureimportplay.api.mvc.{Result,Request,Controller}importplay.api.data._importplay.api.data.Forms._traitTokenValidateElementextendsStackableController {self:Controller=>privatevaltokenForm=Form("token"-> text)privatedefvalidateToken(request:Request[_]):Boolean= (for {    tokenInForm<- tokenForm.bindFromRequest()(request).value    tokenInSession<- request.session.get("token")  }yield tokenInForm== tokenInSession).getOrElse(false)overridedefproceed[A](request:RequestWithAttributes[A])(f:RequestWithAttributes[A]=>Future[Result]):Future[Result]= {if (validateToken(request))super.proceed(request)(f)elseFuture.successful(BadRequest)  }}

You can useTokenValidateElement trait withAuthElement trait.

objectApplicationextendsControllerwithTokenValidateElementwithAuthElementwithAuthConfigImpl {defpage1=StackAction(AuthorityKey->NormalUser) {implicit request=>// do somethingOk(html.page1("result"))  }defpage2=StackAction(AuthorityKey->NormalUser) {implicit request=>// do somethingOk(html.page2("result"))  }}

Asynchronous Support

There are asynchronous libraries ( for example:ReactiveMongo,ScalikeJDBC-Async, and so on ).

You should useFuture[Result] instead ofAsyncResult from Play2.2.

You can useAsyncStack instead ofStackAction for Future[Result]

traitHogeControllerextendsAuthElementwithAuthConfigImpl {defhoge=AsyncStack {implicit req=>valmessages:Future[Seq[Message]]=AsyncDB.withPool {implicit s=>Message.findAll }    messages.map(Ok(html.view.messages(_)))  }}

Stateless vs Stateful implementation.

Play2x-Auth follows the Play framework's stateless policy.However, Play2x-Auth's default implementation is stateful,because the stateless implementation has the following security risk:

If user logs-in to your application in a internet-cafe, then returns home neglecting to logout.If the user logs in again at home they willnot invalidate the session.

Nevertheless, you want to use a fully stateless implementation then just override theidContainer method ofAuthConfig like this:

traitAuthConfigImplextendsAuthConfig {// Other settings omitted.overridelazyvalidContainer:AsyncIdContainer[Id]=AsyncIdContainer(newCookieIdContainer[Id])}

You could also store the session data in a Relational Database by overriding the id container.

Note:CookieIdContainer doesn't support session timeout.

Running The Sample Application

  1. git clone https://github.com/t2v/play2-auth.git
  2. cd play2-auth
  3. sbt "project sample" run
  4. access tohttp://localhost:9000/ on your browser.
    1. clickApply this script now!

    2. login

      defined accounts

       Email             | Password | Role alice@example.com | secret   | Administrator bob@example.com   | secret   | NormalUser chris@example.com | secret   | NormalUser

Attention -- Distributed Servers

Ehcache, the default cache implementation used by Play2.x, does not work on distributed application servers.

If you have distributed servers, use theMemcached Plugin or something similar.

License

This library is released under the Apache Software License, version 2,which should be included with the source in a file namedLICENSE.


[8]ページ先頭

©2009-2025 Movatter.jp