如何实现多个Silhouette Authenticators?

时间:2017-02-13 17:11:15

标签: scala playframework jwt playframework-2.5 silhouette

我使用play-silhouette-seed作为我的应用程序的模板。所以在我的项目中,我使用基于cookie的身份验证器(CookieAuthenticator)。即使对于通过我的Twirl模板中嵌入的JavaScript的REST调用,这也非常好用。但是,现在我想在除浏览器之外的客户端中以编程方式进行REST调用。因此,我必须在每个响应中检索Set-Cookie: authenticator=...元素并将其设置为我的请求的一部分。在我的Twirl模板中嵌入并在浏览器中呈现的JavaScript代码段中,这没有问题,因为我不必处理它,但对于其他客户端(服务器等),这会导致头痛。

除了JWTAuthenticator之外,我想现在实现CookieAuthenticator。这甚至是支持,还是我必须完全切换到JWTAuthenticator?此外,我是否需要单独的操作,即使除验证者之外的所有内容都应该是相同的实现?

1 个答案:

答案 0 :(得分:1)

是的,Silhouette允许您实现多个authenticators。以下是如何实施提供JWT身份验证服务的 JWTAuthenticator 以及 CookieAuthenticator

  1. 正如Douglas Liu已经在评论中指出的那样,您需要创建一个额外的 environment 类型。它应该将Identity与相应的Authenticator相关联。
  2. 例如:

    trait CookieEnv extends Env {
      type I = Account
      type A = CookieAuthenticator
    }
    
    trait JWTEnv extends Env {
      type I = Account
      type A = JWTAuthenticator
    }
    
    1. 在Silhouette模块中实现JWT绑定。请查看 play-silhouette-angular-seed 以获取完整示例。
    2. 例如:

      class SilhouetteModule extends AbstractModule with ScalaModule {
      
        def configure() {
          bind[Silhouette[CookieEnv]].to[SilhouetteProvider[CookieEnv]]
          bind[Silhouette[JWTEnv]].to[SilhouetteProvider[JWTEnv]]
          // ...
          ()
        }
      
         @Provides
        def provideCookieEnvironment(
                                      userService: AccountService,
                                      authenticatorService: AuthenticatorService[CookieAuthenticator],
                                      eventBus: EventBus): Environment[CookieEnv] = {
      
          Environment[CookieEnv](
            userService,
            authenticatorService,
            Seq(),
            eventBus
          )
        }
      
        @Provides
        def provideJWTEnvironment(
                                   userService: AccountService,
                                   authenticatorService: AuthenticatorService[JWTAuthenticator],
                                   eventBus: EventBus): Environment[JWTEnv] = {
      
          Environment[JWTEnv](
            userService,
            authenticatorService,
            Seq(),
            eventBus
          )
        }
      
      // ...
      
        @Provides
        def provideCookieAuthenticatorService(
                                               @Named("authenticator-cookie-signer") cookieSigner: CookieSigner,
                                               @Named("authenticator-crypter") crypter: Crypter,
                                               fingerprintGenerator: FingerprintGenerator,
                                               idGenerator: IDGenerator,
                                               configuration: Configuration,
                                               clock: Clock): AuthenticatorService[CookieAuthenticator] = {
      
          val config = configuration.underlying.as[CookieAuthenticatorSettings]("silhouette.authenticator")
          val encoder = new CrypterAuthenticatorEncoder(crypter)
      
          new CookieAuthenticatorService(config, None, cookieSigner, encoder, fingerprintGenerator, idGenerator, clock)
        }
      
        @Provides
        def provideJWTAuthenticatorService(
                                            @Named("authenticator-crypter") crypter: Crypter,
                                            idGenerator: IDGenerator,
                                            configuration: Configuration,
                                            clock: Clock): AuthenticatorService[JWTAuthenticator] = {
      
          val config = configuration.underlying.as[JWTAuthenticatorSettings]("silhouette.authenticator")
          val encoder = new CrypterAuthenticatorEncoder(crypter)
      
          new JWTAuthenticatorService(config, None, encoder, idGenerator, clock)
        }
      
      // ...
      
      }
      
      1. JWTAuthenticator configuration settings添加到您的silhouette.conf
      2. 例如:

        authenticator.fieldName = "X-Auth-Token"
        authenticator.requestParts = ["headers"]
        authenticator.issuerClaim = "Your fancy app"
        authenticator.authenticatorExpiry = 12 hours
        authenticator.sharedSecret = "!!!changeme!!!"
        
        1. 通过JWT创建单独的身份验证路由:
        2. 例如,在app.routes文件中,添加以下行:

          # JWT Authentication
          POST        /api/jwt/authenticate        controllers.auth.api.AuthController.authenticate
          
          1. 最后,在AuthController中,添加相应的authenticate方法。
          2. 示例代码(改编自 SignInController.scala ):

            implicit val dataReads = (
              (__ \ 'email).read[String] and
                (__ \ 'password).read[String] and
                (__ \ 'rememberMe).read[Boolean]
              ) (SignInForm.SignInData.apply _)
            
            def authenticate = Action.async(parse.json) { implicit request =>
              request.body.validate[SignInForm.SignInData].map { signInData =>
                credentialsProvider.authenticate(Credentials(signInData.email, signInData.password)).flatMap { loginInfo =>
                  accountService.retrieve(loginInfo).flatMap {
                    case Some(user) => silhouette.env.authenticatorService.create(loginInfo).map {
                      case authenticator if signInData.rememberMe =>
                        val c = configuration.underlying
                        authenticator.copy(
                          expirationDateTime = clock.now + c.as[FiniteDuration]("silhouette.authenticator.rememberMe.authenticatorExpiry"),
                          idleTimeout = c.getAs[FiniteDuration]("silhouette.authenticator.rememberMe.authenticatorIdleTimeout")
                        )
                      case authenticator => authenticator
                    }.flatMap { authenticator =>
                      Logger.info(s"User ${user._id} successfully authenticated.")
                      silhouette.env.eventBus.publish(LoginEvent(user, request))
                      silhouette.env.authenticatorService.init(authenticator).map { token =>
                        Ok(Json.obj("token" -> token))
                      }
                    }
                    case None => Future.failed(new IdentityNotFoundException("Couldn't find user."))
                  }
                }.recover {
                  /* Login did not succeed, because user provided invalid credentials. */
                  case e: ProviderException =>
                    Logger.info(s"Host ${request.remoteAddress} tried to login with invalid credentials (email: ${signInData.email}).")
                    Unauthorized(Json.obj("error" -> Messages("error.invalidCredentials")))
                }
              }.recoverTotal {
                case e: JsError =>
                  Logger.info(s"Host ${request.remoteAddress} sent invalid auth payload. Error: $e.")
                  Future.successful(Unauthorized(Json.obj("error" -> Messages("error.invalidPayload"))))
              }
            }