우선 카카오톡에 왼쪽 메뉴에서 보안에 들어가서
client-secret: sTwGq7Ac21lQcNGYlyxkVagNDYXwnMXO
이걸 받아왔다.
server:
port: 8080
security:
oauth2:
client:
registration:
kakao:
client-id: 0299d5f2a30184aea5d21f83481d2a7e
client-secret: sTwGq7Ac21lQcNGYlyxkVagNDYXwnMXO # 여기에 client-secret 추가
redirect-uri: http://localhost:8080/oauth2/callback/kakao
authorization-grant-type: authorization_code
client-authentication-method: client_secret_post
client-name: kakao
scope:
- profile_nickname
- account_email
provider:
kakao:
authorization_uri: https://kauth.kakao.com/oauth/authorize
token_uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user_name_attribute: id
이런식으로 yml에 넣어두면 된다고 한다.
그리고 어제의
Parameter 0 of method setFilterChains in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'org.springframework.security.oauth2.client.registration.ClientRegistrationRepository' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.oauth2.client.registration.ClientRegistrationRepository' in your configuration.
을 해결하기위해 ClientRegistrationRepository를 고쳐야 하는데,
@Configuration
@EnableWebSecurity
class SecurityConfig(
private val jwtAuthenticationFilter: JwtAuthenticationFilter,
private val authenticationEntryPoint: AuthenticationEntryPoint,
private val accessDeniedHandler: AccessDeniedHandler,
private val oAuth2UserService: OAuth2UserService,
private val oAuth2LoginSuccessHandler: OAuth2LoginSuccessHandler
) {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
return http
.httpBasic { it.disable() }
.formLogin { it.disable() }
.csrf { it.disable() }
.headers { it.frameOptions { options -> options.sameOrigin() } }
.authorizeHttpRequests {
it.requestMatchers(
"/login", "/signup", "/swagger-ui/**", "/v3/api-docs/**", "/oauth2/login",
"/oauth2/callback/**"
).permitAll()
.anyRequest().authenticated()
}
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
.exceptionHandling {
it.authenticationEntryPoint(authenticationEntryPoint)
it.accessDeniedHandler(accessDeniedHandler)
}
.oauth2Login { oauthConfig ->
oauthConfig.authorizationEndpoint {
it.baseUri("/oauth2/login")
}.redirectionEndpoint {
it.baseUri("/oauth2/callback/*")
}.userInfoEndpoint {
it.userService(oAuth2UserService)
}.successHandler(oAuth2LoginSuccessHandler)
}
.sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.build()
}
@Bean
fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(
kakaoClientRegistration() // 구글과 네이버 클라이언트 등록 부분 제거
)
}
private fun kakaoClientRegistration(): ClientRegistration {
return ClientRegistration.withRegistrationId("kakao")
.clientId("your-kakao-client-id")
.clientSecret("your-kakao-client-secret")
.redirectUri("{baseUrl}/oauth2/callback/{registrationId}") // 여기서 redirectUriTemplate을 redirectUri로 바꿔보세요
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.scope("profile_nickname", "account_email")
.authorizationUri("https://kauth.kakao.com/oauth/authorize")
.tokenUri("https://kauth.kakao.com/oauth/token")
.userInfoUri("https://kapi.kakao.com/v2/user/me")
.userNameAttributeName("id")
.clientName("Kakao")
.build()
}
}
이렇게 @Bean
fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(
kakaoClientRegistration() // 구글과 네이버 클라이언트 등록 부분 제거
)
}를 추가헤서 에러를 해결할수 있었다.
그 다음엔
Code Details 401 Undocumented Error: response status is 401
Response body Download { "cause": null, "stackTrace": [ { "classLoaderName": "app", "moduleName": null, "moduleVersion": null, "methodName": "commence", "fileName": "CustomAuthenticationEntrypoint.kt", "lineNumber": 24, "className": "org.example.domain.infra.securety.CustomAuthenticationEntrypoint", "nativeMethod": false }
라는 에러가 나타났는데
@Component
class CustomAuthenticationEntrypoint : AuthenticationEntryPoint {
override fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
authException: AuthenticationException,
) {
response.status = HttpServletResponse.SC_UNAUTHORIZED
response.contentType = MediaType.APPLICATION_JSON_VALUE
response.characterEncoding = "UTF-8"
val objectMapper = ObjectMapper()
val jsonString = objectMapper.writeValueAsString(Error("JWT verification failed"))
response.writer.write(jsonString)
}
이것을
@Component
class CustomAuthenticationEntrypoint : AuthenticationEntryPoint {
override fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
authException: AuthenticationException,
) {
response.status = HttpServletResponse.SC_UNAUTHORIZED
response.contentType = MediaType.APPLICATION_JSON_VALUE
response.characterEncoding = "UTF-8"
val objectMapper = ObjectMapper()
try {
val errorResponse = ErrorResponseDto("JWT verification failed")
val jsonString = objectMapper.writeValueAsString(errorResponse)
response.writer.write(jsonString)
} catch (e: Exception) {
// 추가적인 예외 처리를 할 수 있습니다.
response.writer.write("{\"message\": \"An error occurred\"}")
}
}
}
문제의 원인
이전 코드에서 발생한 문제는 Error 클래스를 JSON으로 직렬화(serialize)하려고 시도했기 때문입니다. Error 클래스는 일반적으로 예외를 표현하는 데 사용되는 클래스이며, 이를 JSON으로 변환하는 과정에서 예상치 못한 문제들이 발생할 수 있습니다. 특히, Error 클래스는 내부적으로 직렬화하기 어려운 정보들을 포함하고 있을 수 있습니다.
val jsonString = objectMapper.writeValueAsString(Error("JWT verification failed"))
위 코드에서 Error 객체를 JSON 문자열로 변환하려고 했는데, 이 과정에서 문제가 발생했습니다. 이는 Error 클래스가 직렬화하기에 적합하지 않은 구조를 가지고 있기 때문입니다.
해결 방법
해결 방법은 간단합니다. Error 클래스 대신 직렬화에 적합한 간단한 데이터 전송 객체(DTO, Data Transfer Object)를 사용하는 것입니다. 이를 위해 ErrorResponseDto라는 커스텀 클래스를 만들고, 이 클래스를 사용하여 JSON으로 변환하였습니다.
수정된 코드
data class ErrorResponseDto(val message: String)
@Component
class CustomAuthenticationEntrypoint : AuthenticationEntryPoint {
override fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
authException: AuthenticationException,
) {
response.status = HttpServletResponse.SC_UNAUTHORIZED
response.contentType = MediaType.APPLICATION_JSON_VALUE
response.characterEncoding = "UTF-8"
val objectMapper = ObjectMapper()
try {
// ErrorResponseDto 객체를 사용하여 직렬화
val errorResponse = ErrorResponseDto("JWT verification failed")
val jsonString = objectMapper.writeValueAsString(errorResponse)
response.writer.write(jsonString)
} catch (e: Exception) {
// 예외 발생 시 기본 에러 메시지를 반환
response.writer.write("{\"message\": \"An error occurred\"}")
}
}
}
요약
문제의 원인: Error 클래스는 JSON으로 직렬화하기에 적합하지 않은 구조를 가지고 있습니다.
해결 방법: 직렬화에 적합한 간단한 데이터 클래스(ErrorResponseDto)를 만들어 사용했습니다.
추가 조치: try-catch 블록을 사용하여 직렬화 과정에서 발생할 수 있는 예외를 처리했습니다.