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

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

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服务器。

  1. // Authenticate验证端点
  2. func Authenticate() gin.HandlerFunc {
  3. return func(c *gin.Context) {
  4. var someErr errors.BukyError
  5. someErr.SetUnauthorized()
  6. // 从头部获取令牌
  7. requiredToken := c.GetHeader(constants.AuthorizationHeader)
  8. if len(requiredToken) == 0 {
  9. c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
  10. return
  11. }
  12. splittedToken := strings.SplitN(requiredToken, " ", 2)
  13. if len(splittedToken) != 2 || strings.ToLower(splittedToken[0]) != "bearer" {
  14. primErr := fmt.Errorf("wrong bearer token format on Authorization Header")
  15. someErr.PrimitiveErr = &primErr
  16. c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
  17. return
  18. }
  19. // 从编码的令牌中获取电子邮件
  20. jwtToken, claims, err := helpers.DecodeJWT(splittedToken[1], false)
  21. if err != nil {
  22. someErr.PrimitiveErr = &err
  23. c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
  24. return
  25. }
  26. if _, err := helpers.VerifyObjectIDs(claims.Subject); !err.IsNilError() {
  27. c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
  28. return
  29. }
  30. // 设置User变量,以便我们可以轻松地从其他中间件中检索
  31. // c.Set("User", result)
  32. c.Set(constants.ReqBukyJWTKey, jwtToken)
  33. c.Set(constants.ReqBukyClaimsKey, claims)
  34. // 调用下一个中间件
  35. c.Next()
  36. }
  37. }

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

  1. func TestAuthenticate(t *testing.T) {
  2. userID := primitive.NewObjectID().Hex()
  3. email := "email@email.com"
  4. firstName := "My Name"
  5. lastName := "My Lastname"
  6. scopes := []string{"im_scope"}
  7. statusOK := "statusOK"
  8. someProtectedPath := constants.UsersPath + "/" + userID
  9. engine := gin.Default()
  10. engine.GET(someProtectedPath, Authenticate(), func(c *gin.Context) {
  11. c.String(http.StatusOK, statusOK)
  12. })
  13. t.Run("NoTokenHeader", func(t *testing.T) {
  14. t.Run("UnsetHeader", func(t *testing.T) {
  15. w := httptest.NewRecorder()
  16. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  17. engine.ServeHTTP(w, req)
  18. assert.Equal(t, http.StatusUnauthorized, w.Code)
  19. })
  20. t.Run("EmptyHeader", func(t *testing.T) {
  21. w := httptest.NewRecorder()
  22. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  23. req.Header.Set(constants.AuthorizationHeader, "")
  24. engine.ServeHTTP(w, req)
  25. assert.Equal(t, http.StatusUnauthorized, w.Code)
  26. })
  27. })
  28. t.Run("TokenWithBadFormat", func(t *testing.T) {
  29. t.Run("1", func(t *testing.T) {
  30. w := httptest.NewRecorder()
  31. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  32. badFormatedToken := "hola.hola"
  33. req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", badFormatedToken))
  34. engine.ServeHTTP(w, req)
  35. assert.Equal(t, http.StatusUnauthorized, w.Code)
  36. })
  37. t.Run("2", func(t *testing.T) {
  38. w := httptest.NewRecorder()
  39. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  40. badFormatedToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
  41. req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", badFormatedToken))
  42. engine.ServeHTTP(w, req)
  43. assert.Equal(t, http.StatusUnauthorized, w.Code)
  44. })
  45. t.Run("3", func(t *testing.T) {
  46. w := httptest.NewRecorder()
  47. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  48. badFormatedToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.hola.hola.hola"
  49. req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearere %s", badFormatedToken))
  50. engine.ServeHTTP(w, req)
  51. assert.Equal(t, http.StatusUnauthorized, w.Code)
  52. })
  53. })
  54. t.Run("ExpiredToken", func(t *testing.T) {
  55. w := httptest.NewRecorder()
  56. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  57. expirationTime := time.Second
  58. expiredToken, _, err := helpers.GenerateAccessJWT(userID, email, firstName, lastName, scopes, expirationTime)
  59. time.Sleep(expirationTime * 2)
  60. req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", expiredToken))
  61. engine.ServeHTTP(w, req)
  62. assert.Equal(t, http.StatusUnauthorized, w.Code)
  63. assert.Nil(t, err)
  64. })
  65. t.Run("ValidToken", func(t *testing.T) {
  66. w := httptest.NewRecorder()
  67. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  68. validToken, _, err := helpers.GenerateAccessJWT(userID, email, firstName, lastName, scopes)
  69. req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", validToken))
  70. engine.ServeHTTP(w, req)
  71. assert.Nil(t, err)
  72. assert.Equal(t, http.StatusOK, w.Code)
  73. })
  74. }

希望这可以帮助到你!

