您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

163 行
4.7 KiB

  1. // Copyright 2014 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly
  5. // known as "two-legged OAuth 2.0".
  6. //
  7. // See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12
  8. package jwt
  9. import (
  10. "encoding/json"
  11. "fmt"
  12. "io"
  13. "io/ioutil"
  14. "net/http"
  15. "net/url"
  16. "strings"
  17. "time"
  18. "golang.org/x/net/context"
  19. "golang.org/x/oauth2"
  20. "golang.org/x/oauth2/internal"
  21. "golang.org/x/oauth2/jws"
  22. )
  23. var (
  24. defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
  25. defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
  26. )
  27. // Config is the configuration for using JWT to fetch tokens,
  28. // commonly known as "two-legged OAuth 2.0".
  29. type Config struct {
  30. // Email is the OAuth client identifier used when communicating with
  31. // the configured OAuth provider.
  32. Email string
  33. // PrivateKey contains the contents of an RSA private key or the
  34. // contents of a PEM file that contains a private key. The provided
  35. // private key is used to sign JWT payloads.
  36. // PEM containers with a passphrase are not supported.
  37. // Use the following command to convert a PKCS 12 file into a PEM.
  38. //
  39. // $ openssl pkcs12 -in key.p12 -out key.pem -nodes
  40. //
  41. PrivateKey []byte
  42. // PrivateKeyID contains an optional hint indicating which key is being
  43. // used.
  44. PrivateKeyID string
  45. // Subject is the optional user to impersonate.
  46. Subject string
  47. // Scopes optionally specifies a list of requested permission scopes.
  48. Scopes []string
  49. // TokenURL is the endpoint required to complete the 2-legged JWT flow.
  50. TokenURL string
  51. // Expires optionally specifies how long the token is valid for.
  52. Expires time.Duration
  53. }
  54. // TokenSource returns a JWT TokenSource using the configuration
  55. // in c and the HTTP client from the provided context.
  56. func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
  57. return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c})
  58. }
  59. // Client returns an HTTP client wrapping the context's
  60. // HTTP transport and adding Authorization headers with tokens
  61. // obtained from c.
  62. //
  63. // The returned client and its Transport should not be modified.
  64. func (c *Config) Client(ctx context.Context) *http.Client {
  65. return oauth2.NewClient(ctx, c.TokenSource(ctx))
  66. }
  67. // jwtSource is a source that always does a signed JWT request for a token.
  68. // It should typically be wrapped with a reuseTokenSource.
  69. type jwtSource struct {
  70. ctx context.Context
  71. conf *Config
  72. }
  73. func (js jwtSource) Token() (*oauth2.Token, error) {
  74. pk, err := internal.ParseKey(js.conf.PrivateKey)
  75. if err != nil {
  76. return nil, err
  77. }
  78. hc := oauth2.NewClient(js.ctx, nil)
  79. claimSet := &jws.ClaimSet{
  80. Iss: js.conf.Email,
  81. Scope: strings.Join(js.conf.Scopes, " "),
  82. Aud: js.conf.TokenURL,
  83. }
  84. if subject := js.conf.Subject; subject != "" {
  85. claimSet.Sub = subject
  86. // prn is the old name of sub. Keep setting it
  87. // to be compatible with legacy OAuth 2.0 providers.
  88. claimSet.Prn = subject
  89. }
  90. if t := js.conf.Expires; t > 0 {
  91. claimSet.Exp = time.Now().Add(t).Unix()
  92. }
  93. h := *defaultHeader
  94. h.KeyID = js.conf.PrivateKeyID
  95. payload, err := jws.Encode(&h, claimSet, pk)
  96. if err != nil {
  97. return nil, err
  98. }
  99. v := url.Values{}
  100. v.Set("grant_type", defaultGrantType)
  101. v.Set("assertion", payload)
  102. resp, err := hc.PostForm(js.conf.TokenURL, v)
  103. if err != nil {
  104. return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
  105. }
  106. defer resp.Body.Close()
  107. body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
  108. if err != nil {
  109. return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
  110. }
  111. if c := resp.StatusCode; c < 200 || c > 299 {
  112. return nil, &oauth2.RetrieveError{
  113. Response: resp,
  114. Body: body,
  115. }
  116. }
  117. // tokenRes is the JSON response body.
  118. var tokenRes struct {
  119. AccessToken string `json:"access_token"`
  120. TokenType string `json:"token_type"`
  121. IDToken string `json:"id_token"`
  122. ExpiresIn int64 `json:"expires_in"` // relative seconds from now
  123. }
  124. if err := json.Unmarshal(body, &tokenRes); err != nil {
  125. return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
  126. }
  127. token := &oauth2.Token{
  128. AccessToken: tokenRes.AccessToken,
  129. TokenType: tokenRes.TokenType,
  130. }
  131. raw := make(map[string]interface{})
  132. json.Unmarshal(body, &raw) // no error checks for optional fields
  133. token = token.WithExtra(raw)
  134. if secs := tokenRes.ExpiresIn; secs > 0 {
  135. token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
  136. }
  137. if v := tokenRes.IDToken; v != "" {
  138. // decode returned id token to get expiry
  139. claimSet, err := jws.Decode(v)
  140. if err != nil {
  141. return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err)
  142. }
  143. token.Expiry = time.Unix(claimSet.Exp, 0)
  144. }
  145. return token, nil
  146. }