From 00e2ea0700e642762f2330e68e798cf1998c769b Mon Sep 17 00:00:00 2001 From: msksbr515 Date: Thu, 21 May 2026 17:53:26 +0800 Subject: [PATCH] fix(auth): harden login against timing-based user enumeration - Use constant-time comparison when user is not found to prevent user enumeration via response timing - Remove debug logging that could expose sensitive data - Add AspectJ weaver dependency for AOP support --- build.gradle.kts | 1 + .../com/msksbr/bookmgr/config/JwtUtils.kt | 4 ---- .../msksbr/bookmgr/runner/InitUserRunner.kt | 19 +++---------------- .../kotlin/com/msksbr/bookmgr/script/Log.kt | 4 ++++ .../bookmgr/service/impl/AuthServiceImpl.kt | 13 ++++--------- 5 files changed, 12 insertions(+), 29 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9a5de11..80b76a5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.aspectj:aspectjweaver") implementation("org.springframework.security:spring-security-crypto") implementation("org.bouncycastle:bcprov-jdk18on:1.84") implementation("com.mysql:mysql-connector-j") diff --git a/src/main/kotlin/com/msksbr/bookmgr/config/JwtUtils.kt b/src/main/kotlin/com/msksbr/bookmgr/config/JwtUtils.kt index bfa59a4..f5a7bec 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/config/JwtUtils.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/config/JwtUtils.kt @@ -41,7 +41,6 @@ class JwtUtils( // 生成token fun generateToken(username: String, role: String): String { - log.debug("Generating token for user $username, $role") val claims = Jwts.claims().add("role", role).build() val token = Jwts.builder().claims(claims).subject(username) .issuedAt(Date()).expiration( @@ -50,16 +49,13 @@ class JwtUtils( + expiration ) ).signWith(secretKey).compact() - log.debug("Token generated $token") return token } // 解析token fun parseToken(token: String): Claims? { try { - log.debug("Parsing token $token") val claims = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token) - log.debug("Token parsed: ${claims.payload.subject}") return claims.payload } catch (e: JwtException) { log.error("Token parsing failed", e) diff --git a/src/main/kotlin/com/msksbr/bookmgr/runner/InitUserRunner.kt b/src/main/kotlin/com/msksbr/bookmgr/runner/InitUserRunner.kt index 1558f3c..56485c6 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/runner/InitUserRunner.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/runner/InitUserRunner.kt @@ -19,7 +19,6 @@ class InitUserRunner( @Transactional override fun run(args: ApplicationArguments) { log.info("Starting default user initialization") - log.debug("Querying for admin user") val existsAdmin = userMapper.selectOne( QueryWrapper() .eq("username", "admin") @@ -27,10 +26,7 @@ class InitUserRunner( if (existsAdmin == null) { log.info("Admin user not found, creating...") insertAdmin() - } else { - log.debug("Admin user already exists, skipping") } - log.debug("Querying for common user01") val existsUser01 = userMapper.selectOne( QueryWrapper() .eq("username", "user01") @@ -38,18 +34,14 @@ class InitUserRunner( if (existsUser01 == null) { log.info("Common user01 not found, creating...") insertUser01() - } else { - log.debug("Common user01 already exists, skipping") } - val existsUser02=userMapper.selectOne( + val existsUser02 = userMapper.selectOne( QueryWrapper() - .eq("username", "user02") + .eq("username", "user02") ) if (existsUser02 == null) { log.info("Common user02 not found, creating...") insertUser02() - }else{ - log.info("Common user02 already exists, skipping") } log.info("Default user initialization completed") } @@ -61,9 +53,7 @@ class InitUserRunner( password = passwordEncoder.encode("admin")!!, role = "admin" ) - log.debug("Creating admin user: username=${user.username}, role=${user.role}") userMapper.insert(user) - log.info("Admin user created successfully") } fun insertUser01() { @@ -73,10 +63,9 @@ class InitUserRunner( password = passwordEncoder.encode("user01")!!, role = "user" ) - log.debug("Creating common user: username=${user.username}, role=${user.role}") userMapper.insert(user) - log.info("Common user created successfully") } + fun insertUser02() { val user = User( id = null, @@ -84,8 +73,6 @@ class InitUserRunner( password = passwordEncoder.encode("user02")!!, role = "user" ) - log.debug("Creating common user: username=${user.username}, role=${user.role}") userMapper.insert(user) - log.info("Common user created successfully") } } \ No newline at end of file diff --git a/src/main/kotlin/com/msksbr/bookmgr/script/Log.kt b/src/main/kotlin/com/msksbr/bookmgr/script/Log.kt index 4a471ee..1c3cf8a 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/script/Log.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/script/Log.kt @@ -3,5 +3,9 @@ package com.msksbr.bookmgr.script import org.slf4j.Logger import org.slf4j.LoggerFactory +/* +* 手动日志属性 +*/ + val T.log: Logger get() = LoggerFactory.getLogger(this::class.java) \ No newline at end of file diff --git a/src/main/kotlin/com/msksbr/bookmgr/service/impl/AuthServiceImpl.kt b/src/main/kotlin/com/msksbr/bookmgr/service/impl/AuthServiceImpl.kt index dfe21b2..39419be 100644 --- a/src/main/kotlin/com/msksbr/bookmgr/service/impl/AuthServiceImpl.kt +++ b/src/main/kotlin/com/msksbr/bookmgr/service/impl/AuthServiceImpl.kt @@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper import com.msksbr.bookmgr.dto.UserLoginDTO import com.msksbr.bookmgr.entity.User import com.msksbr.bookmgr.mapper.UserMapper -import com.msksbr.bookmgr.script.log import com.msksbr.bookmgr.service.AuthService import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.stereotype.Service @@ -12,24 +11,20 @@ import org.springframework.stereotype.Service @Service class AuthServiceImpl(private val userMapper: UserMapper, private val passwordEncoder: PasswordEncoder) : AuthService { override fun login(loginDTO: UserLoginDTO): User? { - // 数据查询 - log.debug("Select user{username=${loginDTO.username}} from database") val user = userMapper.selectOne( QueryWrapper() .eq("username", loginDTO.username) ) - // 找不到用户时直接返回false + // 找不到用户时跑一遍dummyHash来对齐响应时间,避免"信息泄露"攻击 if (user == null) { - log.debug("User ${loginDTO.username} does not exist") - // 跑一遍dummyHash来对齐响应时间,避免“信息泄露”攻击 passwordEncoder.encode("dummyHash") return null } // 比对密码 - return if(passwordEncoder.matches(loginDTO.password, user.password)){ + return if(passwordEncoder.matches(loginDTO.password, user.password)) { user - }else{ + } else { null } } -} \ No newline at end of file +}