英文:

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

  1. // Authenticate auth an endpoint
  2. func Authenticate() gin.HandlerFunc {
  3. return func(c *gin.Context) {
  4. var someErr errors.BukyError
  5. someErr.SetUnauthorized()
  6. // Fetch token from the headers
  7. requiredToken := c.GetHeader(constants.AuthorizationHeader)
  8. if len(requiredToken) == 0 {
  9. c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
  10. return
  11. }
  12. splittedToken := strings.SplitN(requiredToken, " ", 2)
  13. if len(splittedToken) != 2 || strings.ToLower(splittedToken[0]) != "bearer" {
  14. primErr := fmt.Errorf("wrong bearer token format on Authorization Header")
  15. someErr.PrimitiveErr = &primErr
  16. c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
  17. return
  18. }
  19. // Get email from encoded token
  20. jwtToken, claims, err := helpers.DecodeJWT(splittedToken[1], false)
  21. if err != nil {
  22. someErr.PrimitiveErr = &err
  23. c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
  24. return
  25. }
  26. if _, err := helpers.VerifyObjectIDs(claims.Subject); !err.IsNilError() {
  27. c.AbortWithStatusJSON(someErr.HttpErrorCode, someErr.JSON())
  28. return
  29. }
  30. // Set the User variable so that we can easily retrieve from other middlewares
  31. // c.Set("User", result)
  32. c.Set(constants.ReqBukyJWTKey, jwtToken)
  33. c.Set(constants.ReqBukyClaimsKey, claims)
  34. // Call the next middlware
  35. c.Next()
  36. }
  37. }

And then I just tested like following

  1. func TestAuthenticate(t *testing.T) {
  2. userID := primitive.NewObjectID().Hex()
  3. email := "email@email.com"
  4. firstName := "My Name"
  5. lastName := "My Lastname"
  6. scopes := []string{"im_scope"}
  7. statusOK := "statusOK"
  8. someProtectedPath := constants.UsersPath + "/" + userID
  9. engine := gin.Default()
  10. engine.GET(someProtectedPath, Authenticate(), func(c *gin.Context) {
  11. c.String(http.StatusOK, statusOK)
  12. })
  13. t.Run("NoTokenHeader", func(t *testing.T) {
  14. t.Run("UnsetHeader", func(t *testing.T) {
  15. w := httptest.NewRecorder()
  16. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  17. engine.ServeHTTP(w, req)
  18. assert.Equal(t, http.StatusUnauthorized, w.Code)
  19. })
  20. t.Run("EmptyHeader", func(t *testing.T) {
  21. w := httptest.NewRecorder()
  22. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  23. req.Header.Set(constants.AuthorizationHeader, "")
  24. engine.ServeHTTP(w, req)
  25. assert.Equal(t, http.StatusUnauthorized, w.Code)
  26. })
  27. })
  28. t.Run("TokenWithBadFormat", func(t *testing.T) {
  29. t.Run("1", func(t *testing.T) {
  30. w := httptest.NewRecorder()
  31. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  32. badFormatedToken := "hola.hola"
  33. req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", badFormatedToken))
  34. engine.ServeHTTP(w, req)
  35. assert.Equal(t, http.StatusUnauthorized, w.Code)
  36. })
  37. t.Run("2", func(t *testing.T) {
  38. w := httptest.NewRecorder()
  39. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  40. badFormatedToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
  41. req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", badFormatedToken))
  42. engine.ServeHTTP(w, req)
  43. assert.Equal(t, http.StatusUnauthorized, w.Code)
  44. })
  45. t.Run("3", func(t *testing.T) {
  46. w := httptest.NewRecorder()
  47. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  48. badFormatedToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.hola.hola.hola"
  49. req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearere %s", badFormatedToken))
  50. engine.ServeHTTP(w, req)
  51. assert.Equal(t, http.StatusUnauthorized, w.Code)
  52. })
  53. })
  54. t.Run("ExpiredToken", func(t *testing.T) {
  55. w := httptest.NewRecorder()
  56. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  57. expirationTime := time.Second
  58. expiredToken, _, err := helpers.GenerateAccessJWT(userID, email, firstName, lastName, scopes, expirationTime)
  59. time.Sleep(expirationTime * 2)
  60. req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", expiredToken))
  61. engine.ServeHTTP(w, req)
  62. assert.Equal(t, http.StatusUnauthorized, w.Code)
  63. assert.Nil(t, err)
  64. })
  65. t.Run("ValidToken", func(t *testing.T) {
  66. w := httptest.NewRecorder()
  67. req, _ := http.NewRequest("GET", someProtectedPath, nil)
  68. validToken, _, err := helpers.GenerateAccessJWT(userID, email, firstName, lastName, scopes)
  69. req.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("Bearer %s", validToken))
  70. engine.ServeHTTP(w, req)
  71. assert.Nil(t, err)
  72. assert.Equal(t, http.StatusOK, w.Code)
  73. })
  74. }

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:

确定