Ktor 服务器中的认证与授权
必需的依赖项: io.ktor:ktor-server-auth
Ktor 提供了 Authentication 插件来处理认证和授权。典型使用场景包括用户登录、授予对特定资源的访问权限以及在各方之间安全地传输信息。您还可以将 Authentication
与 Sessions 结合使用,以在请求之间保留用户的会话信息。
在客户端,Ktor 提供了 Authentication 插件用于处理认证和授权。
支持的认证类型
Ktor 支持以下认证和授权方案:
HTTP 认证
HTTP 提供了 通用框架 用于访问控制和认证。在 Ktor 中,您可以使用以下 HTTP 认证方案:
- Basic - 使用
Base64
编码提供用户名和密码。通常不建议单独使用,除非与 HTTPS 结合使用。 - Digest - 一种认证方法,通过对用户名和密码应用哈希函数,以加密形式通信用户凭据。
- Bearer - 一种涉及称为 bearer 令牌的安全令牌的认证方案。 Bearer 认证方案作为 OAuth 或 JWT 的一部分使用,但您也可以为授权 bearer 令牌提供自定义逻辑。
基于表单的认证
JSON Web Token (JWT)
JSON Web Token 是一种开放标准,用于将信息作为 JSON 对象在各方之间安全传输。您可以将 JSON Web Token 用于授权:当用户登录后,每个请求都将包含一个 token,允许用户访问该 token 允许的资源。在 Ktor 中,您可以使用 jwt
认证来验证 token 并校验其中包含的声明。
LDAP
LDAP 是一种开放且跨平台的协议,用于目录服务认证。Ktor 提供了 ldapAuthenticate 函数,用于根据指定的 LDAP 服务器认证用户凭据。
OAuth
OAuth 是一种用于保护 API 访问安全的开放标准。Ktor 中的 oauth
提供者允许您使用 Google、Facebook、Twitter 等外部提供者实现认证。
Session
Sessions 提供了一种在不同 HTTP 请求之间持久化数据的机制。典型用例包括存储登录用户的 ID、购物车内容或在客户端保存用户偏好。在 Ktor 中,已有关联 session 的用户可以使用 session
提供者进行认证。关于如何实现,请参阅 Ktor 服务器中的 Session 认证。
自定义
Ktor 还提供了用于创建 自定义插件 的 API,可用于实现您自己的插件来处理认证和授权。 例如,AuthenticationChecked
钩子 在认证凭据检测后执行,它允许您实现授权:custom-plugin-authorization。
添加依赖项
要使用 Authentication
,您需要在构建脚本中包含 ktor-server-auth
artifact:
请注意,一些认证提供者,例如 JWT 和 LDAP,需要额外的 artifacts。
安装 Authentication
要 安装 Authentication
插件到应用程序中, 请将其传递给指定
install
函数。 下面的代码片段展示了如何安装 Authentication
... - ... 在
embeddedServer
函数调用内部。 - ... 在显式定义的
module
内部,它是Application
类的一个扩展函数。
配置 Authentication
安装 Authentication 后,您可以按如下方式配置和使用 Authentication
:
步骤 1:选择认证提供者
要使用特定的认证提供者,例如 basic、digest 或 form, 您需要在 install
代码块中调用相应的函数。例如,要使用 basic 认证, 请调用 .basic()
函数:
import io.ktor.server.application.*
import io.ktor.server.auth.*
// ...
install(Authentication) {
basic {
// Configure basic authentication
}
}
在此函数中,您可以 配置 此提供者特有的设置。
步骤 2:指定提供者名称
用于 使用特定提供者 的函数可选地允许您指定提供者名称。下面的代码 示例安装了 basic 和 form 提供者,分别命名为 "auth-basic"
和 "auth-form"
:
install(Authentication) {
basic("auth-basic") {
// Configure basic authentication
}
form("auth-form") {
// Configure form authentication
}
// ...
}
这些名称稍后可以使用来 认证不同的路由,使用不同的提供者。
请注意,提供者名称应该是唯一的,并且您只能定义一个没有名称的提供者。
步骤 3:配置提供者
每种 提供者类型 都有自己的配置。例如, BasicAuthenticationProvider.Config
类为 .basic()
函数提供选项。此类中的关键函数是 validate()
, 它负责校验用户名和密码。以下代码示例演示了其用法:
install(Authentication) {
basic("auth-basic") {
realm = "Access to the '/' path"
validate { credentials ->
if (credentials.name == "jetbrains" && credentials.password == "foobar") {
UserIdPrincipal(credentials.name)
} else {
null
}
}
}
}
要理解 validate()
函数的工作原理,我们需要介绍两个术语:
- principal 是一种可以被认证的实体:用户、计算机、服务等。在 Ktor 中,各种认证提供者可能会使用不同的 principal。例如,
basic
、digest
和form
提供者认证UserIdPrincipal
, 而jwt
提供者验证JWTPrincipal
。您也可以创建自定义 principal。这在以下情况下可能很有用:
- 将凭据映射到自定义 principal 允许您在 路由处理器 内部获取已认证 principal 的额外信息。
- 如果您使用 session 认证,principal 可能是一个存储 session 数据的数据类。
- credential 是一组服务器用于认证 principal 的属性:用户名/密码对、API 密钥等。例如,
basic
和form
提供者使用UserPasswordCredential
校验用户名和密码,而jwt
校验JWTCredential
。
因此,validate()
函数会检测指定的凭据并在认证成功的情况下返回一个 Any
类型的 principal,或在认证失败时返回 null
。
要根据特定条件跳过认证, 请使用
skipWhen()
。 例如,如果 session 已存在,您可以跳过basic
认证:kotlinbasic { skipWhen { call -> call.sessions.get<UserSession>() != null } }
步骤 4:保护特定资源
最后一步是保护应用程序中的特定资源。您可以使用 authenticate()
函数来实现。此函数接受两个可选形参:
用于认证嵌套路由的 提供者名称。 下面的代码片段使用名为 auth-basic 的提供者来保护
/login
和/orders
路由:kotlinrouting { authenticate("auth-basic") { get("/login") { // ... } get("/orders") { // ... } } get("/") { // ... } }
用于解析嵌套认证提供者的策略。 此策略由
AuthenticationStrategy
枚举值表示。例如,客户端应为所有使用
AuthenticationStrategy.Required
策略注册的提供者提供认证数据。 在下面的代码片段中,只有通过 session 认证 的用户才能尝试使用 basic 认证访问/admin
路由:kotlinrouting { authenticate("auth-session", strategy = AuthenticationStrategy.Required) { get("/hello") { // ... } authenticate("auth-basic", strategy = AuthenticationStrategy.Required) { get("/admin") { // ... } } } }
完整示例请参阅 auth-form-session-nested。
步骤 5:在路由处理器内部获取 principal
认证成功后,您可以使用 call.principal()
函数在路由处理器内部检索已认证的 principal。此函数接受由 配置的认证提供者 返回的特定 principal 类型。在以下示例中,call.principal()
用于获取 UserIdPrincipal
并获取已认证用户的名称。
routing {
authenticate("auth-basic") {
get("/") {
call.respondText("Hello, ${call.principal<UserIdPrincipal>()?.name}!")
}
}
}
如果您使用 session 认证,principal 可能是一个存储 session 数据的数据类。 因此,您需要将此数据类传递给 call.principal()
:
authenticate("auth-session") {
get("/hello") {
val userSession = call.principal<UserSession>()
}
}
在 嵌套认证提供者 的情况下,您可以将 提供者名称 传递给 call.principal()
以获取所需提供者的 principal。
在下面的示例中,传递了 "auth-session"
值以获取顶层 session 提供者的 principal:
authenticate("auth-session", strategy = AuthenticationStrategy.Required) {
authenticate("auth-basic", strategy = AuthenticationStrategy.Required) {
get("/admin") {
val userSession = call.principal<UserSession>("auth-session")
}
}
}