Files
sodalive-backend-spring-boot/src/main/kotlin/kr/co/vividnext/sodalive/jwt/TokenProvider.kt

134 lines
4.9 KiB
Kotlin

package kr.co.vividnext.sodalive.jwt
import io.jsonwebtoken.ExpiredJwtException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.MalformedJwtException
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.UnsupportedJwtException
import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.security.Keys
import io.jsonwebtoken.security.SignatureException
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.MemberAdapter
import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.token.MemberToken
import kr.co.vividnext.sodalive.member.token.MemberTokenRepository
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.repository.findByIdOrNull
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.stereotype.Component
import java.security.Key
import java.util.Date
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
// 토큰의 생성, 유효성 검증 담당 클래스
@Component
class TokenProvider(
@Value("\${jwt.secret}")
private val secret: String,
@Value("\${jwt.token-validity-in-seconds}")
private val tokenValidityInSeconds: Long,
private val repository: MemberRepository,
private val tokenRepository: MemberTokenRepository
) : InitializingBean {
private val logger = LoggerFactory.getLogger(TokenProvider::class.java)
private val tokenValidityInMilliseconds: Long = tokenValidityInSeconds * 1000
private val tokenLocks: MutableMap<Long, ReentrantReadWriteLock> = mutableMapOf()
private lateinit var key: Key
override fun afterPropertiesSet() {
val keyBytes = Decoders.BASE64.decode(secret)
this.key = Keys.hmacShaKeyFor(keyBytes)
}
fun createToken(authentication: Authentication, memberId: Long): String {
val authorities = authentication.authorities
.joinToString(separator = ",", transform = GrantedAuthority::getAuthority)
val now = Date().time
val validity = Date(now + tokenValidityInMilliseconds)
val token = Jwts.builder()
.setSubject(memberId.toString())
.claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact()
val lock = getOrCreateLock(memberId = memberId)
lock.write {
val memberToken = tokenRepository.findByIdOrNull(memberId)
?: MemberToken(id = memberId)
memberToken.tokenSet.add(token)
tokenRepository.save(memberToken)
}
return token
}
fun getAuthentication(token: String): Authentication {
val claims = Jwts
.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.body
val authorities = claims[AUTHORITIES_KEY].toString().split(",").map { SimpleGrantedAuthority(it) }
val memberToken = tokenRepository.findByIdOrNull(id = claims.subject.toLong())
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (!memberToken.tokenSet.contains(token)) {
throw SodaException(messageKey = "common.error.bad_credentials")
}
val member = repository.findByIdOrNull(id = claims.subject.toLong())
?: throw SodaException(messageKey = "common.error.bad_credentials")
val principal = MemberAdapter(member)
return UsernamePasswordAuthenticationToken(principal, token, authorities)
}
fun validateToken(token: String): Boolean {
try {
Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
return true
} catch (e: SecurityException) {
logger.info("잘못된 JWT 서명입니다.")
} catch (e: MalformedJwtException) {
logger.info("잘못된 JWT 서명입니다.")
} catch (e: ExpiredJwtException) {
logger.info("만료된 JWT 서명입니다.")
} catch (e: UnsupportedJwtException) {
logger.info("지원되지 않는 JWT 서명입니다.")
} catch (e: IllegalArgumentException) {
logger.info("JWT 토큰이 잘못되었습니다.")
} catch (e: SignatureException) {
logger.info("잘못된 JWT 서명입니다.")
}
return false
}
private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock {
return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() }
}
companion object {
private const val AUTHORITIES_KEY = "auth"
}
}