Ktor 中的表单身份验证

Posted

技术标签:

【中文标题】Ktor 中的表单身份验证【英文标题】:Form Authentication in Ktor 【发布时间】:2018-03-12 00:29:21 【问题描述】:

我是 KotlinKtor 的新手,试图查看身份验证部分,所以我得到了以下代码。

路由“/”和“/bye”工作正常,但路由“登录”给定空白页!

package blog

import kotlinx.html.*
import kotlinx.html.stream.*    // for createHTML
import org.jetbrains.ktor.application.*
import org.jetbrains.ktor.auth.*
import org.jetbrains.ktor.features.*
import org.jetbrains.ktor.http.*
import org.jetbrains.ktor.response.*
import org.jetbrains.ktor.routing.*

import org.jetbrains.ktor.request.*   // for request.uri

import org.jetbrains.ktor.html.*
import org.jetbrains.ktor.pipeline.*

import org.jetbrains.ktor.host.*   // for embededServer
import org.jetbrains.ktor.netty.*  // for Netty

fun main(args: Array<String>) 
    embeddedServer(Netty, 8080, watchPaths = listOf("BlogAppKt"), module = Application::module).start()


fun Application.module() 
    install(DefaultHeaders)
    install(CallLogging)

    intercept(ApplicationCallPipeline.Call)  
        if (call.request.uri == "/hi")
            call.respondText("Test String")
    

    install(Routing) 
        get("/") 
            call.respondText("""Hello, world!<br><a href="/bye">Say bye?</a>""", ContentType.Text.Html)
        
        get("/bye") 
            call.respondText("""Good bye! <br><a href="/login">Login?</a> """, ContentType.Text.Html)
        
        route("/login") 
            authentication 
                formAuthentication  up: UserPasswordCredential ->
                    when 
                        up.password == "ppp" -> UserIdPrincipal(up.name)
                        else -> null
                    
                
            

            handle 
                val principal = call.authentication.principal<UserIdPrincipal>()
                if (principal != null) 
                    call.respondText("Hello, $principal.name")
                 else 
                        val html = createHTML().html 
                        body 
                            form(action = "/login", encType = FormEncType.applicationXWwwFormUrlEncoded, method = FormMethod.post) 
                                p 
                                    +"user:"
                                    textInput(name = "user") 
                                        value = principal?.name ?: ""
                                    
                                

                                p 
                                    +"password:"
                                    passwordInput(name = "pass")
                                

                                p 
                                    submitInput()  value = "Login" 
                                
                            
                        
                    
                    call.respondText(html, ContentType.Text.Html)
                
            
        
    

当我禁用下面的身份验证部分时,路由'/login'显示所需的表单,这意味着错误最有可能在这部分或调用它的方式上?我猜。

            authentication 
                formAuthentication  up: UserPasswordCredential ->
                    when 
                        up.password == "ppp" -> UserIdPrincipal(up.name)
                        else -> null
                    
                
            

【问题讨论】:

【参考方案1】:

您不仅获得了一个空白页面,还获得了401 (UNAUTHORIZED) 的 HTTP 状态代码。那是因为formAuthentication 有四个参数,其中三个具有默认值。你只实现了最后一个(validate,没有默认):

userParamName: String = "user",
passwordParamName: String = "password",
challenge: FormAuthChallenge = FormAuthChallenge.Unauthorized,
validate: (UserPasswordCredential) -> Principal?

当您到达/login 路由但没有正确的凭据时,您将获得challenge 的默认值,即FormAuthChallenge.Unauthorized,即401 响应。

您可以使用FormAuthChallenge.Redirect,而不是使用challenge 的默认值。一个需要两条路线的简短示例:

get("/login") 
    val html = """
            <form action="/authenticate" enctype="..."
            REST OF YOUR LOGIN FORM
            </form>
            """
    call.respondText(html, ContentType.Text.Html)


route("/authenticate") 
    authentication 
        formAuthentication(challenge = FormAuthChallenge.Redirect( _, _ -> "/login" )) 
            credential: UserPasswordCredential ->
            when 
                credential.password == "secret" -> UserIdPrincipal(credential.name)
                else -> null
            
        
    

    handle 
        val principal = call.authentication.principal<UserIdPrincipal>()
        val html = "Hello, $principal?.name"
        call.respondText(html, ContentType.Text.Html)
    

更新

如果上述方法效果不佳,请清楚地定义userid-parameterpassword-parameter,因为它们出现在执行POSTform 中,如下所示:

        authentication 
            formAuthentication("user", "pass",
                    challenge = FormAuthChallenge.Redirect( _, _ -> "/login" ))
                credential: UserPasswordCredential ->
                when 
                credential.password == "secret" -> UserIdPrincipal(credential.name)
                else -> null
                
            
        

【讨论】:

给出Status 302 并继续重新加载\login 页面,无论输入密码:( 当我使用formAuthentication("user", "pass", .. 时效果很好,非常感谢。

以上是关于Ktor 中的表单身份验证的主要内容,如果未能解决你的问题,请参考以下文章

Ktor websockets 身份验证

来自 UserHashedTableAuth 的 Ktor 基本身份验证

Firebase 身份验证不适用于 AppEngine 上的 Ktor 应用程序

Android 中 Ktor 服务器身份验证的几个问题

基本身份验证应在邮递员中响应 500(授权),但 401 Unauthorized 可以正常工作。使用 ktor intellij mongodb

.NET 基于 cookie 的身份验证,无需表单身份验证