25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

171 lines
4.9 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. "context"
  11. "encoding/json"
  12. "fmt"
  13. "io"
  14. "io/ioutil"
  15. "net/http"
  16. "net/url"
  17. "strings"
  18. "time"
  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. // Audience optionally specifies the intended audience of the
  54. // request. If empty, the value of TokenURL is used as the
  55. // intended audience.
  56. Audience string
  57. }
  58. // TokenSource returns a JWT TokenSource using the configuration
  59. // in c and the HTTP client from the provided context.
  60. func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
  61. return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c})
  62. }
  63. // Client returns an HTTP client wrapping the context's
  64. // HTTP transport and adding Authorization headers with tokens
  65. // obtained from c.
  66. //
  67. // The returned client and its Transport should not be modified.
  68. func (c *Config) Client(ctx context.Context) *http.Client {
  69. return oauth2.NewClient(ctx, c.TokenSource(ctx))
  70. }
  71. // jwtSource is a source that always does a signed JWT request for a token.
  72. // It should typically be wrapped with a reuseTokenSource.
  73. type jwtSource struct {
  74. ctx context.Context
  75. conf *Config
  76. }
  77. func (js jwtSource) Token() (*oauth2.Token, error) {
  78. pk, err := internal.ParseKey(js.conf.PrivateKey)
  79. if err != nil {
  80. return nil, err
  81. }
  82. hc := oauth2.NewClient(js.ctx, nil)
  83. claimSet := &jws.ClaimSet{
  84. Iss: js.conf.Email,
  85. Scope: strings.Join(js.conf.Scopes, " "),
  86. Aud: js.conf.TokenURL,
  87. }
  88. if subject := js.conf.Subject; subject != "" {
  89. claimSet.Sub = subject
  90. // prn is the old name of sub. Keep setting it
  91. // to be compatible with legacy OAuth 2.0 providers.
  92. claimSet.Prn = subject
  93. }
  94. if t := js.conf.Expires; t > 0 {
  95. claimSet.Exp = time.Now().Add(t).Unix()
  96. }
  97. if aud := js.conf.Audience; aud != "" {
  98. claimSet.Aud = aud
  99. }
  100. h := *defaultHeader
  101. h.KeyID = js.conf.PrivateKeyID
  102. payload, err := jws.Encode(&h, claimSet, pk)
  103. if err != nil {
  104. return nil, err
  105. }
  106. v := url.Values{}
  107. v.Set("grant_type", defaultGrantType)
  108. v.Set("assertion", payload)
  109. resp, err := hc.PostForm(js.conf.TokenURL, v)
  110. if err != nil {
  111. return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
  112. }
  113. defer resp.Body.Close()
  114. body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
  115. if err != nil {
  116. return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
  117. }
  118. if c := resp.StatusCode; c < 200 || c > 299 {
  119. return nil, &oauth2.RetrieveError{
  120. Response: resp,
  121. Body: body,
  122. }
  123. }
  124. // tokenRes is the JSON response body.
  125. var tokenRes struct {
  126. AccessToken string `json:"access_token"`
  127. TokenType string `json:"token_type"`
  128. IDToken string `json:"id_token"`
  129. ExpiresIn int64 `json:"expires_in"` // relative seconds from now
  130. }
  131. if err := json.Unmarshal(body, &tokenRes); err != nil {
  132. return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
  133. }
  134. token := &oauth2.Token{
  135. AccessToken: tokenRes.AccessToken,
  136. TokenType: tokenRes.TokenType,
  137. }
  138. raw := make(map[string]interface{})
  139. json.Unmarshal(body, &raw) // no error checks for optional fields
  140. token = token.WithExtra(raw)
  141. if secs := tokenRes.ExpiresIn; secs > 0 {
  142. token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
  143. }
  144. if v := tokenRes.IDToken; v != "" {
  145. // decode returned id token to get expiry
  146. claimSet, err := jws.Decode(v)
  147. if err != nil {
  148. return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err)
  149. }
  150. token.Expiry = time.Unix(claimSet.Exp, 0)
  151. }
  152. return token, nil
  153. }