본문 바로가기

카테고리 없음

24.02.07 댓글, 게시글, 로그인 수정. 토큰

게시글 조회 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