Ktor角色授权问题:全局配置覆盖与路由插件重复安装
问题描述
我用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) = withRoles(role, build = build) fun Route.withRoles(vararg roles: String, build: Route.() -> Unit) { val authenticatedRoute = createChild(AuthorizationRouteSelector) 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 ")
路由调用方式:
route("/auth") { authenticate { withRoles("user", "admin"){ get("register"){ //Implementar el registro } } } withRole("admin"){ get("test"){ call.respond("Test") } } }
问题现象:以user角色请求/auth/register接口时返回401,控制台输出[admin] - [user],可见所有路由复用了最后一次调用withRole/withRoles的角色配置。尝试改用createRouteScopePlugin替代createApplicationPlugin,但多次调用时会抛出插件已安装的异常,无法正常使用。
解决方案
核心问题是使用全局变量共享角色配置,导致所有路由分支复用同一规则。以下是两种可行的修复方案:
方案1:基于Route扩展属性+拦截器实现
无需插件,直接给每个路由分支绑定独立的角色要求,通过拦截器完成校验:
// 给Route添加扩展属性,存储当前路由分支需要的角色 private val Route.requiredRoles: MutableSet<String> by mutableStateOf(mutableSetOf()) fun Route.withRole(role: String, build: Route.() -> Unit) = withRoles(role, build = build) fun Route.withRoles(vararg roles: String, build: Route.() -> Unit) { val authRoute = createChild(AuthorizationRouteSelector) // 给当前路由分支设置专属角色要求 authRoute.requiredRoles.addAll(roles) // 添加认证后拦截器,校验角色 authRoute.intercept(ApplicationCallPipeline.Authentication) { val jwtToken = call.request.headers["Authorization"]?.toJWT() ?: run { call.respondText("Missing principal", status = HttpStatusCode.Unauthorized) finish() return@intercept } val userRoles = JWTConfig.verifier.verify(jwtToken).getClaim("role").toString().roleToSet() if (userRoles.intersect(authRoute.requiredRoles).isEmpty()) { call.respondText("You don't have access to this resource.", status = HttpStatusCode.Unauthorized) finish() } } authRoute.build() } object AuthorizationRouteSelector : RouteSelector() { override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { return RouteSelectorEvaluation.Transparent } override fun toString(): String = "(authorize)" } fun String.roleToSet(): Set<String> { return split(",").map { it.trim().replace("\"", "") }.toSet() } fun String.toJWT() = removePrefix("Bearer ")
方案2:正确使用createRouteScopePlugin
createRouteScopePlugin是路由范围的插件,需给每个子路由独立安装,避免全局复用:
internal class RoleBaseConfiguration( var requiredRoles: Set<String> = emptySet() ) internal val RoleAuthorizationPlugin = createRouteScopePlugin( name = "RoleAuthorizationPlugin", createConfiguration = ::RoleBaseConfiguration ) { on(AuthenticationChecked) { call -> val jwtToken = call.request.headers["Authorization"]?.toJWT() ?: run { call.respondText("Missing principal", status = HttpStatusCode.Unauthorized) return@on } val userRoles = JWTConfig.verifier.verify(jwtToken).getClaim("role").toString().roleToSet() if (userRoles.intersect(pluginConfig.requiredRoles).isEmpty()) { call.respondText("You don't have access to this resource.", status = HttpStatusCode.Unauthorized) } } } fun Route.withRole(role: String, build: Route.() -> Unit) = withRoles(role, build = build) fun Route.withRoles(vararg roles: String, build: Route.() -> Unit) { val authRoute = createChild(AuthorizationRouteSelector) // 给每个子路由独立安装插件并配置角色 authRoute.install(RoleAuthorizationPlugin) { requiredRoles = roles.toSet() } authRoute.build() } object AuthorizationRouteSelector : RouteSelector() { override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { return RouteSelectorEvaluation.Transparent } override fun toString(): String = "(authorize)" } fun String.roleToSet(): Set<String> { return split(",").map { it.trim().replace("\"", "") }.toSet() } fun String.toJWT() = removePrefix("Bearer ")
路由调用修正
确保所有需要认证的路由都嵌套在authenticate块内:
route("/auth") { authenticate { withRoles("user", "admin") { get("register") { call.respond("Register success") } } withRole("admin") { get("test") { call.respond("Test") } } } }
关键修复点
- 移除全局变量
globalconfig,避免路由间配置污染 - 每个路由分支独立存储/配置角色要求
- 用路由拦截器或路由范围插件实现独立的角色校验逻辑
内容的提问来源于stack exchange,提问作者bruno Diaz martin
相关产品推荐
相关产品推荐

