시큐리티 설정

유저 API - 로그인, 회원가입, 계정정보 추가
This commit is contained in:
2023-07-23 03:26:17 +09:00
parent 23506e79f1
commit f81f07bd05
36 changed files with 1247 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
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, listOf())
val memberTokenSet = memberToken.tokenList.toMutableSet()
memberTokenSet.add(token)
memberToken.tokenList = memberTokenSet.toList()
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("로그인 정보를 확인해주세요.")
if (!memberToken.tokenList.contains(token)) throw SodaException("로그인 정보를 확인해주세요.")
val member = repository.findByIdOrNull(id = claims.subject.toLong())
?: throw SodaException("로그인 정보를 확인해주세요.")
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"
}
}