๐Ÿ”ฅ TIL - Day 75 Kotlin & Springboot 06 SpringSecurity ์—†์ด ํ† ํฐ๊ธฐ๋ฐ˜ ์ธ์ฆ ๊ตฌํ˜„ 2 (๊ถŒํ•œ๊ฒ€์ฆ + ๊ฐ™์€ URI์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ - ํ”„๋ก์‹œ ํŒจํ„ด)

Kim Dae Hyunยท2021๋…„ 12์›” 22์ผ
0

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
86/93

์ด์ „์— Spring Security ์—†์ด ๊ตฌํ˜„ํ•œ ์ธ์ฆ์—๋Š” ๊ถŒํ•œ์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ํฌํ•จ๋˜์ง€ ์•Š์•˜๋‹ค.

์ด๋ฒˆ์—๋Š” ๊ถŒํ•œ์— ๋Œ€ํ•œ ๊ฒ€์ฆ๊ณผ ๊ฐ™์€ URI์ด๋”๋ผ๋„ Http Method์— ๋”ฐ๋ผ ์ธ์ฆํ•„์š”์—ฌ๋ถ€๋ฅผ ๋ถ„๊ธฐ์‹œํ‚ค๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•œ๋‹ค.

๐Ÿ“Œ ๊ถŒํ•œ๊ฒ€์ฆ ์ถ”๊ฐ€

Jwt ํ† ํฐ ์ƒ์„ฑ์‹œ Claim์œผ๋กœ ๊ถŒํ•œ์„ ๊ฐ–๋„๋ก ํ•œ๋‹ค.
๋‹ค์Œ์œผ๋กœ Jwt์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋กœ์ง์„ ๋‹ค๋ฃจ๋Š” JwtUtils ํด๋ž˜์Šค์— ๊ถŒํ•œ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

    fun verifySellerToken(token: String): Boolean {
        try {
            val claims = getAllClaims(token)

            val expiration = claims.expiration // ๋งŒ๋ฃŒ๊ธฐ๊ฐ„ ๊ฒ€์ฆ
            if (!expiration.after(Date())) return false

            when(val role = claims["role"]) { 
                is String -> {
                    return role.equals("ROLE_SELLER") // ๊ถŒํ•œ๊ฒ€์ฆ
                }
                else -> throw IllegalArgumentException("ํƒ€์ž…์—๋Ÿฌ")
            }
        } catch (e: JwtException) {
            return false
        } catch (e: IllegalArgumentException) {
            return false
        }
    }

๐Ÿ“Œ ๊ถŒํ•œ๊ฒ€์ฆ์„ ์œ„ํ•œ ์ธํ„ฐ์…‰ํ„ฐ ์ •์˜

ํŒ๋งค์ž ๊ถŒํ•œ(ROLE_SELLER)์˜ ๊ฒ€์ฆ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์•„๋ž˜ ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ํ†ตํ•˜๋„๋ก ์„ค์ •ํ•ด์ค€๋‹ค.

@Component
class RoleVerifyInterceptor(private val jwtTokenProvider: JwtTokenProvider): HandlerInterceptor {

    override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
        if(isPreRequest(request)) return true

        val token = JwtTokenExtractor.extract(request)
        token?.let {
            if(jwtTokenProvider.verifySellerToken(it)) return true
        }

        throw AuthenticationException("์ธ์ฆ์‹คํŒจ")
    }

    private fun isPreRequest(request: HttpServletRequest): Boolean {
        return request.method.equals(HttpMethod.OPTIONS)
    }
}

๐Ÿ“Œ Http Method์— ๋”ฐ๋ผ ์ธํ„ฐ์…‰ํ„ฐ ์ ์šฉ์—ฌ๋ถ€ ํŒ๋‹จํ•˜๊ธฐ

๊ธฐ์กด์—๋Š” ์š”์ฒญ URI๋กœ๋งŒ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ธํ„ฐ์…‰ํ„ฐ์˜ ์ ์šฉ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ–ˆ๋‹ค.
ํ•˜์ง€๋งŒ Rest API์˜ ๊ฒฝ์šฐ ๊ฐ™์€ URI์— Http Method๋งŒ ๋‹ฌ๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๋‹ค.

ex)
GET /articles => ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ (์ธ์ฆ ๋ถˆํ•„์š”)
POST /articles => ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก (์ธ์ฆ ํ•„์š”)

