SecureSocialで独自の認証画面を作る。
環境は
- Scala : 2.11
- Play : 2.3
- SecureSocial : 3.0-M1
SecureSocialで独自の認証画面を使う方法を書く。 前提として、以下の方法でsecuresocialを導入しているものとする。
Global.scala
RuntimeEnvironmentのサブクラスを実装する。 ほぼ、前回と同じだが、カスタムの画面を使用する場合はviewTemplatesをオーバーライドする。
object ApplicationRuntimeEnvironment extends RuntimeEnvironment.Default[User] {
protected override def include(p: IdentityProvider) = p.id -> p
protected override def oauth1ClientFor(provider: String) = new OAuth1Client.Default(ServiceInfoHelper.forProvider(provider), httpService)
protected override def oauth2ClientFor(provider: String) = new OAuth2Client.Default(httpService, OAuth2Settings.forProvider(provider))
override lazy val userService: UserService[User] = new UserServiceImpl
override lazy val providers = ListMap(
include(new FacebookProvider(routes, cacheService, oauth2ClientFor(FacebookProvider.Facebook))),
include(new GoogleProvider(routes, cacheService,oauth2ClientFor(GoogleProvider.Google))),
include(new UsernamePasswordProvider[User](userService, avatarService, viewTemplates, passwordHashers))
)
override lazy val viewTemplates
= new plugins.CustomTemplates(this) /// <====追加
}
plugins/CustomTemplates.scala
ViewTemplatesを継承したクラスを作成。 独自に実装したい画面に対応するメソッドをオーバーライドする。FacebookなどのSNSでのログインのみであれば、getLoginPage、ID/パスワードの認証であれば、その他のメソッドもオーバーライドする必要がある
- getLoginPage
- getNotAuthorizedPage
- getSignUpPage
- getStartSignUpPage
- getResetPasswordPage
- getStartResetPasswordPage
- getPasswordChangePage
import play.api.data.Form
import play.api.i18n.Lang
import play.api.mvc.RequestHeader
import play.twirl.api.Html
import securesocial.controllers._
import securesocial.core.RuntimeEnvironment
class CustomTemplates(env: RuntimeEnvironment[_]) extends ViewTemplates {
implicit val implicitEnv = env
override def getLoginPage(form: Form[(String, String)],
msg: Option[String] = None)(implicit request: RequestHeader, lang: Lang): Html = {
views.html.login(form, msg)
}
...(略)...
}
あとはテンプレートを書けばいいだけなので、特に問題となるようなところは無いはず。 わからなければ、ココらへんに転がってるものを見ればいいと思う。
https://github.com/jaliss/securesocial/tree/master/module-code/app/securesocial/views
views.login.scala.html
@(loginForm: Form[(String,String)], errorMsg: Option[String] = None)(implicit request: RequestHeader, env:securesocial.core.RuntimeEnvironment[_])
@import java.util._
@import java.text.DateFormat
@import securesocial.core.providers.UsernamePasswordProvider.UsernamePassword
@main {
<div class="form-box" id="login-box">
<div class="header">Sign In</div>
@env.providers.get(UsernamePassword).map { up =>
@views.html.provider("userpass", Some(loginForm))
}
<div class="margin text-center">
@defining( env.providers.values.filter( _.id != UsernamePassword) ) { externalProviders =>
@if( externalProviders.size > 0 ) {
<div class="clearfix">
<p>@Messages("securesocial.login.instructions")</p>
<p>
@for(p <- externalProviders) {
@views.html.provider(p.id)
}
</p>
</div>
}
}
</div>
</div>
}{
}{
}
views.provider.scala.html
@(providerId: String, loginForm: Option[Form[(String, String)]] = None)(implicit request: RequestHeader, lang: Lang, env: securesocial.core.RuntimeEnvironment[_])
@import securesocial.core.providers.UsernamePasswordProvider
@import securesocial.core.AuthenticationMethod._
@import play.api.Logger
@import helper._
@import play.api.Play
@implicitFieldConstructor = @{ FieldConstructor(securesocial.views.html.inputFieldConstructor.f) }
@env.providers.get(providerId).map { provider =>
@if( provider.authMethod == OAuth1 || provider.authMethod == OAuth2 ) {
@defining( "images/providers/%s.png".format(provider.id) ) { imageUrl =>
<a href="@env.routes.authenticationUrl(provider.id)"> <img src="@controllers.routes.Assets.at(imageUrl)"/></a>
}
}
@if( provider.authMethod == UserPassword ) {
<form action="@env.routes.authenticationUrl("userpass")" method="post">
<div class="body bg-gray">
@if(loginForm.get.hasErrors) {
<div class="alert alert-danger alert-dismissable">
<i class="fa fa-ban"></i>
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
User ID or password is invalid!
</div>
}
@if( UsernamePasswordProvider.withUserNameSupport ) {
@helper.inputText(
loginForm.get("username"),
'_label -> Messages("securesocial.signup.username"),
'class -> "form-control",
'placeholder -> "User ID"
)
} else {
@helper.inputText(
loginForm.get("username"),
'_label -> Messages("securesocial.signup.email1"),
'class -> "form-control",
'placeholder -> "User ID"
)
}
@helper.inputPassword(
loginForm.get("password"),
'_label -> Messages("securesocial.signup.password1"),
'class -> "form-control",
'placeholder -> "Password"
)
<div class="form-group">
<input type="checkbox" name="remember_me"/> Remember me
</div>
</div>
<div class="footer">
<button type="submit" class="btn bg-olive btn-block">Sign me in</button>
<p><a href="@env.routes.resetPasswordUrl">I forgot my password</a></p>
@if(Play.current.configuration.getBoolean("securesocial.registrationEnabled").getOrElse(true) ){
<a href="@env.routes.signUpUrl" class="text-center">Register a new membership</a>
}
</div>
</form>
}
}.getOrElse {
@*****************************************
* Todo: throw a runtime exception? this might need improvement
*****************************************@
@Logger.error("[securesocial] unknown provider '%s'. Can't render it.".format(providerId))
{ throw new RuntimeException("Unknown provider '%s') }
}
実行
あとは、実行するだけ。簡単にログイン画面のカスタマイズができた。
最後に
- securesocialのバージョンが3.0になって変わったのは、play.pluginsを使わなくなった(かわりにRuntimeEnvironmentを使う)だけっぽい。
- 認証時のメールのテンプレートなどもカスタマイズできる。その場合は、RuntimeEnvironmentのmailTemplatesをオーバーライドすればいい。