134 lines
4.9 KiB
Kotlin
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"
|
|
}
|
|
}
|