如何编写一个测试用例来验证OAuth令牌?

huangapple go评论85阅读模式
英文:

How to write a test case to verify OAuth token?

问题

我们有一个GoLang后端服务(启用了OAuth),它接受带有Authorization头部的HTTP请求,头部的值为"Bearer" + OAuthTokenString

如何编写一个单元测试或集成测试用例来验证后端服务是否启用了OAuth(验证令牌)?我不确定,我们无法创建一个启用了OAuth的模拟服务(httptest.NewServer)。

英文:

We have a GoLang backend service(OAuth enabled) that accepts http requests, with Authorization header with value "Bearer" + OAuthTokenString.

How to write a unit or integration test case for backend service to verify that backend service is OAuth enabled(verifies the token)? am not sure, we cannot create a mock service(httptest.NewServer) with OAuth enabled....

答案1

得分: 1

这是一个非常有趣的问题。我可以看出你的团队关心通过测试代码来最小化可能的错误。这是许多开发人员经常忽视的一个方面。

没有看到你的代码,所以很难为你的情况提供一个100%正确的答案。

我假设我的示例将作为编写你自己的测试的指南,或者在最好的情况下,优化我建议的示例。

我在我的项目中使用了gin gonic作为HTTP Web框架,并编写了一个名为Authenticate的方法,它作为每个受保护的端点的中间件被调用。然后,为了进行测试,我只创建了一个通过gin.Default()方法创建的HTTP服务器。

// Authenticate验证端点
func Authenticate() gin.HandlerFunc {
  return func(c *gin.Context) {
    var someErr errors.BukyError
    someErr.SetUnauthorized()

    // 从头部获取令牌
    requiredToken := c.GetHeader(constants.AuthorizationHeader)
    if len(requiredToken) == 0 {
      c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
      return
    }

    splittedToken := strings.SplitN(requiredToken, " ", 2)
    if len(splittedToken) != 2 || strings.ToLower(splittedToken[0]) != "bearer" {
      primErr := fmt.Errorf("wrong bearer token format on Authorization Header")
      someErr.PrimitiveErr = &primErr
      c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
      return
    }

    // 从编码的令牌中获取电子邮件
    jwtToken, claims, err := helpers.DecodeJWT(splittedToken[1], false)
    if err != nil {
      someErr.PrimitiveErr = &err
      c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
      return
    }

    if _, err := helpers.VerifyObjectIDs(claims.Subject); !err.IsNilError() {
      c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
      return
    }

    // 设置User变量,以便我们可以轻松地从其他中间件中检索
    // c.Set("User", result)
    c.Set(constants.ReqBukyJWTKey, jwtToken)
    c.Set(constants.ReqBukyClaimsKey, claims)

    // 调用下一个中间件
    c.Next()
  }
}

然后我只是按照以下方式进行测试:

func TestAuthenticate(t *testing.T) {
  userID := primitive.NewObjectID().Hex()
  email := "email@email.com"
  firstName := "My Name"
  lastName := "My Lastname"
  scopes := []string{"im_scope"}

  statusOK := "statusOK"
  someProtectedPath := constants.UsersPath + "/" + userID

  engine := gin.Default()
  engine.GET(someProtectedPath, Authenticate(), func(c *gin.Context) {
    c.String(http.StatusOK, statusOK)
  })

  t.Run("NoTokenHeader", func(t *testing.T) {
    t.Run("UnsetHeader", func(t *testing.T) {
      w := httptest.NewRecorder()
      req, _ := http.NewRequest("GET", someProtectedPath, nil)
      engine.ServeHTTP(w, req)
      assert.Equal(t, http.StatusUnauthorized, w.Code)
    })

    t.Run("EmptyHeader", func(t *testing.T) {
      w := httptest.NewRecorder()
      req, _ := http.NewRequest("GET", someProtectedPath, nil)
      req.Header.Set(constants.AuthorizationHeader, "")
      engine.ServeHTTP(w, req)
      assert.Equal(t, http.StatusUnauthorized, w.Code)
    })
  })

  t.Run("TokenWithBadFormat", func(t *testing.T) {
    t.Run("1", func(t *testing.T) {
      w := httptest.NewRecorder()
      req, _ := http.NewRequest("GET", someProtectedPath, nil)
      badFormatedToken := "hola.hola"
      req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", badFormatedToken))
      engine.ServeHTTP(w, req)
      assert.Equal(t, http.StatusUnauthorized, w.Code)
    })

    t.Run("2", func(t *testing.T) {
      w := httptest.NewRecorder()
      req, _ := http.NewRequest("GET", someProtectedPath, nil)
      badFormatedToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
      req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", badFormatedToken))
      engine.ServeHTTP(w, req)
      assert.Equal(t, http.StatusUnauthorized, w.Code)
    })

    t.Run("3", func(t *testing.T) {
      w := httptest.NewRecorder()
      req, _ := http.NewRequest("GET", someProtectedPath, nil)
      badFormatedToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.hola.hola.hola"
      req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearere %s", badFormatedToken))
      engine.ServeHTTP(w, req)
      assert.Equal(t, http.StatusUnauthorized, w.Code)
    })
  })

  t.Run("ExpiredToken", func(t *testing.T) {
    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", someProtectedPath, nil)
    expirationTime := time.Second
    expiredToken, _, err := helpers.GenerateAccessJWT(userID, email, firstName, lastName, scopes, expirationTime)
    time.Sleep(expirationTime * 2)
    req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", expiredToken))
    engine.ServeHTTP(w, req)
    assert.Equal(t, http.StatusUnauthorized, w.Code)
    assert.Nil(t, err)
  })

  t.Run("ValidToken", func(t *testing.T) {
    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", someProtectedPath, nil)
    validToken, _, err := helpers.GenerateAccessJWT(userID, email, firstName, lastName, scopes)
    req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", validToken))
    engine.ServeHTTP(w, req)
    assert.Nil(t, err)
    assert.Equal(t, http.StatusOK, w.Code)
  })
}