์ธํ„ฐ์…‰ํ„ฐ์—์„œ๋Š” HttpServletRequest๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์š”์ฒญ URI์™€ HTTP ๋ฉ”์„œ๋“œ๋ฅผ ์•Œ์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ชจ๋“  ์š”์ฒญ์˜ ๋ฉ”์„œ๋“œ๋งˆ๋‹ค ๋ถ„๊ธฐ์ž‘์—…์„ ํ•˜๋Š” ๊ฒƒ์€ ๋„ˆ๋ฌด ๋ฒˆ๊ฑฐ๋กญ๊ณ  ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์ด ๋  ๊ฒƒ์ด๋‹ค.
(if... if ... if ... if..........)

์ด๋Ÿฐ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ์ธ์ฆ ์ธํ„ฐ์…‰ํ„ฐ ์ ์šฉ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๋กœ์ง์„ ๋–ผ์–ด๋‚ผ ๊ฒƒ์ด๋‹ค.

class PatternMatcherInterceptor(
    var targetInterceptor: HandlerInterceptor, // ์ ์šฉ๋  ์ธํ„ฐ์…‰ํ„ฐ
) : HandlerInterceptor {

    var addPathPatterns: MutableMap<String, HttpMethod> = mutableMapOf() // ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์ ์šฉ์กฐ๊ฑด <์š”์ฒญURI, ์š”์ฒญ๋ฉ”์„œ๋“œ>

    fun addPathPatterns(uri: String, httpMethod: HttpMethod) = this.addPathPatterns.put(uri, httpMethod)

    override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
        val method = request.method
        val requestURI = request.requestURI

        // URI์™€ ์š”์ฒญ๋ฉ”์„œ๋“œ๊ฐ€ ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ targetInterceptor ๋ฅผ ํ†ตํ•˜๋„๋ก ์ฒ˜๋ฆฌ
        for (uri in addPathPatterns.keys) {
            if (requestURI.equals(uri) && addPathPatterns[uri].toString() == method) {
                return targetInterceptor.preHandle(request,response, handler)
            }
        }

        return true
    }
}
  • ์‹ค์ œ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•  ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์ฃผ์ž…๋ฐ›๋Š”๋‹ค.
  • ์‹ค์ œ ๊ฒ€์ฆ์ด ์ˆ˜ํ–‰๋  URI, HTTP Method์— ๋Œ€ํ•œ ์„ค์ •์„ ์œ„ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•œ๋‹ค. (addPathPatterns)
  • ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์˜ URI์™€ HTTP Method๊ฐ€ addPathPatterns์— ์กด์žฌํ•œ๋‹ค๋ฉด ์‹ค์ œ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•  ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. (targetInterceptor.preHandle)

์‹ค์ œ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ธํ„ฐ์…‰ํ„ฐ ์•ž ๋‹จ์—์„œ ํ”„๋ก์‹œ ๋Š๋‚Œ์œผ๋กœ ์š”์ฒญ์„ ๋งค์นญํ•ด์ฃผ๋Š” ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•œ ๊ฒƒ์ด๋‹ค.


๐Ÿ“Œ ์ธํ„ฐ์…‰ํ„ฐ ๋ฐ ArgumentResolver ๋“ฑ๋ก

@Configuration
class WebMvcConfig(
    private val tokenVerifyInterceptor: TokenVerifyInterceptor,
    private val roleVerifyInterceptor: RoleVerifyInterceptor,
    private val authenticatedUserArgumentResolver: AuthenticatedUserArgumentResolver) : WebMvcConfigurer{

    override fun addInterceptors(registry: InterceptorRegistry) {
        val patternMatcherInterceptor1 = PatternMatcherInterceptor(roleVerifyInterceptor) // ํŒ๋งค์ž ๊ถŒํ•œ ๊ฒ€์ฆ์„ ์œ„ํ•œ ์ธํ„ฐ์…‰ํ„ฐ
        patternMatcherInterceptor1.addPathPatterns("/test/auth-test/seller", HttpMethod.GET)

        val patternMatcherInterceptor2 = PatternMatcherInterceptor(tokenVerifyInterceptor) // ์ผ๋ฐ˜ ๊ฒ€์ฆ ์ธํ„ฐ์…‰ํ„ฐ
        patternMatcherInterceptor2.addPathPatterns("/items", HttpMethod.POST)
        patternMatcherInterceptor2.addPathPatterns("/test/auth-test", HttpMethod.GET)

        registry.addInterceptor(patternMatcherInterceptor1)
            .addPathPatterns("/**")
        registry.addInterceptor(patternMatcherInterceptor2)
            .addPathPatterns("/**")
    }

    override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
        resolvers.add(authenticatedUserArgumentResolver)
    }
}

profile
์ข€ ๋” ์ฒœ์ฒœํžˆ ๊นŒ๋จน๊ธฐ ์œ„ํ•ด ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ๐Ÿง

0๊ฐœ์˜ ๋Œ“๊ธ€