게시글 조회 API 호출시 해당 게시글의 댓글 목록도 응답을 하려면,
data class PostDto(
val id: Long,
val title: String,
val nickname: String,
val content: String,
val createdAt: LocalDateTime,
val userId: Long,
val comments: List<CommentDto> // 이 부분을 추가
) {
companion object {
fun from(post: Post, comments: List<CommentDto> = listOf()): PostDto {
return PostDto(
id = post.id!!,
title = post.title,
nickname = post.nickname,
content = post.content,
createdAt = post.createdAt,
userId = post.userId,
comments = comments //여기도 추가
)
}
}
}
interface CommentRepository : JpaRepository<Comment, Long> {
fun findByIdAndUserId(id: Long, userId: Long): Optional<Comment>
fun findByPostId(postId: Long): List<Comment>// 이렇게 게시글에 댓글목록 가져옴
}
override fun getPost(postId: Long): PostDto {
val post = postRepository.findById(postId)
.orElseThrow { IllegalArgumentException("해당 id의 게시글이 존재하지 않습니다.") }
val comments = commentRepository.findByPostId(postId) // postId에 해당하는 Comment 객체들을 조회
val commentDtos = comments.map { CommentDto.from(it) } // Comment 객체들을 CommentDto로 변환
return PostDto.from(post, commentDtos) // 변환된 CommentDto 리스트를 PostDto의 생성자에 전달
}
그런데 SwaggerConfig를 안넣으면 토큰 인증화면이 스웨거에서 안뜬다는걸 처음 알았다. 그래서
package com.example.demo
import io.swagger.v3.oas.models.Components
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Info
import io.swagger.v3.oas.models.security.SecurityRequirement
import io.swagger.v3.oas.models.security.SecurityScheme
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class SwaggerConfig {
@Bean
fun openAPI(): OpenAPI {
return OpenAPI()
.addSecurityItem(
SecurityRequirement().addList("Bearer Authentication")
)
.components(
Components().addSecuritySchemes(
"Bearer Authentication",
SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("Bearer")
.bearerFormat("JWT")
.`in`(SecurityScheme.In.HEADER)
.name("Authorization")
)
)
.info(
Info()
.title("Course API")
.description("Course API schema")
.version("1.0.0")
)
}
}
그럼 이제 인증버튼이 생겨난다.
그런데, 회원가입과 로그인도 인증을 먼저 받으라면서 안된다? 그건 시큐리티 컨피그가 login, signup이라고 되어있어서 그런거다. 스웨거를 열어보면 /api/members/signup 이런식으로 되어있으니 그걸 똑같이 넣어줘야 한다.
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig(
private val jwtAuthenticationFilter: JwtAuthenticationFilter,
private val authenticationEntrypoint: AuthenticationEntryPoint
) {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
return http
.httpBasic { it.disable() }
.formLogin { it.disable() }
.csrf { it.disable() }
.authorizeHttpRequests {
it.requestMatchers(
"/api/members/login",// 이런식으로 형태가 스웨거랑 같아야한다.
"/api/members/signup",
"/swagger-ui/**",
"/v3/api-docs/**",
).permitAll()
.anyRequest().authenticated()
}
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
.exceptionHandling{
it.authenticationEntryPoint(authenticationEntrypoint)
}
.build()
}
}
그런데, 로그인을 했더니 토큰이 보이질 않는다?
먼저, 로그인리스폰스와 연결이 되어야 한다.
package com.example.demo.dto
data class LoginResponse(
val accessToken: String
)
그리고 이걸 서비스에 연결
interface MemberService {
fun signUp(signUpRequest: SignUpRequest): Member
fun login(loginRequest: LoginRequest): LoginResponse
}
그리고 서비스 임플에
override fun login(loginRequest: LoginRequest): LoginResponse {
val member = memberRepository.findByNickName(loginRequest.nickName)
?: throw IllegalArgumentException("닉네임 또는 패스워드를 확인해주세요.")
if (member.password != loginRequest.password) {
throw IllegalArgumentException("닉네임 또는 패스워드를 확인해주세요.")
}
return LoginResponse(
accessToken = jwtPlugin.generateAccessToken(
subject = member.id.toString(), // 사용자의 ID를 subject로 사용
nickName = member.nickName // 사용자의 닉네임을 nickName으로 사용
)
)
}
로그인 리스폰스를 받아오고 토큰에 접속하고 서브젝트도 추가
@Component
class JwtPlugin() {
companion object {
const val SECRET = "PO4c8z41Hia5gJG3oeuFJMRYBB4Ws4aZ"
const val ISSUER = "team.sparta.com"
const val ACCESS_TOKEN_EXPIRATION_HOUR: Long = 240
}
fun validateToken(jwt: String): Result<Jws<Claims>> {
return kotlin.runCatching {
val key = Keys.hmacShaKeyFor(SECRET.toByteArray(StandardCharsets.UTF_8))
Jwts.parser().verifyWith(key).build().parseSignedClaims(jwt)
}
}
fun generateAccessToken(subject: String, nickName: String): String {
val expirationPeriod: Long = Duration.ofHours(ACCESS_TOKEN_EXPIRATION_HOUR).toMillis()
return generateToken(subject, nickName, expirationPeriod)
}
private fun generateToken(subject: String, nickName: String, expirationPeriod : Long): String {
val claims: Claims = Jwts.claims()
.add(mapOf("nickName" to nickName))
.build()
val key = Keys.hmacShaKeyFor(SECRET.toByteArray(StandardCharsets.UTF_8))
val now = Instant.now()
return Jwts.builder()
.subject(subject)
.issuer(ISSUER)
.issuedAt(Date.from(now))
.expiration(Date.from(now.plusMillis(expirationPeriod)))
.claims(claims)
.signWith(key)
.compact()
}
}
제너레이트 어세스 토큰에 저렇게 서브젝트를 넣고 밑에 리턴에도 서브젝트를 넣는다
@PostMapping("/login")
fun login(@RequestBody loginRequest: LoginRequest): ResponseEntity<Any> {
return try {
val loginResponse = memberService.login(loginRequest)
ResponseEntity.ok().body(loginResponse) // LoginResponse 객체를 HTTP 응답 본문에 담아 반환
} catch (e: IllegalArgumentException) {
ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.message)
}
}
그럼 이제 토큰이 생성된다.
회원코드
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZWFtLnNwYXJ0YS5jb20iLCJpYXQiOjE3MDczMDM5ODUsImV4cCI6MTcwODE2Nzk4NSwic3ViIjoiMSIsIm5pY2tOYW1lIjoiQWEyMyJ9.CLmhtHp1dU4imiho9AUAgM41HWXiHZMMxz6ROqKJ510