UnsatisfiedDependencyException를 해결하기위해서 jwtplugin의 의존성이 부족하다고 판단을 내리고
auth:
jwt:
issuer: team.sparta.com
secret: PO4c8z41Hia5gJG3oeuFJMRYBB4Ws4aZ
accessTokenExpirationHour: 168
어플리케이션.yml에 이걸 추가해서
@Component
class JwtPlugin(
@Value("\${auth.jwt.issuer}") private val issuer: String,
@Value("\${auth.jwt.secret}") private val secret: String,
@Value("\${auth.jwt.accessTokenExpirationHour}") private val accessTokenExpirationHour: Long,
이렇게 JwtPlugin과 연결을 하면서 해결을 할수가 있었다.
class JwtAuthenticationFilter(
private val jwtPlugin: JwtPlugin
) : OncePerRequestFilter() {
@Throws(IOException::class, ServletException::class)
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
chain: FilterChain
) {
val header = request.getHeader("Authorization")
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response)
return
}
val token = header.replace("Bearer ", "")
val result = jwtPlugin.validateToken(token)
if (result.isSuccess) {
val claims: Jws<Claims> = result.getOrNull() ?: return
val body = claims.body
val email = body["email"] as String
val nickname = body["nickname"] as String
val authorities = mutableListOf<GrantedAuthority>()
val authentication = JwtAuthenticationToken(token, email, nickname, authorities)
SecurityContextHolder.getContext().authentication = authentication
}
chain.doFilter(request, response)
}
}
이것은 HTTP 요청이 들어올 때 JWT 토큰을 검증하는 말그 대로 필터의 역할을 한다. 이 검증에 통과해야 토큰을 만들수있다.
class JwtAuthenticationToken(
private val token: String,
private val email: String,
private val nickname: String,
authorities: Collection<GrantedAuthority>
) : AbstractAuthenticationToken(authorities) {
override fun getCredentials(): Any {
return token
}
override fun getPrincipal(): Any {
return email
}
fun getNickname(): String {
return nickname
}
}
이것은 스프링 시큐리티의 인증 과정에서 사용할 JWT 용 인증 토큰을 만들기 위해서 필요하다. 토큰엔 이메일, 닉네임, 권한 정보가 들어가서 이걸로 토큰들을 구분하는데 쓰인다
@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)
}
}
이 클래스는 주로 JWT 인증 오류가 발생했을 때 사용자에게 적절한 응답을 제공하기 위해 사용된다
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig(
private val jwtPlugin: JwtPlugin, // JwtPlugin 의존성 주입
private val authenticationEntrypoint: AuthenticationEntryPoint,
private val accessDeniedHandler: AccessDeniedHandler
) {
@Bean
fun jwtAuthenticationFilter(): JwtAuthenticationFilter {
// JwtAuthenticationFilter 빈 생성 및 JwtPlugin 의존성 주입
return JwtAuthenticationFilter(jwtPlugin)
}
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
return http
.httpBasic { it.disable() }
.formLogin { it.disable() }
.csrf { it.disable() }
.authorizeHttpRequests { authz ->
authz.requestMatchers(
"/login", "/signup", "/swagger-ui/**", "/v3/api-docs/**"
).permitAll()
.anyRequest().authenticated()
}
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter::class.java)
.exceptionHandling { eh ->
eh.authenticationEntryPoint(authenticationEntrypoint)
eh.accessDeniedHandler(accessDeniedHandler)
}
.build()
}
}
클래스 및 어노테이션 설명:
@Configuration: 이 클래스가 애플리케이션의 설정 정보를 담고 있다고 Spring Framework에 알립니다.
@EnableWebSecurity: Spring Security의 웹 보안 지원을 활성화하고, Spring MVC 통합을 제공합니다.
@EnableMethodSecurity: 메서드 수준의 보안을 활성화합니다. 이를 통해 서비스 레이어에서 메서드 호출에 대한 권한 검사를 할 수 있습니다.
주요 구성 요소:
JwtPlugin: JWT 처리를 위한 커스텀 플러그인으로, 의존성 주입을 통해 SecurityConfig에 주입됩니다.
AuthenticationEntryPoint와 AccessDeniedHandler: 인증 실패 또는 접근 거부와 같은 보안 관련 예외를 처리하는 데 사용됩니다. 이들도 의존성 주입을 통해 주입됩니다.
주요 메서드 및 설정:
jwtAuthenticationFilter(): JwtAuthenticationFilter 빈을 생성하고 반환합니다. 이 필터는 JWT를 사용한 인증 메커니즘을 담당합니다.
filterChain(HttpSecurity): Spring Security의 HTTP 보안 설정을 구성합니다. 주요 설정은 다음과 같습니다:
httpBasic()와 formLogin(): HTTP 기본 인증과 폼 로그인을 비활성화합니다. JWT 인증 방식을 사용하기 때문에 이러한 인증 방식은 필요하지 않습니다.
csrf(): CSRF 보호를 비활성화합니다. JWT를 사용하는 경우 일반적으로 CSRF 보호가 필요 없습니다.
authorizeHttpRequests(): 특정 경로에 대한 접근 권한을 설정합니다. 여기서는 "/login", "/signup", "/swagger-ui/", "/v3/api-docs/" 경로를 누구나 접근할 수 있도록 설정하고, 그 외의 모든 요청은 인증을 요구하도록 설정합니다.
addFilterBefore(): JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 이전에 추가하여, 인증 과정에서 JWT 필터가 먼저 실행되도록 합니다.
exceptionHandling(): 인증 실패나 접근 거부 시 처리할 AuthenticationEntryPoint와 AccessDeniedHandler를 설정합니다.
이렇게 대략적으로 완성했다.