You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot Kotlin中@PreAuthorize权限控制失效问题排查

问题描述

在Spring Boot Kotlin项目中实现了基于数据库的自定义UserDetailsService,但出现权限控制异常:仅拥有ROLE_USER权限的新用户,居然能访问需要ROLE_ADMIN权限的/admin路由。尝试使用hasRole('ADMIN')和hasAuthority('ROLE_ADMIN')两种表达式都无效,而且getAuthorities()与loadUserByUsername方法内的打印语句完全不执行,即使开启logging.level.org.springframework.security=DEBUG日志也无法进行调试。此前在Java的OAuth2项目中,该路由的权限限制可以正常生效,怀疑是Kotlin特有的问题。

相关代码如下:

TestController代码

@RestController
class TestController() {

    @PreAuthorize("hasAuthority('ROLE_USER')")
    @GetMapping("/api/user")
    fun getUser() : String {
        return "hello user"
    }

    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @GetMapping("/api/admin")
    fun getAdmin() : String {
        return "hello admin"
    }
}

User实体类(实现UserDetails)

@Document(collection="users")
class User (
    val principalId: String,
    val name: String,
    val email: String, 
    val service: AuthService, 
    @JsonIgnore
    @get:JvmName("getPasswordJVM")
    var password: String? = null,
    val createdDate: Long = System.currentTimeMillis() / 1000,
    val draftGroups: MutableList<ObjectId> = mutableListOf<ObjectId>(),
    @Id 
    val id : ObjectId = ObjectId.get(),
    var accountNonExpired: Boolean = true,
    var accountNonLocked: Boolean = true,
    var credentialsNonExpired: Boolean = true,
    var enabled: Boolean = true,
    var roles: String = "ROLE_USER"
    ) : UserDetails {
        override fun equals(other: Any?): Boolean =
            other is User && other.name == name && other.email == email
        var admin : Boolean = false
        override fun getAuthorities() : List<GrantedAuthority> {
            val authorities = mutableListOf<GrantedAuthority>()
            for (role in roles.split(",")) {
                authorities.add(SimpleGrantedAuthority(role))
            }
            return authorities
        }
        override fun getUsername() : String {
            return this.name
        }
        override fun getPassword() : String {
            return this.password ?: "no password due to oauth"
        }
        override fun isAccountNonExpired() : Boolean {
            return this.accountNonExpired
        }
        override fun isAccountNonLocked() : Boolean {
            return this.accountNonLocked
        }
        override fun isCredentialsNonExpired() : Boolean {
            return this.credentialsNonExpired
        }
        override fun isEnabled() : Boolean  {
            return this.enabled
        }
    }

自定义UserDetailsService

@Service
public class MyUserDetailsService (private val userRepository: UserRepository) : UserDetailsService {

    init {
        println("user details service 13")
    }

    public override fun loadUserByUsername(s: String) : UserDetails {
        println("IN LOAD USER BY USERNAME")
        val users : List<User> = userRepository.findByName(s);

        if (users.size == 0)
            throw UsernameNotFoundException(String.format("Username[%s] not found"));
        
        return users[0]
    }
}

Security配置类

@EnableWebSecurity
@Configuration
public class SecurityConfig(
    private val authenticationSuccessHandler: AuthenticationSuccessHandler,
    private val myUserDetailsService: MyUserDetailsService) {
    @Throws(Exception::class)
    @Bean
    public fun override(http: HttpSecurity): SecurityFilterChain {
        return http
                .csrf{csrf -> csrf.disable()}
                .authorizeRequests{auth -> auth
                    auth.antMatchers("/api/user").authenticated()
                    auth.antMatchers("/api/admin").authenticated()
                }
                .userDetailsService(myUserDetailsService)
                .oauth2Login()
                .successHandler(authenticationSuccessHandler)
                //\.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                // csrf token is stored in browser as cookie XSRF-TOKEN, it is send along with POST requests in request header X-XSRF-TOKEN
                .and()
                .build()
    }

    @Bean
    fun passwordEncoder(): PasswordEncoder {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder()
    }
}

内容的提问来源于stack exchange,提问作者Ethan Michel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 09:42:36