#4 Golang 인증 middleware 구현하기(gin framework)

2019. 12. 20. 10:17 개발 이야기/Golang

 

 

오늘은 Golang을 이용한 API 서버를 구현하던 중!

 

 

 

소스코드를 아주 효율적으로 줄여줄 수 있는 !

 

미들웨어를 작성해볼건데요!

 

이 미들웨어란 무엇을 이야기하는 걸까요?

 


미들웨어양 쪽을 연결하여 데이터를 주고받을 수 있도록 중간에서 매개 역할을 하는 소프트웨어, 네트워크를 통해서 연결된 여러 개의 컴퓨터에 있는 많은 프로세스들에게 어떤 서비스를 사용할 수 있도록 연결해주는 소프트웨어를 말합니다. 3계층 클라이언트/서버 구조에서 미들웨어가 존재합니다. 웹브라우저에서 데이터베이스로부터 데이터를 저장하거나 읽어올 수 있게 중간에 미들웨어가 존재하게 됩니다.

출처: https://12bme.tistory.com/289 [길은 가면, 뒤에 있다.]


 

이 블로그에서 이야기하는 의미가 정확이 맞는거 같아서 가져와봤는데요!

 

 

제가 Golang으로 웹서비스를 만들면서(물론 극초반에)

 

아무것도 모르고 개발을 하다보니 조금 불편한점이 있었습니다!

 

 

무엇이냐면 저는 JWT 토큰을 적용해서 개발중에 있는데요!

 

Golang JWT 토큰 적용하기

 

API 마다 토큰의 유효성을 검증해야하는 로직을 넣어야했었는데요

 

그리고 헤더마다 설정값을 계속 넣어줘야하는줄알고 계속 API리턴마다 필요한부분에

 

넣어줬었습니다 ㅜㅜ (무식하면 손발이 고생한다죠)

 

그래서 python에서 flask를 이용해서 개발했을 때

인증이 필요한 부분이나 특정 함수가 실행되기 전에 

 

실행하는

데코레이터(@)

생각이 났는데요

 

우리의 구글신이 만든 golang에도 당연히 이런게 있겠지! 라고 생각하고

 포풍 검색을 했습니다!

 

그렇게 찾은 결과가 바로 미들웨어인데요!

 

 

특정 API가 실행될 때마다 실행하게 해줄수도 있고

 

인증이 필요한 부분에만 미들웨어를 적용시켜서 인증로직을 추가할 수 있습니다!

 

그렇다면 기존에 작성하던 비효율적인 로직을 한번 보겠습니다.

 

    // return func(c *gin.Context) {
    token, err := c.Request.Cookie("access-token")
    if err != nil {
        // c.JSON(http.StatusUnauthorized,
        //     gin.H{"status": http.StatusUnauthorized, "error": "Authentication failed"})
        c.JSON(http.StatusUnauthorized,
            // 401
            gin.H{"status": http.StatusUnauthorized, "error": "Authentication failed"})
        c.Abort()
        return
    }
    // log.Println(token.Value)
    // Get the JWT string from the cookie
    tknStr := token.Value

    if tknStr == "" {
        c.JSON(http.StatusUnauthorized,
            // 401
            gin.H{"status": http.StatusUnauthorized, "error": "token is None"})
        c.Abort()
        return
    }

    // Initialize a new instance of `Claims`
    claims := &model.Claims{}

    // Parse the JWT string and store the result in `claims`.
    // Note that we are passing the key in this method as well. This method will return an error
    // if the token is invalid (if it has expired according to the expiry time we set on sign in),
    // or if the signature does not match
    _, err = jwt.ParseWithClaims(tknStr, claims, func(token *jwt.Token) (interface{}, error) {
        return model.JwtKey, nil
    })

    if err != nil {
        if err == jwt.ErrSignatureInvalid {
            c.JSON(http.StatusUnauthorized,
                // 401
                gin.H{"status": http.StatusUnauthorized, "error": "token is expired"})
            c.Abort()
            return
        }

        c.JSON(http.StatusForbidden,
            // 401
            gin.H{"status": http.StatusForbidden, "error": "Authentication failed"})
        c.Abort()
        return
    }

 

요런 로직을 함수로 구현해서

 

API마다 초입에 넣어줬었는데요

이부분은 여기서 확인해보시구요!

Golang JWT 토큰 적용하기

 

그리고 헤더 설정이 필요한 부분에는 아래와같이 소스코드를 넣어줬었습니다.

 

    c.Header("Access-Control-Allow-Origin", "*")
    c.Header("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0")
    c.Header("Last-Modified", time.Now().String())
    c.Header("Pragma", "no-cache")
    c.Header("Expires", "-1")

 

자 이렇게 계속해서 수동으로 넣어주다보니

아주~~ 비효율적인 코딩을 하는 것같은 기분이 들었고

 

그리고 코드량도 쓸데없이 길어지는 부분이 있었습니다 ㅜㅜ

 

