"silhouette"的名字来源于美国的科幻小说《守望者》中的一个角色名,至于作者为何会选择这个名字,我们不得而知,总之作者可定非常喜欢这个角色吧。“silhouette”是一个基于play的认证相关的库,是目前使用比较广泛的库。它提供了OAuth1, OAuth2, OpenID, Credentials, Basic Authentication等多种认证方式。它可以整合成一个特定的Action,只要把传统的play的Action替换成整合成的Action,就可以在运行相应Action之前首先校验。这种校验在和其他代码的耦合性很低,非常的赞。具体信息可以看(http://silhouette.mohiva.com/docs/features)或者关注gitHub:(https://github.com/mohiva/play-silhouette)。
“silhouette”有几个非常好的例子可以供大家参考:play-silhouette-seed、play-silhouette-slick-seed和play-silhouette-angular-seed等,大家可以从Activator UI上找到相应的项目clone下来。作为入手,一个完整的例子是再好不过的了。再次佩服Typesafe的工作者。
本文就以play-silhouette-slick-seed这个例子展开,这个例子是结合了slick和silhouette的play工程,也是用的比较多的一个工程模式。
从Activator UI clone下来工程以后,运行程序,发现它其实虽然简单,但是完整的做了一个认证的程序。
1、判断没登陆过,跳转到登陆界面。
2、没用户,注册用户:
3、利用注册信息登陆后,显示index界面。而且但凡是登陆过,下次不用重复输入,还会跳转到此页面。
4、点击sign out 按钮后,下次输入index网址需要登陆
这就是这个sample所有的功能,虽然很简单,但是却是一个很完整的实现了认证的一个例子。
下面我们就开始学一下这个例子:
1、首先想要引用silhouette这个框架,需要在bulid.sbt中引入它的包(目前是3.0.4版本):
resolvers += "Atlassian Releases" at "https://maven.atlassian.com/public/"
libraryDependencies ++= Seq(
"com.mohiva" %% "play-silhouette" % "3.0.4",
"com.mohiva" %% "play-silhouette-testkit" % "3.0.4" % "test"
)
+= "Atlassian Releases" at "https://maven.atlassian.com/public/"
libraryDependencies ++= Seq(
"com.mohiva" %% "play-silhouette" % "3.0.4",
"com.mohiva" %% "play-silhouette-testkit" % "3.0.4" % "test"
)
这样,silhouette就可以使用了。
我们先看一下认证的入口:
POST /authenticate/credentials @controllers.CredentialsAuthController.authenticate
找到对应的方法:CredentialsAuthController下的authenticate方法,发现这个class就实现了这么一个方法,而且用Guice通过构造方法注入注入了很多很多东西,其中messageApi和env是有get set方法的,并有可以被访问的字段。
Environment是silhouette重要的组成部分,它定义了一个Silhouette项目主要的“key”模块,它包含了identity接口实现、authenticator接口实现、identity service接口实现、authenticator service接口实现、请求提供接口实现和事件总线接口实现。
从名字也可以看出来,它配置了认证相关的所有“环境”。
CredentialAuthController这个类还继承了Silhouette这个类,这个类是框架本身定义好的工具类,从而引出了“Endpoints”这个概念,在play中一个Endpoint可以是一个Action、也可以是Websocket。。它是多面的,Silhouette提供了一套机制来确保他们的访问安全。
在此,在例子中Endpoint就是一个Action,最常见的也都是Action。还有Websocket、Request Handler的例子可以参照官网:http://silhouette.mohiva.com/docs/endpoints。本例子中用到了两个Action:一个是SecureAction,一个是UserAwareAction。
这两个Action分别基于SecureRequestHandler和UserAwareRequestHandler。下面先给出这两个RequestHandler的例子:
class Application(env: Environment[User, CookieAuthenticator])
extends Silhouette[User, CookieAuthenticator] {
/**
* An example for a secured request handler.
*/
def securedRequestHandler = Action.async { implicit request =>
SecuredRequestHandler { securedRequest =>
Future.successful(HandlerResult(Ok, Some(securedRequest.identity)))
}.map {
case HandlerResult(r, Some(user)) => Ok(Json.toJson(user.loginInfo))
case HandlerResult(r, None) => Unauthorized
}
}
/**
* An example for an user aware request handler.
*/
def userAwareRequestHandler = Action.async { implicit request =>
UserAwareRequestHandler { userAwareRequest =>
Future.successful(HandlerResult(Ok, userAwareRequest.identity))
}.map {
case HandlerResult(r, Some(user)) => Ok(Json.toJson(user.loginInfo))
case HandlerResult(r, None) => Unauthorized
}
}
}
Application(env: Environment[User, CookieAuthenticator])
extends Silhouette[User, CookieAuthenticator] {
/**
* An example for a secured request handler.
*/
def securedRequestHandler = Action.async { implicit request =>
SecuredRequestHandler { securedRequest =>
Future.successful(HandlerResult(Ok, Some(securedRequest.identity)))
}.map {
case HandlerResult(r, Some(user)) => Ok(Json.toJson(user.loginInfo))
case HandlerResult(r, None) => Unauthorized
}
}
/**
* An example for an user aware request handler.
*/
def userAwareRequestHandler = Action.async { implicit request =>
UserAwareRequestHandler { userAwareRequest =>
Future.successful(HandlerResult(Ok, userAwareRequest.identity))
}.map {
case HandlerResult(r, Some(user)) => Ok(Json.toJson(user.loginInfo))
case HandlerResult(r, None) => Unauthorized
}
}
}
SecuredRequestHandler能够拦截请求,并且检查是否存在认证信息,如果存在,将会按照需求继续执行。UserAwareRequestHandler是被用于需要知道当前有没有认证的用户的endpoints,但是即使没有,也会继续执行。以此类推,SecuredAction和UserAwareAction的区别也就显而易见了。一下是两个例子是实现了相同的功能的不同Action:
SecuredAction:
class Application(env: Environment[User, CookieAuthenticator])
extends Silhouette[User, CookieAuthenticator] {
/**
* Renders the index page.
*
* @returns The result to send to the client.
*/
def index = SecuredAction { implicit request =>
Ok(views.html.index(request.identity))
}
}
Application(env: Environment[User, CookieAuthenticator])
extends Silhouette[User, CookieAuthenticator] {
/**
* Renders the index page.
*
* @returns The result to send to the client.
*/
def index = SecuredAction { implicit request =>
Ok(views.html.index(request.identity))
}
}
UserAwareAction:
class Application(env: Environment[User, CookieAuthenticator])
extends Silhouette[User, CookieAuthenticator] {
/**
* Renders the index page.
*
* @returns The result to send to the client.
*/
def index = UserAwareAction { implicit request =>
val userName = request.identity match {
case Some(identity) => identity.fullName
case None => "Guest"
}
Ok("Hello %s".format(userName))
}
}
Application(env: Environment[User, CookieAuthenticator])
extends Silhouette[User, CookieAuthenticator] {
/**
* Renders the index page.
*
* @returns The result to send to the client.
*/
def index = UserAwareAction { implicit request =>
val userName = request.identity match {
case Some(identity) => identity.fullName
case None => "Guest"
}
Ok("Hello %s".format(userName))
}
}
我们跟进添加了注入的类中发现,只有User是自定义的,发现User继承了Identity这个trait,Identity 这个trait是由Silhouette提供的,利用Identity,我们可以定义一个 user,这个trait没有定义任何默认的值。因此,你可以通过自身需要,自由的设计。
在User类中,你还会发现有一个LoginInfo类型的字段,这个LoginInfo也是由Silhoueete提供,它扮演着Silhouette中Identity ID这样一个角色,而且他用于确定在工作流程中的用户,他包含了认证信息。要创建一个程序的User,你必须要继承Identity trait,在User类中,你可以包含你想要的任何字段。
然后,如果你想实现认证功能,你要定义一个Identity service,并实现它的实现类。Silhouette就是依靠这些实现类来实现认证的功能的。
User 继承 Identity trait ,Identity 依赖于 IdentityService,想要完成自己的功能,需要定义一个继承于IdentityService的trait,并且实现继承trait的实现类,实现类中可能需要访问数据库的内容,于是,IdentityServiceImpl就需要注入DAO的内容,于是,DAO相关的trait和Impl也联系了进来。这些种种的依赖trait和相应trait的实现类,都是在modules文件夹中的SilhouetteModule class中实现依赖注入的。而且想要程序自动实现此依赖关系,需要在application.conf文件中加上:
play.modules.enabled += "modules.SilhouetteModule"
于是models文件夹下的classes关系就理清了:
User ---》Identity ----》IdentityService[User]----》UserService (UserServiceImpl) ----》 userDAO (UserDAOImpl)
DI注入依赖关系:
SilhouetteModule
搞清了以上关系,发现理解起Silhouette-slick-seed这个工程的代码就容易多了,可以细读一下,其实很简单。这是个很有用的工程,项目中可以直接拿这个工程来修改。