github.com/zooyer/miskit@v1.0.71/oauth2/oauth2.go (about)

     1  package oauth2
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"net/url"
     8  	"strings"
     9  
    10  	"github.com/google/uuid"
    11  	"github.com/zooyer/miskit/zrpc"
    12  )
    13  
    14  // Endpoint URL地址
    15  type Endpoint struct {
    16  	AuthorizeURL    string
    17  	AccessTokenURL  string
    18  	RefreshTokenURL string
    19  }
    20  
    21  // Formatter 请求参数序列化方式(JSON:Post、Form:Post、Query:Get)
    22  type Formatter int
    23  
    24  // Parameter 参数配置
    25  type Parameter interface {
    26  	// AuthCodeParams 生成请求授权码URL参数,直接拼接在地址后面
    27  	AuthCodeParams(ctx context.Context, config Config, state string) string
    28  	// AuthCodeTokenParams 生成授权码请求token参数
    29  	AuthCodeTokenParams(ctx context.Context, config Config, code string) map[string]string
    30  	// PasswordTokenParams 生成密码请求token参数
    31  	PasswordTokenParams(ctx context.Context, config Config, username, password string) map[string]string
    32  	// RefreshTokenParams 生成刷新token参数
    33  	RefreshTokenParams(ctx context.Context, config Config, refreshToken string) map[string]string
    34  	// ClientCredentialsParams 生成客户端凭证参数
    35  	ClientCredentialsParams(ctx context.Context, config Config) map[string]string
    36  }
    37  
    38  // Config OAuth2服务相关配置
    39  type Config struct {
    40  	ClientID     string    // 应用ID
    41  	ClientSecret string    // 应用证书
    42  	RedirectURI  string    // 回调地址
    43  	Scope        string    // 授权权限
    44  	Endpoint     Endpoint  // URL地址
    45  	Parameter    Parameter // 参数生成
    46  	Formatter    Formatter // 序列化方式
    47  }
    48  
    49  // Token OAuth2 Token
    50  type Token struct {
    51  	AccessToken  string      // 访问Token
    52  	TokenType    string      // Token类型
    53  	RefreshToken string      // 刷新Token
    54  	ExpiresIn    int64       // 过期时间(单位秒)
    55  	Scope        string      // 授权权限
    56  	Raw          interface{} // 自定义
    57  }
    58  
    59  // TokenParser Token解析
    60  type TokenParser interface {
    61  	Parse(data []byte) (token *Token, err error)
    62  }
    63  
    64  // Client OAuth2客户端
    65  type Client struct {
    66  	client *zrpc.Client
    67  	config Config
    68  }
    69  
    70  // 序列化方式
    71  const (
    72  	JSONFormatter  Formatter = 1 // JSON + POST方式
    73  	FORMFormatter  Formatter = 2 // FORM + POST方式
    74  	QueryFormatter Formatter = 3 // Query + GET方式
    75  )
    76  
    77  // defaultParameter 默认标准参数生成
    78  type defaultParameter struct{}
    79  
    80  func (defaultParameter) AuthCodeParams(ctx context.Context, config Config, state string) string {
    81  	var values = url.Values{
    82  		"response_type": {"code"},
    83  		"client_id":     {config.ClientID},
    84  	}
    85  
    86  	if config.RedirectURI != "" {
    87  		values.Set("redirect_uri", config.RedirectURI)
    88  	}
    89  
    90  	if config.Scope != "" {
    91  		values.Set("scope", config.Scope)
    92  	}
    93  
    94  	if state != "" {
    95  		values.Set("state", state)
    96  	}
    97  
    98  	return values.Encode()
    99  }
   100  
   101  func (defaultParameter) AuthCodeTokenParams(ctx context.Context, config Config, code string) map[string]string {
   102  	var values = map[string]string{
   103  		"grant_type":    "authorization_code",
   104  		"code":          code,
   105  		"client_id":     config.ClientID,
   106  		"client_secret": config.ClientSecret,
   107  	}
   108  
   109  	if config.RedirectURI != "" {
   110  		values["redirect_uri"] = config.RedirectURI
   111  	}
   112  
   113  	return values
   114  }
   115  
   116  func (defaultParameter) PasswordTokenParams(ctx context.Context, config Config, username, password string) map[string]string {
   117  	var values = map[string]string{
   118  		"grant_type":    "password",
   119  		"username":      username,
   120  		"password":      password,
   121  		"client_id":     config.ClientID,
   122  		"client_secret": config.ClientSecret,
   123  	}
   124  
   125  	if config.Scope != "" {
   126  		values["scope"] = config.Scope
   127  	}
   128  
   129  	return values
   130  }
   131  
   132  func (defaultParameter) RefreshTokenParams(ctx context.Context, config Config, refreshToken string) map[string]string {
   133  	var values = map[string]string{
   134  		"grant_type":    "refresh_token",
   135  		"refresh_token": refreshToken,
   136  		"client_id":     config.ClientID,
   137  		"client_secret": config.ClientSecret,
   138  	}
   139  
   140  	return values
   141  }
   142  
   143  func (defaultParameter) ClientCredentialsParams(ctx context.Context, config Config) map[string]string {
   144  	var values = map[string]string{
   145  		"grant_type": "client_credentials",
   146  	}
   147  
   148  	if config.Scope != "" {
   149  		values["scope"] = config.Scope
   150  	}
   151  
   152  	return values
   153  }
   154  
   155  // genState 生成随机state
   156  func (c Config) genState(ctx context.Context) string {
   157  	id := uuid.NewMD5(uuid.New(), []byte(c.ClientID)).String()
   158  	return strings.ReplaceAll(id, "-", "")
   159  }
   160  
   161  // AuthCodeURL 获取授权码URL地址
   162  func (c Config) AuthCodeURL(ctx context.Context) (state string, url string) {
   163  	var buf bytes.Buffer
   164  	buf.WriteString(c.Endpoint.AuthorizeURL)
   165  
   166  	if strings.Contains(c.Endpoint.AuthorizeURL, "?") {
   167  		buf.WriteByte('&')
   168  	} else {
   169  		buf.WriteByte('?')
   170  	}
   171  
   172  	state = c.genState(ctx)
   173  	params := c.Parameter.AuthCodeParams(ctx, c, state)
   174  	buf.WriteString(params)
   175  
   176  	return state, buf.String()
   177  }
   178  
   179  // NewClient 场景OAuth2客户端
   180  func NewClient(rpc *zrpc.Client, config Config) *Client {
   181  	if config.Parameter == nil {
   182  		config.Parameter = defaultParameter{}
   183  	}
   184  
   185  	if config.Formatter == 0 {
   186  		config.Formatter = JSONFormatter
   187  	}
   188  
   189  	var client = Client{
   190  		client: rpc,
   191  		config: config,
   192  	}
   193  
   194  	return &client
   195  }
   196  
   197  // doRequest 请求OAuth2服务
   198  func (c *Client) doRequest(ctx context.Context, url string, params map[string]string, parser TokenParser) (*Token, error) {
   199  	var (
   200  		err   error
   201  		data  []byte
   202  		token *Token
   203  		form  = make(map[string][]string)
   204  	)
   205  
   206  	for key, val := range params {
   207  		form[key] = []string{val}
   208  	}
   209  
   210  	switch c.config.Formatter {
   211  	case JSONFormatter:
   212  		data, _, err = c.client.PostJSON(ctx, url, params, nil)
   213  	case FORMFormatter:
   214  		data, _, err = c.client.PostForm(ctx, url, form, nil)
   215  	case QueryFormatter:
   216  		data, _, err = c.client.Get(ctx, url, form, nil)
   217  	default:
   218  		return nil, errors.New("invalid formatter")
   219  	}
   220  
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	if token, err = parser.Parse(data); err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	return token, nil
   230  }
   231  
   232  // AuthCodeURL 获取授权码URL地址
   233  func (c *Client) AuthCodeURL(ctx context.Context) (state string, url string) {
   234  	return c.config.AuthCodeURL(ctx)
   235  }
   236  
   237  // AuthorizationCodeToken 授权码方式获取Token
   238  func (c *Client) AuthorizationCodeToken(ctx context.Context, code string, parser TokenParser) (*Token, error) {
   239  	var params = c.config.Parameter.AuthCodeTokenParams(ctx, c.config, code)
   240  	return c.doRequest(ctx, c.config.Endpoint.AccessTokenURL, params, parser)
   241  }
   242  
   243  // PasswordCredentialsToken 密码方式获取Token
   244  func (c *Client) PasswordCredentialsToken(ctx context.Context, username, password string, parser TokenParser) (*Token, error) {
   245  	var params = c.config.Parameter.PasswordTokenParams(ctx, c.config, username, password)
   246  	return c.doRequest(ctx, c.config.Endpoint.AccessTokenURL, params, parser)
   247  }
   248  
   249  // ClientCredentialsToken 客户端凭证
   250  func (c *Client) ClientCredentialsToken(ctx context.Context, parser TokenParser) (*Token, error) {
   251  	var params = c.config.Parameter.ClientCredentialsParams(ctx, c.config)
   252  	return c.doRequest(ctx, c.config.Endpoint.AccessTokenURL, params, parser)
   253  }
   254  
   255  // RefreshToken 刷新Token
   256  func (c *Client) RefreshToken(ctx context.Context, refreshToken string, parser TokenParser) (*Token, error) {
   257  	var params = c.config.Parameter.RefreshTokenParams(ctx, c.config, refreshToken)
   258  	return c.doRequest(ctx, c.config.Endpoint.RefreshTokenURL, params, parser)
   259  }