본문 바로가기

카테고리 없음

UnsatisfiedDependencyException 해결, jwt보강

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를 설정합니다.

 

 

이렇게 대략적으로 완성했다.