그래서 이 부분을 미들웨어로 구현을해서 자동으로 특정 API에 적용되도록 해보겠습니다!

 

저는 이 블로그를 참고했습니다!

 

func DummyMiddleware(c *gin.Context) {
  fmt.Println("Im a dummy!")

  // Pass on to the next-in-chain
  c.Next()
}

func main() {
  // Insert this middleware definition before any routes
  api.Use(DummyMiddleware)
  // ... more code
}

 

DummyMiddleware를 작성한 후에 Use 메소드를 이용해서 적용시켯는데요

 

한번 어떻게 동작하는지 확인해보겠습니다!

 

이렇게 API를 들릴때마다 DummyMiddle가 실행되면서 

"""

Im a dummy!

"""

 

를 외치고 있는 모습을 볼 수 있는데요!

 

그러면 이렇게 작성법을 익혔으니

인증용 미들웨어를 구현해보고

header를 셋팅하는 미들웨어도 구현해보겠습니다

 

먼저 간단하게 헤더 셋팅용 소스를 만들어보겠습니다!

 

func SetHeader(c *gin.Context) {
    c.Header("Access-Control-Allow-Origin", "*")
    c.Header("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0")
    c.Header("Last-Modified", time.Now().String())
    c.Header("Pragma", "no-cache")
    c.Header("Expires", "-1")

    // Pass on to the next-in-chain
    c.Next()
}


func main() {
    r := gin.Default()

    r.Use(middleware.SetHeader) // 헤더 관리 미들웨어
    
    .......
    
 }

 

자 이렇게 헤더셋팅용 미들웨어를 넣고 헤더에 잘 적용이 되서 날라오는지

 

확인해보겠습니다

Postman을 이용해서 테스트를 해본 결과 아주 잘 적용되고 있는 것을 볼 수 있는데요!

 

그렇다면 이제 JWT 토큰을 검증해주는 미들웨어를 작성해보겠습니다.

 

func TokenAuthMiddleware(c *gin.Context) {
    token, err := c.Request.Cookie("access-token")
    if err != nil {
        // c.JSON(http.StatusUnauthorized,
        //     gin.H{"status": http.StatusUnauthorized, "error": "Authentication failed"})
        c.JSON(http.StatusUnauthorized,
            // 401
            gin.H{"status": http.StatusUnauthorized, "error": "Authentication failed"})
        c.Abort()
        return
    }
    // log.Println(token.Value)
    // Get the JWT string from the cookie
    tknStr := token.Value

    if tknStr == "" {
        c.JSON(http.StatusUnauthorized,
            // 401
            gin.H{"status": http.StatusUnauthorized, "error": "token is None"})
        c.Abort()
        return
    }

    // Initialize a new instance of `Claims`
    claims := &model.Claims{}

    // Parse the JWT string and store the result in `claims`.
    // Note that we are passing the key in this method as well. This method will return an error
    // if the token is invalid (if it has expired according to the expiry time we set on sign in),
    // or if the signature does not match
    _, err = jwt.ParseWithClaims(tknStr, claims, func(token *jwt.Token) (interface{}, error) {
        return model.JwtKey, nil
    })

    if err != nil {
        if err == jwt.ErrSignatureInvalid {
            c.JSON(http.StatusUnauthorized,
                // 401
                gin.H{"status": http.StatusUnauthorized, "error": "token is expired"})
            c.Abort()
            return
        }

        c.JSON(http.StatusForbidden,
            // 401
            gin.H{"status": http.StatusForbidden, "error": "Authentication failed"})
        c.Abort()
        return
    } else {
        c.Next()
    }
}

 

여기서 중요한 부분이 있는데요!

 

바로 c.Abort()라고 생긴 메소드입니다.

 

이 부분을 넣어주지않는다면 미들웨어에서 JWT 유효성검증이 되지 않은 요청에 대한 응답

실제 접근하고자 했던 API의 응답 두가지가 한꺼번에 가는 일이 발생하는데요!

 

무슨말인지 한번 확인해보겠습니다.

자 보이시나요? 

 

두 개의 응답이 한꺼번에 온 것을 확인할 수 있는데요

 

저도 이부분 때문에 애를좀 썩었습니다!

 

c.Abort() 꼭 넣어줍시다!

 

그리고는 이제 이 인증용 미들웨어를 꼭 필요한 부분에 넣어주어야겠죠!?

 

저는 setting이라는 그룹을 만들어서 이부분에만 Token 검증 미들웨어를 적용시켰는데요

이렇게 하면

URL/setting/userSetting

 

이렇게 접근하게되면 미들웨어에서 토큰 검증 후 넘어가게 됩니다!

 

엄청 유용하죠!?

 

저는 이 미들웨어를 통해 소스코드를 굉장히 줄일 수 있었습니다!

 

그리고 또 보기도 편리하죠 ㅎ

 

유용한 미들웨어 ! 꼭 사용해보시길 바랍니다!!