Adds JWT-based auth with refresh tokens

This commit is contained in:
2026-04-09 15:57:26 -05:00
parent 3a53769421
commit 9633d95e75
21 changed files with 426 additions and 48 deletions

View File

@@ -0,0 +1,32 @@
package net.halfbinary.scavengerhuntapi.service
import net.halfbinary.scavengerhuntapi.repository.HunterRepository
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service
import java.util.Collections
@Service
class HunterDetailsService(private val hunterRepository: HunterRepository): UserDetailsService {
override fun loadUserByUsername(username: String): UserDetails {
hunterRepository.findByEmail(username)
?.let { hunter ->
val hunterAuthorities =
if (hunter.isAdmin) {
SimpleGrantedAuthority("ROLE_ADMIN")
} else {
SimpleGrantedAuthority("ROLE_USER")
}
return User(
hunter.email,
hunter.password,
Collections.singleton(hunterAuthorities)
)
}
throw UsernameNotFoundException("User Not Found with username: $username")
}
}

View File

@@ -0,0 +1,49 @@
package net.halfbinary.scavengerhuntapi.service
import net.halfbinary.scavengerhuntapi.config.JwtUtil
import net.halfbinary.scavengerhuntapi.error.exception.ExpiredRefreshTokenException
import net.halfbinary.scavengerhuntapi.error.exception.InvalidRefreshTokenException
import net.halfbinary.scavengerhuntapi.model.RefreshId
import net.halfbinary.scavengerhuntapi.model.record.RefreshTokenRecord
import net.halfbinary.scavengerhuntapi.repository.RefreshTokenRepository
import org.slf4j.LoggerFactory
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
@Service
class RefreshTokenService(private val refreshTokenRepository: RefreshTokenRepository, private val jwtUtil: JwtUtil) {
companion object {
private val log = LoggerFactory.getLogger(RefreshTokenService::class.java)
}
fun getAccessToken(tokenId: RefreshId): String {
return getToken(tokenId)?.let { refreshToken ->
if (isTokenExpired(refreshToken)) {
removeToken(tokenId)
throw ExpiredRefreshTokenException(tokenId)
} else {
jwtUtil.generateToken(refreshToken.email)
}
}?: throw InvalidRefreshTokenException(tokenId)
}
fun generateRefreshToken(email: String): RefreshId {
return refreshTokenRepository.save(RefreshTokenRecord(RefreshId.randomUUID(), email, LocalDateTime.now().plus(1, ChronoUnit.MONTHS))).token
}
fun isTokenExpired(token: RefreshTokenRecord): Boolean {
return token.expiryDateTime.isBefore(LocalDateTime.now())
}
fun getToken(token: RefreshId): RefreshTokenRecord? {
return refreshTokenRepository.findByIdOrNull(token)
}
fun removeToken(token: RefreshId) {
log.debug("Removing refresh token: $token")
refreshTokenRepository.deleteById(token)
}
}