希望这可以帮助到你!

英文:

This is a very interesting question. I can see that your team is concerned about minimizing possible errors through testing the code. This is an aspect that many developers often forget.

Without having seen your code, it is a bit difficult to suggest a 100% correct answer for your case.

I will assume that my example will serve as a guide to write your own test or in the best case to optimize the example that I suggest

I was using gin gonic as the HTTP web framework for my project and I wrote a method Authenticate that is called as middleware for each protected endpoint. Then for testing I only created an http server through the gin.Default () method

// Authenticate auth an endpoint
func Authenticate() gin.HandlerFunc {
return func(c *gin.Context) {
var someErr errors.BukyError
someErr.SetUnauthorized()
// Fetch token from the headers
requiredToken := c.GetHeader(constants.AuthorizationHeader)
if len(requiredToken) == 0 {
c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
return
}
splittedToken := strings.SplitN(requiredToken, " ", 2)
if len(splittedToken) != 2 || strings.ToLower(splittedToken[0]) != "bearer" {
primErr := fmt.Errorf("wrong bearer token format on Authorization Header")
someErr.PrimitiveErr = &primErr
c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
return
}
// Get email from encoded token
jwtToken, claims, err := helpers.DecodeJWT(splittedToken[1], false)
if err != nil {
someErr.PrimitiveErr = &err
c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
return
}
if _, err := helpers.VerifyObjectIDs(claims.Subject); !err.IsNilError() {
c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
return
}
// Set the User variable so that we can easily retrieve from other middlewares
// c.Set("User", result)
c.Set(constants.ReqBukyJWTKey, jwtToken)
c.Set(constants.ReqBukyClaimsKey, claims)
// Call the next middlware
c.Next()
}
}

And then I just tested like following

func TestAuthenticate(t *testing.T) {
userID := primitive.NewObjectID().Hex()
email := "email@email.com"
firstName := "My Name"
lastName := "My Lastname"
scopes := []string{"im_scope"}
statusOK := "statusOK"
someProtectedPath := constants.UsersPath + "/" + userID
engine := gin.Default()
engine.GET(someProtectedPath, Authenticate(), func(c *gin.Context) {
c.String(http.StatusOK, statusOK)
})
t.Run("NoTokenHeader", func(t *testing.T) {
t.Run("UnsetHeader", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
t.Run("EmptyHeader", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
req.Header.Set(constants.AuthorizationHeader, "")
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
})
t.Run("TokenWithBadFormat", func(t *testing.T) {
t.Run("1", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
badFormatedToken := "hola.hola"
req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", badFormatedToken))
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
t.Run("2", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
badFormatedToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", badFormatedToken))
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
t.Run("3", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
badFormatedToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.hola.hola.hola"
req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearere %s", badFormatedToken))
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
})
t.Run("ExpiredToken", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
expirationTime := time.Second
expiredToken, _, err := helpers.GenerateAccessJWT(userID, email, firstName, lastName, scopes, expirationTime)
time.Sleep(expirationTime * 2)
req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", expiredToken))
engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Nil(t, err)
})
t.Run("ValidToken", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", someProtectedPath, nil)
validToken, _, err := helpers.GenerateAccessJWT(userID, email, firstName, lastName, scopes)
req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", validToken))
engine.ServeHTTP(w, req)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, w.Code)
})
}

huangapple
  • 本文由 发表于 2021年6月7日 14:29:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/67866882.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定