kotlin 如何在Ktor中通过方法实现角色授权

vvppvyoh  于 2023-03-30  发布在  Kotlin
关注(0)|答案(1)|浏览(137)

我有一个用Ktor做的API,我尝试按角色实现授权,如下所示:

internal class RoleBaseConfiguration(
        var requiredRoles: Set<String> = emptySet()
    )

    internal val globalconfig = RoleBaseConfiguration()

    internal val RoleAuthorizationPlugin = createApplicationPlugin(
        name = "RoleAuthorizationPlugin",
        createConfiguration = ::RoleBaseConfiguration
    ){
        pluginConfig.apply{
            on(AuthenticationChecked){ call ->
                val jwtToken = call.request.headers["Authorization"]?.toJWT() ?: throw 
                Exception("Missing principal")
                val roles = 
                JWTConfig.verifier.verify(jwtToken).getClaim("role")
                .toString().roleToSet()

                println("${globalconfig.requiredRoles} - $roles")

                if(roles.intersect(globalconfig.requiredRoles).isEmpty()){
                    call.respondText("You don`t have access to this resource.", status = 
                                    HttpStatusCode.Unauthorized)
                }
            }
        }
    }

    fun Route.withRole(role: String, build: Route.() -> Unit): Route {
        return withRoles(role, build = build)
    }

    fun Route.withRoles(vararg roles: String, build: Route.() -> Unit): Route {
        val authenticatedRoute = createChild(AuthorizationRouteSelector)

        /*authenticatedRoute.install(RoleAuthorizationPlugin) {
            this.requiredRoles = roles.toSet()
        }*/

        globalconfig.requiredRoles = roles.toSet()

        authenticatedRoute.build()
        return authenticatedRoute
    }

    object AuthorizationRouteSelector : RouteSelector() {
        override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): 
        RouteSelectorEvaluation {
            return RouteSelectorEvaluation.Transparent
        }

        override fun toString(): String = "(authorize \"default\" )"
    }
    
    fun String.roleToSet(): Set<String>{
        return split(",").map{ it.trim().replace("\"", "") }.toSet()
    }

    fun String.toJWT() = removePrefix("Bearer ")

然后在routes文件中,我调用withRole和withRoles方法,如下所示:

route("/auth") {
        authenticate {
            withRoles("user", "admin){
                get("register"){
                    //Implementar el registro
                }
            }
        }
            withRole("admin"){
                get("test"){
                    call.respond("Test")
                }
            }
    }

当我用用户类型user请求/auth/register时,我得到401,println打印“[admin] - [user]”,据我所知,只有最后一次调用withRole或withRoles的角色被保存。
我一直在研究,但我找不到解决方案,使其正常工作.我想使用createRouteScopePlugin(...)而不是createApplicationPlugin(...),但事实证明,如果我使用第一个选项,我只能调用withRole或withRoles一次,如果我调用它多次抛出一个异常说,该插件已经安装.

eeq64g8w

eeq64g8w1#

我在你的解决方案中看到了两个问题。第一,你使用了一个在路由之间共享的全局配置。第二,AuthenticationChecked钩子只在路由级别执行,但你的插件是应用程序范围的。
我建议在插件的配置中存储所需的角色,并为每个授权范围安装插件。

fun Route.withRoles(vararg roles: String, build: Route.() -> Unit) {
    val route = createChild(object : RouteSelector() {
        override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
            return RouteSelectorEvaluation.Transparent
        }

    })
    route.install(RoleAuthorizationPlugin) {
        roles(roles.toSet())
    }

    route.build()
}

class RoleBaseConfiguration {
    val requiredRoles = mutableSetOf<String>()
    fun roles(roles: Set<String>) {
        requiredRoles.addAll(roles)
    }
}

val RoleAuthorizationPlugin = createRouteScopedPlugin("RoleAuthorizationPlugin", ::RoleBaseConfiguration) {
    on(AuthenticationChecked) { call ->
        val principal = call.principal<JWTPrincipal>() ?: return@on
        val roles = principal.payload.getClaim("role").asList(String::class.java).toSet()

        if (pluginConfig.requiredRoles.isNotEmpty() && roles.intersect(pluginConfig.requiredRoles).isEmpty()) {
            call.respondText("You don`t have access to this resource.", status = HttpStatusCode.Unauthorized)
        }
    }
}

以下是路由中插件使用的示例:

routing {
    authenticate("auth-jwt") {
        withRoles("admin") {
            get("/admin") {
                call.respondText { "For admin only" }
            }
        }

        withRoles("registered") {
            get("/reg") {
                call.respondText { "For registered users" }
            }
        }

        withRoles {
            get("/anyone") {
                call.respondText { "For anyone" }
            }
        }
    }
}

相关问题