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 }