├── .gitignore ├── go.mod ├── generator ├── generator.go └── generator_default │ └── generator_default.go ├── oauth2.go ├── README_CH.md ├── store ├── store.go └── store_mem │ └── store_mem.go ├── server ├── handler.go ├── utils.go └── server.go ├── const.go ├── config.go ├── README.md ├── example ├── server │ └── server.go └── client │ └── client.go ├── error.go ├── manager └── manager.go ├── go.sum └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sagacioushugo/oauth2 2 | 3 | require ( 4 | github.com/astaxie/beego v1.11.1 5 | github.com/satori/go.uuid v1.2.0 6 | ) 7 | -------------------------------------------------------------------------------- /generator/generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "github.com/astaxie/beego/context" 5 | "github.com/sagacioushugo/oauth2" 6 | ) 7 | 8 | type Generator interface { 9 | Code(req *oauth2.Request, ctx *context.Context) (code string, err error) 10 | Token(req *oauth2.Request, ctx *context.Context, isGenerateRefresh bool) (access, refresh string, err error) 11 | } 12 | -------------------------------------------------------------------------------- /oauth2.go: -------------------------------------------------------------------------------- 1 | package oauth2 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type Request struct { 8 | // authorize 9 | ResponseType ResponseType 10 | State string 11 | 12 | // token 13 | ClientSecret string 14 | Code string 15 | Refresh string 16 | 17 | //common 18 | ClientId string 19 | ClientInfo map[string]string 20 | GrantType GrantType 21 | UserId string 22 | RedirectUri string 23 | Scope string 24 | } 25 | 26 | // Response error response 27 | type Response struct { 28 | Error error 29 | ErrorCode int 30 | Description string 31 | Uri string 32 | StatusCode int 33 | Header http.Header 34 | } 35 | -------------------------------------------------------------------------------- /README_CH.md: -------------------------------------------------------------------------------- 1 | # Golang Oauth2 Server 2 | 基于`github.com/go-oauth2`的二次开发,提供了TokenStore和TokenGenerator基于接口的可拔插式设计,能够方便的定制自己需要的TokenStore和Generator,并且支持TokenGC功能。 3 | 4 | 5 | ## 特点 6 | * 清晰的配置项,并且预先设置了默认配置(config.go) 7 | * 支持7个自定义钩子,可在授权流程扩展逻辑(server/handler.go) 8 | * 支持在钩子函数中response(例如输出授权页面、错误页面等,后续授权逻辑自动中断) 9 | * 支持Token GC功能 10 | * 支持用任意数据库自定义Token Store(你需要自己实现store.TokenStore接口) 11 | * 支持用任意算法自定义Token Generator(你需要自己实现generator.Generator接口) 12 | * 完全基于[RFC 6749](https://tools.ietf.org/html/rfc6749)实现(支持RedirectURI为空,支持配置多RedirectURI) 13 | 14 | 15 | 16 | ## 快速开始 17 | ### 下载和安装 18 | ```bash 19 | go get github.com/sagacioushugo/oauth2 20 | ``` 21 | 22 | ### 编译运行示例代码 23 | 24 | ```bash 25 | go build server.go 26 | ./server 27 | 28 | go build client.go 29 | ./client 30 | ``` 31 | 32 | ### 在浏览器向client请求 33 | 34 | client收到相应请求后会自动模拟某种授权方式,并把获取的token返回至浏览器 35 | 36 | 可参考[RFC 6749](https://tools.ietf.org/html/rfc6749)文档,并结合client日志了解具体的授权流程 37 | 38 | 授权方式| client url | 备注 39 | ---|---|--- 40 | Authorization Code|http://localhost:8080/test/authorization_code | 41 | Implicit|http://localhost:8080/test/implicit | 42 | Password Credentials|http://localhost:8080/test/password| 43 | Client Credentials | http://localhost:8080/test/client_credentials| 44 | Refreshing an access token|http://localhost:8080/test/refresh_token?refresh_token=yourtoken| 需要参数refresh_token 45 | 46 | 47 | ## 文档 48 | * [English](./README.md) 49 | * [中文文档](./README_CH.md) 50 | 51 | ## Apache License 2.0 52 | 53 | Copyright (c) 2019 Guoyiming -------------------------------------------------------------------------------- /store/store.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "github.com/astaxie/beego/context" 5 | "time" 6 | ) 7 | 8 | type Token interface { 9 | 10 | //base info 11 | GetClientId() string 12 | SetClientId(clientId string) 13 | GetUserId() string 14 | SetUserId(userId string) 15 | GetScope() string 16 | SetScope(scope string) 17 | 18 | //code info 19 | GetCode() string 20 | SetCode(code string) 21 | GetCodeCreateAt() time.Time 22 | SetCodeCreateAt(codeCreateAt time.Time) 23 | GetCodeExpireIn() int64 24 | SetCodeExpireIn(codeExpireIn int64) 25 | 26 | //access info 27 | GetAccess() string 28 | SetAccess(access string) 29 | GetAccessCreateAt() time.Time 30 | SetAccessCreateAt(accessCreateAt time.Time) 31 | GetAccessExpireIn() int64 32 | SetAccessExpireIn(accessExpireIn int64) 33 | 34 | // refresh info 35 | GetRefresh() string 36 | SetRefresh(refresh string) 37 | GetRefreshCreateAt() time.Time 38 | SetRefreshCreateAt(refreshCreateAt time.Time) 39 | GetRefreshExpireIn() int64 40 | SetRefreshExpireIn(refreshExpireIn int64) 41 | 42 | IsAccessExpired() bool 43 | IsCodeExpired() bool 44 | IsRefreshExpired() bool 45 | } 46 | 47 | type TokenStore interface { 48 | Init(tokenConfig string) error 49 | NewToken(ctx *context.Context) Token 50 | Create(token Token) error 51 | GetByAccess(access string) (Token, error) 52 | GetByRefresh(fresh string) (Token, error) 53 | GetByCode(code string) (Token, error) 54 | CreateAndDel(tokenNew Token, tokenDel Token) error 55 | GC(gcInterval int64) 56 | } 57 | -------------------------------------------------------------------------------- /generator/generator_default/generator_default.go: -------------------------------------------------------------------------------- 1 | package generator_default 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "github.com/astaxie/beego/context" 7 | "github.com/sagacioushugo/oauth2" 8 | "github.com/sagacioushugo/oauth2/manager" 9 | "github.com/satori/go.uuid" 10 | "strconv" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | type Default struct { 16 | } 17 | 18 | func init() { 19 | manager.Register("default", &Default{}) 20 | } 21 | 22 | func (g *Default) Code(req *oauth2.Request, ctx *context.Context) (code string, err error) { 23 | buf := bytes.NewBufferString(req.ClientId) 24 | buf.WriteString(req.UserId) 25 | buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10)) 26 | token := uuid.NewV3(uuid.Must(uuid.NewV1(), nil), buf.String()) 27 | code = base64.URLEncoding.EncodeToString(token.Bytes()) 28 | code = strings.TrimRight(code, "=") 29 | 30 | return 31 | } 32 | 33 | func (g *Default) Token(req *oauth2.Request, ctx *context.Context, isGenRefresh bool) (access, refresh string, err error) { 34 | buf := bytes.NewBufferString(req.ClientId) 35 | buf.WriteString(req.UserId) 36 | buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10)) 37 | 38 | access = base64.URLEncoding.EncodeToString(uuid.NewV3(uuid.Must(uuid.NewV4(), nil), buf.String()).Bytes()) 39 | access = strings.TrimRight(access, "=") 40 | if isGenRefresh { 41 | refresh = base64.URLEncoding.EncodeToString(uuid.NewV5(uuid.Must(uuid.NewV4(), nil), buf.String()).Bytes()) 42 | refresh = strings.TrimRight(refresh, "=") 43 | } 44 | 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /server/handler.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/astaxie/beego/context" 5 | "github.com/sagacioushugo/oauth2" 6 | "github.com/sagacioushugo/oauth2/store" 7 | ) 8 | 9 | type ( 10 | // check user if has or not grant access ( check user is login and user has granted access to the client) 11 | CheckUserGrantAccessHandler func(req *oauth2.Request, ctx *context.Context) (userId string, err error) 12 | 13 | // check username and password is match 14 | CheckUserPasswordHandler func(username, password string, ctx *context.Context) (userId string, err error) 15 | 16 | // authenticate client (client is valid or satisfy some condition) or default when authorize request check client existed and token request check client_id match client_secret 17 | AuthenticateClientHandler func(ctx *context.Context, clientIdAndSecret ...string) (redirectUris string, err error) 18 | 19 | // check scope or default not check scope 20 | CustomizedCheckScopeHandler func(scope string, grantType *oauth2.GrantType, ctx *context.Context) (allowed bool, err error) 21 | 22 | // check refresh scope or default check newScope equals oldScope 23 | CustomizedRefreshingScopeHandler func(newScope, oldScope string, ctx *context.Context) (allowed bool, err error) 24 | 25 | CustomizedAuthorizeErrHandler func(err error, ctx *context.Context) error 26 | 27 | CustomizedClientCredentialsUserIdHandler func(clientId string) (userId string) 28 | 29 | CustomizedTokenExtensionFieldsHandler func(token store.Token, req *oauth2.Request, ctx *context.Context) (fieldsValue map[string]interface{}) 30 | ) 31 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | package oauth2 2 | 3 | /* 4 | 5 | Oauth2 Pattern responseType(GET /authorize) grantType(POST /token) 6 | Authorization Code Grant code authorization_code 7 | Implicit Grant token x 8 | Password Credentials Grant x password 9 | Client Credentials Grant x client_credentials 10 | Refreshing an access token x refresh_token 11 | 12 | Refer to rfc6749 13 | https://tools.ietf.org/html/rfc6749#section-4.1 14 | https://tools.ietf.org/html/rfc6749#section-4.2 15 | https://tools.ietf.org/html/rfc6749#section-4.3 16 | https://tools.ietf.org/html/rfc6749#section-4.4 17 | https://tools.ietf.org/html/rfc6749#section-6 18 | 19 | */ 20 | 21 | type ResponseType string 22 | 23 | func (rt ResponseType) IsValid() bool { 24 | switch rt { 25 | case Token: 26 | return true 27 | case Code: 28 | return true 29 | default: 30 | return false 31 | } 32 | } 33 | 34 | const ( 35 | Token ResponseType = "token" 36 | Code ResponseType = "code" 37 | ) 38 | 39 | type GrantType string 40 | 41 | const ( 42 | AuthorizationCode GrantType = "authorization_code" 43 | Implicit GrantType = "__implicit" 44 | PasswordCredentials GrantType = "password" 45 | ClientCredentials GrantType = "client_credentials" 46 | RefreshToken GrantType = "refresh_token" 47 | ) 48 | 49 | func (gt GrantType) IsValid() bool { 50 | switch gt { 51 | case AuthorizationCode: 52 | return true 53 | case PasswordCredentials: 54 | return true 55 | case ClientCredentials: 56 | return true 57 | case RefreshToken: 58 | return true 59 | default: 60 | return false 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package oauth2 2 | 3 | type Config struct { 4 | TokenType string 5 | RedirectUriSep string 6 | RedirectAllowEmpty bool 7 | AllowGrantType map[GrantType]GrantTypeConfig 8 | ManagerConfig ManagerConfig 9 | } 10 | 11 | type ManagerConfig struct { 12 | TokenGcInterval int64 13 | TokenStoreName string 14 | TokenStoreConfig string 15 | GeneratorName string 16 | } 17 | 18 | type GrantTypeConfig struct { 19 | CodeExpire int64 20 | AccessTokenExpire int64 21 | RefreshTokenExpire int64 22 | IsGenerateRefresh bool 23 | IsResetRefreshTime bool 24 | } 25 | 26 | var DefaultOauth2Config = Config{ 27 | TokenType: "Bearer", 28 | RedirectUriSep: "|", 29 | RedirectAllowEmpty: false, 30 | AllowGrantType: map[GrantType]GrantTypeConfig{ 31 | AuthorizationCode: { 32 | AccessTokenExpire: 12 * 3600, 33 | RefreshTokenExpire: 72 * 3600, 34 | CodeExpire: 300, 35 | IsGenerateRefresh: true, 36 | IsResetRefreshTime: false, 37 | }, 38 | Implicit: { 39 | AccessTokenExpire: 6 * 3600, 40 | IsGenerateRefresh: false, 41 | IsResetRefreshTime: false, 42 | }, 43 | PasswordCredentials: { 44 | AccessTokenExpire: 6 * 3600, 45 | IsGenerateRefresh: false, 46 | IsResetRefreshTime: false, 47 | }, 48 | ClientCredentials: { 49 | AccessTokenExpire: 6 * 3600, 50 | IsGenerateRefresh: false, 51 | IsResetRefreshTime: false, 52 | }, 53 | RefreshToken: { 54 | AccessTokenExpire: 12 * 3600, 55 | RefreshTokenExpire: 72 * 3600, 56 | IsGenerateRefresh: true, 57 | IsResetRefreshTime: false, 58 | }, 59 | }, 60 | ManagerConfig: ManagerConfig{ 61 | TokenGcInterval: 7 * 24 * 3600, 62 | TokenStoreName: "mem", 63 | GeneratorName: "default", 64 | TokenStoreConfig: "", 65 | }, 66 | } 67 | 68 | func NewDefaultConfig() *Config { 69 | return &DefaultOauth2Config 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang Oauth2 Server 2 | Based on the secondary development `github.com/go-oauth2`, provides TokenStore and TokenGenerator based on interface can be pull out plug type design, can easily customize their need TokenStore and the Generator, and support the TokenGC. 3 | 4 | 5 | ## Features 6 | * clear configuration items with default configuration(config.go) 7 | * 7 custom hooks are supported to extend logic in the authorization process (server/handler.go) 8 | * support response in hook functions (such as output authorization page, error page, etc., subsequent authorization logic is automatically interrupted) 9 | * support for Token GC 10 | * support custom Token Store with any database (you need to implement the store.tokenstore interface yourself) 11 | * support custom Token Generator with any algorithm (you need to implement the Generator.Generator interface yourself) 12 | * based entirely on [RFC 6749](https://tools.ietf.org/html/rfc6749) implementation (support RedirectURI is empty, support multiple RedirectURI configuration) 13 | 14 | ## Quick Start 15 | ### Download and install 16 | ```bash 17 | go get github.com/sagacioushugo/oauth2 18 | ``` 19 | 20 | ### Build and run example 21 | 22 | ```bash 23 | go build server.go 24 | ./server 25 | 26 | go build client.go 27 | ./client 28 | ``` 29 | 30 | 31 | ### Request to client in browser 32 | 33 | After receiving the corresponding request, the client will automatically simulate a certain authorization pattern and return the token obtained to the browser 34 | 35 | Test the authorization process with [RFC 6749](https://tools.ietf.org/html/rfc6749) and client logs 36 | 37 | 38 | Authorization Pattern| Client Url | note 39 | ---|---|--- 40 | Authorization Code|http://localhost:8080/test/authorization_code | 41 | Implicit|http://localhost:8080/test/implicit | 42 | Password Credentials|http://localhost:8080/test/password| 43 | Client Credentials | http://localhost:8080/test/client_credentials| 44 | Refreshing an access token|http://localhost:8080/test/refresh_token?refresh_token=yourtoken| param refresh_token is required 45 | 46 | 47 | ## Documentation 48 | * [English](./README.md) 49 | * [中文文档](./README_CH.md) 50 | 51 | ## Apache License 2.0 52 | 53 | Copyright (c) 2019 Guoyiming -------------------------------------------------------------------------------- /server/utils.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/astaxie/beego/context" 9 | "github.com/sagacioushugo/oauth2" 10 | "net/http" 11 | "net/url" 12 | "strings" 13 | ) 14 | 15 | func GetErrorData(err error) (data map[string]interface{}, statusCode int, header http.Header) { 16 | data = make(map[string]interface{}) 17 | if oe, ok := err.(*oauth2.Oauth2Error); ok { 18 | data["error"] = oe.Error() 19 | data["error_description"] = oe.ErrorDescription() 20 | statusCode = oe.StatusCode() 21 | } else if sc, ok := err.(oauth2.StatusCoder); ok { 22 | data["error"] = err.Error() 23 | statusCode = sc.StatusCode() 24 | } else { 25 | data["error"] = err.Error() 26 | data["error_description"] = oauth2.ErrServerError.ErrorDescription() 27 | statusCode = oauth2.ErrServerError.StatusCode() 28 | } 29 | return 30 | } 31 | 32 | func GetRedirectUri(req *oauth2.Request, data map[string]interface{}) (uri string, err error) { 33 | if req.RedirectUri == "" { 34 | err = oauth2.ErrInvalidRequest 35 | return 36 | } 37 | u, err := url.Parse(req.RedirectUri) 38 | 39 | if err != nil { 40 | return 41 | } 42 | q := u.Query() 43 | if req.State != "" { 44 | q.Set("state", req.State) 45 | } 46 | 47 | for k, v := range data { 48 | q.Set(k, fmt.Sprint(v)) 49 | } 50 | switch req.ResponseType { 51 | case oauth2.Code: 52 | u.RawQuery = q.Encode() 53 | case oauth2.Token: 54 | u.RawQuery = "" 55 | u.Fragment, err = url.QueryUnescape(q.Encode()) 56 | if err != nil { 57 | return 58 | } 59 | default: 60 | err = oauth2.ErrInvalidRequest 61 | return 62 | } 63 | uri = u.String() 64 | return 65 | } 66 | 67 | func Redirect(req *oauth2.Request, data map[string]interface{}, ctx *context.Context) (err error) { 68 | uri, err := GetRedirectUri(req, data) 69 | if err != nil { 70 | return 71 | } 72 | ctx.Redirect(http.StatusFound, uri) 73 | return 74 | } 75 | 76 | func RedirectError(req *oauth2.Request, err error, ctx *context.Context) error { 77 | data, _, _ := GetErrorData(err) 78 | return Redirect(req, data, ctx) 79 | } 80 | 81 | func ResponseErr(err error, ctx *context.Context) error { 82 | data, statusCode, header := GetErrorData(err) 83 | return ResponseToken(data, header, ctx, statusCode) 84 | } 85 | func ResponseToken(data map[string]interface{}, header http.Header, ctx *context.Context, statusCode ...int) (err error) { 86 | ctx.ResponseWriter.Header().Set("Content-Type", "application/json;charset=UTF-8") 87 | ctx.ResponseWriter.Header().Set("Cache-Control", "no-store") 88 | ctx.ResponseWriter.Header().Set("Pragma", "no-cache") 89 | 90 | for k := range header { 91 | ctx.ResponseWriter.Header().Set(k, header.Get(k)) 92 | } 93 | status := http.StatusOK 94 | if len(statusCode) > 0 && statusCode[0] > 0 { 95 | status = statusCode[0] 96 | } 97 | ctx.ResponseWriter.WriteHeader(status) 98 | 99 | err = json.NewEncoder(ctx.ResponseWriter).Encode(data) 100 | return 101 | } 102 | 103 | func ParseBasicAuth(basicAuth string) (username, password string, ok bool) { 104 | const prefix = "Basic " 105 | if !strings.HasPrefix(basicAuth, prefix) { 106 | return 107 | } 108 | c, err := base64.StdEncoding.DecodeString(basicAuth[len(prefix):]) 109 | if err != nil { 110 | return 111 | } 112 | s := bytes.IndexByte(c, ':') 113 | if s < 0 { 114 | return 115 | } 116 | return string(c[:s]), string(c[s+1:]), true 117 | } 118 | -------------------------------------------------------------------------------- /example/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/astaxie/beego/context" 5 | "github.com/sagacioushugo/oauth2" 6 | _ "github.com/sagacioushugo/oauth2/generator/generator_default" 7 | "github.com/sagacioushugo/oauth2/server" 8 | _ "github.com/sagacioushugo/oauth2/store/store_mem" 9 | "log" 10 | "net/http" 11 | ) 12 | 13 | func main() { 14 | // modify default config if you need 15 | config := oauth2.NewDefaultConfig() 16 | 17 | // example: config.ManagerConfig.TokenStoreName = "your token store implemented Mysql, MongoDB, Redis..etc" 18 | // how to implement your token store refer to store/store_mem/store_mem.go 19 | config.ManagerConfig.TokenStoreName = "mem" 20 | 21 | // example: config.ManagerConfig.GeneratorName = "your token generator implemented random, jwt, uuid..etc" 22 | // how to implement your token generator refer to generator/generator_default/generator_default.go 23 | config.ManagerConfig.GeneratorName = "default" 24 | 25 | oauth2Server := server.NewServer(config) 26 | 27 | // All Oauth2 Pattern must implement 28 | oauth2Server.SetAuthenticateClientHandler(authenticateClientHandler) 29 | 30 | // PasswordCredentials Pattern must implement 31 | oauth2Server.SetCheckUserPasswordHandler(checkUserPasswordHandler) 32 | 33 | // Authorization Code Pattern must implement 34 | oauth2Server.SetCheckUserGrantAccessHandler(checkUserGrantAccessHandler) 35 | 36 | // Other custom api detail refer to server/handler.go 37 | // CustomizedCheckScopeHandler 38 | // CustomizedAuthorizeErrHandler 39 | // CustomizedClientCredentialsUserIdHandler 40 | // CustomizedTokenExtensionFieldsHandler 41 | 42 | // start token GC 43 | go oauth2Server.Manager.TokenGC() 44 | 45 | http.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) { 46 | context := context.NewContext() 47 | context.Reset(w, r) 48 | oauth2Server.Authorize(context) 49 | }) 50 | 51 | http.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { 52 | context := context.NewContext() 53 | context.Reset(w, r) 54 | oauth2Server.Token(context) 55 | }) 56 | 57 | log.Println("Start Listen :8081") 58 | 59 | log.Fatal(http.ListenAndServe(":8081", nil)) 60 | 61 | } 62 | 63 | func checkUserPasswordHandler(username, password string, ctx *context.Context) (userId string, err error) { 64 | // should check username and password from database 65 | if username == "admin" && password == "123456" { 66 | userId = username 67 | return 68 | } else { 69 | return "", oauth2.ErrInvalidUsernameOrPassword 70 | } 71 | } 72 | 73 | func checkUserGrantAccessHandler(req *oauth2.Request, ctx *context.Context) (userId string, err error) { 74 | // should check user is valid in session 75 | // if user is valid return userId 76 | // else return "", oauth2.ErrAccessDenied 77 | return "admin", nil 78 | } 79 | 80 | func authenticateClientHandler(ctx *context.Context, clientIdAndSecret ...string) (redirectUris string, err error) { 81 | if len(clientIdAndSecret) == 0 || len(clientIdAndSecret) > 2 { 82 | return "", oauth2.ErrInvalidRequest 83 | } 84 | const demoRedirectUris = "http://localhost:8080/test/code_to_token|http://localhost:8080/test/implicit_token" 85 | 86 | if len(clientIdAndSecret) == 1 { 87 | if clientIdAndSecret[0] == "1" { 88 | return demoRedirectUris, nil 89 | } else { 90 | return "", oauth2.ErrInvalidClient 91 | } 92 | } 93 | 94 | if clientIdAndSecret[0] == "1" && clientIdAndSecret[1] == "1he5k5ZUrHFjznxN" { 95 | return demoRedirectUris, nil 96 | } else { 97 | return "", oauth2.ErrInvalidClient 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package oauth2 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type StatusCoder interface { 8 | StatusCode() int 9 | } 10 | 11 | type Oauth2Error struct { 12 | msg string 13 | description string 14 | statusCode int 15 | } 16 | 17 | func (e Oauth2Error) Error() string { 18 | return e.msg 19 | } 20 | 21 | func (e Oauth2Error) StatusCode() int { 22 | return e.statusCode 23 | } 24 | 25 | func (e Oauth2Error) ErrorDescription() string { 26 | return e.description 27 | } 28 | 29 | func NewError(statusCode int, msg string, description string) *Oauth2Error { 30 | return &Oauth2Error{statusCode: statusCode, msg: msg, description: description} 31 | } 32 | 33 | var ( 34 | // oauth2 err https://tools.ietf.org/html/rfc6749#section-5.2 35 | ErrInvalidRequest = NewError(http.StatusBadRequest, "invalid_request", "The request is missing a required parameter, includes an unsupported parameter value (other than grant type), repeats a parameter, includes multiple credentials, utilizes more than one mechanism for authenticating the client, or is otherwise malformed.") 36 | ErrUnauthorizedClient = NewError(http.StatusUnauthorized, "unauthorized_client", "The authenticated client is not authorized to use this authorization grant type.") 37 | ErrAccessDenied = NewError(http.StatusUnauthorized, "access_denied", "The resource owner or authorization server denied the request") 38 | ErrUnsupportedResponseType = NewError(http.StatusUnauthorized, "unsupported_response_type", "The authorization server does not support obtaining an authorization code or an access token using this method") 39 | ErrInvalidScope = NewError(http.StatusBadRequest, "invalid_scope", "The requested scope is invalid, unknown, or malformed") 40 | ErrServerError = NewError(http.StatusInternalServerError, "server_error", "The authorization server encountered an unexpected condition that prevented it from fulfilling the request") 41 | ErrTemporarilyUnavailable = NewError(http.StatusServiceUnavailable, "temporarily_unavailable", "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server") 42 | ErrInvalidClient = NewError(http.StatusBadRequest, "invalid_client", "Client authentication failed") 43 | ErrInvalidGrant = NewError(http.StatusUnauthorized, "invalid_grant", "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client") 44 | ErrUnsupportedGrantType = NewError(http.StatusUnauthorized, "unsupported_grant_type", "The authorization grant type is not supported by the authorization server") 45 | 46 | // extra customized errors 47 | ErrInvalidRedirectURI = NewError(http.StatusBadRequest, "invalid_redirect_uri", "The request is missing redirect uri or includes an invalid redirect uri value") 48 | ErrInvalidAuthorizeCode = NewError(http.StatusBadRequest, "invalid_authorize_code", "The request is missing authorize code or includes an invalid authorize code value") 49 | ErrInvalidAccessToken = NewError(http.StatusBadRequest, "invalid_access_token", "The request is missing access token or includes an invalid access token value") 50 | ErrInvalidRefreshToken = NewError(http.StatusBadRequest, "invalid_refresh_token", "The request is missing refresh token or includes an invalid refresh token value") 51 | ErrExpiredAuthorizeCode = NewError(http.StatusBadRequest, "expired_authorize_code", "The request includes an expired authorize code value") 52 | ErrExpiredAccessToken = NewError(http.StatusBadRequest, "expired_access_token", "The request includes an expired access token value") 53 | ErrExpiredRefreshToken = NewError(http.StatusBadRequest, "expired_refresh_token", "The request includes an expired refresh token value") 54 | ErrInvalidUsernameOrPassword = NewError(http.StatusBadRequest, "invalid_username_or_password", "The request includes invalid username or password") 55 | ) 56 | -------------------------------------------------------------------------------- /example/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | 12 | // Authorization Code Grant 13 | http.HandleFunc("/test/authorization_code", func(w http.ResponseWriter, r *http.Request) { 14 | http.Redirect(w, r, "http://localhost:8081/authorize?client_id=1&redirect_uri=http://localhost:8080/test/code_to_token&response_type=code", 302) 15 | }) 16 | 17 | http.HandleFunc("/test/code_to_token", func(w http.ResponseWriter, r *http.Request) { 18 | query := r.URL.Query() 19 | code := query.Get("code") 20 | 21 | log.Println("code:" + code) 22 | 23 | url := fmt.Sprintf("http://localhost:8081/token?client_id=1&client_secret=1he5k5ZUrHFjznxN&grant_type=authorization_code&code=%s", code) 24 | 25 | req, _ := http.NewRequest("POST", url, nil) 26 | 27 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 28 | 29 | res, _ := http.DefaultClient.Do(req) 30 | 31 | if res != nil && res.Body != nil { 32 | defer res.Body.Close() 33 | } 34 | 35 | body, _ := ioutil.ReadAll(res.Body) 36 | 37 | log.Println(fmt.Sprintf("POST url: %s repsonse %s", url, body)) 38 | 39 | w.Write(body) 40 | }) 41 | 42 | // Implicit Grant 43 | http.HandleFunc("/test/implicit", func(w http.ResponseWriter, r *http.Request) { 44 | http.Redirect(w, r, "http://localhost:8081/authorize?client_id=1&redirect_uri=http://localhost:8080/test/implicit_token&response_type=token", 302) 45 | }) 46 | 47 | http.HandleFunc("/test/implicit_token", func(w http.ResponseWriter, r *http.Request) { 48 | w.Write([]byte("access_token in uri (only for frontend)")) 49 | }) 50 | 51 | // Password Credentials Grant 52 | http.HandleFunc("/test/password", func(w http.ResponseWriter, r *http.Request) { 53 | url := "http://localhost:8081/token?client_id=1&client_secret=1he5k5ZUrHFjznxN&grant_type=password&username=admin&password=123456" 54 | 55 | req, _ := http.NewRequest("POST", url, nil) 56 | 57 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 58 | 59 | res, _ := http.DefaultClient.Do(req) 60 | 61 | if res != nil && res.Body != nil { 62 | defer res.Body.Close() 63 | } 64 | 65 | body, _ := ioutil.ReadAll(res.Body) 66 | 67 | log.Println(fmt.Sprintf("POST url: %s repsonse %s", url, body)) 68 | 69 | w.Write(body) 70 | }) 71 | 72 | // Client Credentials Grant 73 | http.HandleFunc("/test/client_credentials", func(w http.ResponseWriter, r *http.Request) { 74 | url := "http://localhost:8081/token?client_id=1&client_secret=1he5k5ZUrHFjznxN&grant_type=client_credentials" 75 | 76 | req, _ := http.NewRequest("POST", url, nil) 77 | 78 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 79 | 80 | res, _ := http.DefaultClient.Do(req) 81 | 82 | if res != nil && res.Body != nil { 83 | defer res.Body.Close() 84 | } 85 | 86 | body, _ := ioutil.ReadAll(res.Body) 87 | 88 | log.Println(fmt.Sprintf("POST url: %s repsonse %s", url, body)) 89 | 90 | w.Write(body) 91 | }) 92 | 93 | // Refreshing an access token 94 | http.HandleFunc("/test/refresh_token", func(w http.ResponseWriter, r *http.Request) { 95 | query := r.URL.Query() 96 | refresh_token := query.Get("refresh_token") 97 | if refresh_token == "" { 98 | w.Write([]byte("missing params:refresh_token")) 99 | return 100 | } 101 | url := fmt.Sprintf("http://localhost:8081/token?client_id=1&client_secret=1he5k5ZUrHFjznxN&grant_type=refresh_token&refresh_token=%s", refresh_token) 102 | 103 | req, _ := http.NewRequest("POST", url, nil) 104 | 105 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 106 | 107 | res, _ := http.DefaultClient.Do(req) 108 | 109 | if res != nil && res.Body != nil { 110 | defer res.Body.Close() 111 | } 112 | 113 | body, _ := ioutil.ReadAll(res.Body) 114 | 115 | log.Println(fmt.Sprintf("POST url: %s repsonse %s", url, body)) 116 | 117 | w.Write(body) 118 | }) 119 | 120 | log.Println("Start Listen :8080") 121 | 122 | log.Fatal(http.ListenAndServe(":8080", nil)) 123 | } 124 | -------------------------------------------------------------------------------- /manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego/context" 6 | "github.com/sagacioushugo/oauth2" 7 | "github.com/sagacioushugo/oauth2/generator" 8 | "github.com/sagacioushugo/oauth2/store" 9 | "time" 10 | ) 11 | 12 | var tokenStores = make(map[string]store.TokenStore) 13 | 14 | var generators = make(map[string]generator.Generator) 15 | 16 | func Register(name string, target interface{}) { 17 | if target == nil { 18 | panic("oauth2 manager: Register target is nil") 19 | } 20 | switch t := target.(type) { 21 | case store.TokenStore: 22 | if _, dup := tokenStores[name]; dup { 23 | panic("oauth2 manager: Register called twice for tokenStore " + name) 24 | } 25 | newTarget, _ := target.(store.TokenStore) 26 | tokenStores[name] = newTarget 27 | case generator.Generator: 28 | if _, dup := generators[name]; dup { 29 | panic("oauth2 manager: Register called twice for generator " + name) 30 | } 31 | newTarget, _ := target.(generator.Generator) 32 | generators[name] = newTarget 33 | default: 34 | panic(fmt.Sprintf("oauth2 manager: Unsupported register type %v", t)) 35 | } 36 | } 37 | 38 | type Manager struct { 39 | config *oauth2.ManagerConfig 40 | tokenStore store.TokenStore 41 | generator generator.Generator 42 | } 43 | 44 | func NewManager(managerConfig *oauth2.ManagerConfig) *Manager { 45 | var ok bool 46 | var tokenStore store.TokenStore 47 | var generator generator.Generator 48 | 49 | tokenStore, ok = tokenStores[managerConfig.TokenStoreName] 50 | if !ok { 51 | panic(fmt.Errorf("oauth2 manager: unknow tokenStore %q", managerConfig.TokenStoreName)) 52 | } 53 | if err := tokenStore.Init(managerConfig.TokenStoreConfig); err != nil { 54 | panic(err) 55 | } 56 | 57 | generator, ok = generators[managerConfig.GeneratorName] 58 | if !ok { 59 | panic(fmt.Errorf("oauth2 manager: unknow generator %q", managerConfig.GeneratorName)) 60 | } 61 | 62 | return &Manager{ 63 | config: managerConfig, 64 | tokenStore: tokenStore, 65 | generator: generator, 66 | } 67 | } 68 | 69 | func (manager *Manager) TokenGC() { 70 | manager.tokenStore.GC(manager.config.TokenGcInterval) 71 | time.AfterFunc(time.Duration(manager.config.TokenGcInterval)*time.Second, func() { manager.TokenGC() }) 72 | } 73 | 74 | //tokenStore wrap 75 | func (manager *Manager) TokenNew(ctx *context.Context) store.Token { 76 | return manager.tokenStore.NewToken(ctx) 77 | } 78 | 79 | func (manager *Manager) TokenCreate(token store.Token) error { 80 | return manager.tokenStore.Create(token) 81 | } 82 | 83 | func (manager *Manager) TokenGetByAccess(access string) (store.Token, error) { 84 | return manager.tokenStore.GetByAccess(access) 85 | } 86 | 87 | func (manager *Manager) TokenGetByRefresh(refresh string) (store.Token, error) { 88 | return manager.tokenStore.GetByRefresh(refresh) 89 | } 90 | func (manager *Manager) TokenGetByCode(code string) (store.Token, error) { 91 | return manager.tokenStore.GetByCode(code) 92 | } 93 | 94 | // generator wrap 95 | func (manager *Manager) GenerateCode(token store.Token, req *oauth2.Request, ctx *context.Context) error { 96 | if code, err := manager.generator.Code(req, ctx); err != nil { 97 | return err 98 | } else { 99 | token.SetCode(code) 100 | return manager.tokenStore.Create(token) 101 | } 102 | } 103 | 104 | func (manager *Manager) GenerateToken(token store.Token, req *oauth2.Request, ctx *context.Context, isGenerateRefresh bool) error { 105 | if access, refresh, err := manager.generator.Token(req, ctx, isGenerateRefresh); err != nil { 106 | return err 107 | } else { 108 | if isGenerateRefresh { 109 | token.SetRefresh(refresh) 110 | } 111 | token.SetAccess(access) 112 | return manager.tokenStore.Create(token) 113 | } 114 | } 115 | 116 | func (manager *Manager) GenerateTokenAndDelToken(token store.Token, tokenToDel store.Token, req *oauth2.Request, ctx *context.Context, isGenerateRefresh bool) error { 117 | if access, refresh, err := manager.generator.Token(req, ctx, isGenerateRefresh); err != nil { 118 | return err 119 | } else { 120 | if isGenerateRefresh { 121 | token.SetRefresh(refresh) 122 | } 123 | token.SetAccess(access) 124 | return manager.tokenStore.CreateAndDel(token, tokenToDel) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 2 | github.com/astaxie/beego v1.11.1 h1:6DESefxW5oMcRLFRKi53/6exzup/IR6N4EzzS1n6CnQ= 3 | github.com/astaxie/beego v1.11.1/go.mod h1:i69hVzgauOPSw5qeyF4GVZhn7Od0yG5bbCGzmhbWxgQ= 4 | github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= 5 | github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= 6 | github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY= 7 | github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= 8 | github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= 9 | github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= 10 | github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= 11 | github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= 12 | github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= 13 | github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= 14 | github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 15 | github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= 16 | github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= 17 | github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 18 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 19 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 20 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 21 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 22 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 23 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 24 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 25 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 26 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 27 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 28 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 29 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 30 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 31 | github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= 32 | github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= 33 | github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= 34 | github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= 35 | github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= 36 | github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= 37 | golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI= 38 | golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 39 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 42 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 43 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | -------------------------------------------------------------------------------- /store/store_mem/store_mem.go: -------------------------------------------------------------------------------- 1 | package store_mem 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego/context" 6 | "github.com/sagacioushugo/oauth2" 7 | "github.com/sagacioushugo/oauth2/manager" 8 | "github.com/sagacioushugo/oauth2/store" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type MemToken struct { 14 | Id int64 15 | ClientId string 16 | UserId string 17 | Scope string 18 | Access string 19 | AccessCreateAt time.Time 20 | AccessExpireIn int64 21 | Refresh string 22 | RefreshCreateAt time.Time 23 | RefreshExpireIn int64 24 | Code string 25 | CodeCreateAt time.Time 26 | CodeExpireIn int64 27 | } 28 | 29 | func init() { 30 | manager.Register("mem", &MemTokenStore{}) 31 | } 32 | 33 | func (token *MemToken) GetClientId() string { 34 | return token.ClientId 35 | } 36 | func (token *MemToken) SetClientId(clientId string) { 37 | token.ClientId = clientId 38 | } 39 | func (token *MemToken) GetUserId() string { 40 | return token.UserId 41 | } 42 | func (token *MemToken) SetUserId(userId string) { 43 | token.UserId = userId 44 | } 45 | func (token *MemToken) GetScope() string { 46 | return token.Scope 47 | } 48 | func (token *MemToken) SetScope(scope string) { 49 | token.Scope = scope 50 | } 51 | 52 | //code info 53 | func (token *MemToken) GetCode() string { 54 | return token.Code 55 | } 56 | func (token *MemToken) SetCode(code string) { 57 | token.Code = code 58 | } 59 | func (token *MemToken) GetCodeCreateAt() time.Time { 60 | return token.CodeCreateAt 61 | } 62 | func (token *MemToken) SetCodeCreateAt(codeCreateAt time.Time) { 63 | token.CodeCreateAt = codeCreateAt 64 | } 65 | func (token *MemToken) GetCodeExpireIn() int64 { 66 | return token.CodeExpireIn 67 | } 68 | func (token *MemToken) SetCodeExpireIn(codeExpireIn int64) { 69 | token.CodeExpireIn = codeExpireIn 70 | } 71 | 72 | //access info 73 | func (token *MemToken) GetAccess() string { 74 | return token.Access 75 | } 76 | func (token *MemToken) SetAccess(access string) { 77 | token.Access = access 78 | } 79 | func (token *MemToken) GetAccessCreateAt() time.Time { 80 | return token.AccessCreateAt 81 | } 82 | func (token *MemToken) SetAccessCreateAt(accessCreateAt time.Time) { 83 | token.AccessCreateAt = accessCreateAt 84 | } 85 | func (token *MemToken) GetAccessExpireIn() int64 { 86 | return token.AccessExpireIn 87 | 88 | } 89 | func (token *MemToken) SetAccessExpireIn(accessExpireIn int64) { 90 | token.AccessExpireIn = accessExpireIn 91 | } 92 | 93 | // refresh info 94 | func (token *MemToken) GetRefresh() string { 95 | return token.Refresh 96 | } 97 | func (token *MemToken) SetRefresh(refresh string) { 98 | token.Refresh = refresh 99 | } 100 | func (token *MemToken) GetRefreshCreateAt() time.Time { 101 | return token.RefreshCreateAt 102 | } 103 | func (token *MemToken) SetRefreshCreateAt(refreshCreateAt time.Time) { 104 | token.RefreshCreateAt = refreshCreateAt 105 | } 106 | func (token *MemToken) GetRefreshExpireIn() int64 { 107 | return token.RefreshExpireIn 108 | } 109 | func (token *MemToken) SetRefreshExpireIn(refreshExpireIn int64) { 110 | token.RefreshExpireIn = refreshExpireIn 111 | } 112 | 113 | func (token *MemToken) IsCodeExpired() bool { 114 | if token.CodeExpireIn == 0 { 115 | return true 116 | } else { 117 | ct := time.Now() 118 | return ct.After(token.CodeCreateAt.Add(time.Second * time.Duration(token.CodeExpireIn))) 119 | } 120 | } 121 | 122 | func (token *MemToken) IsAccessExpired() bool { 123 | if token.AccessExpireIn == 0 { 124 | return true 125 | } else { 126 | ct := time.Now() 127 | return ct.After(token.AccessCreateAt.Add(time.Second * time.Duration(token.AccessExpireIn))) 128 | } 129 | } 130 | 131 | func (token *MemToken) IsRefreshExpired() bool { 132 | if token.RefreshExpireIn == 0 { 133 | return true 134 | } else { 135 | ct := time.Now() 136 | return ct.After(token.RefreshCreateAt.Add(time.Second * time.Duration(token.RefreshExpireIn))) 137 | } 138 | } 139 | 140 | type MemTokenStore struct { 141 | autoIncrement int64 142 | list []*MemToken 143 | lock sync.RWMutex 144 | } 145 | 146 | func (s *MemTokenStore) Init(tokenConfig string) error { 147 | return nil 148 | } 149 | 150 | func (s *MemTokenStore) NewToken(ctx *context.Context) store.Token { 151 | token := MemToken{} 152 | return &token 153 | } 154 | 155 | func (s *MemTokenStore) Create(token store.Token) error { 156 | t := token.(*MemToken) 157 | s.lock.Lock() 158 | defer s.lock.Unlock() 159 | t.Id = s.autoIncrement 160 | s.autoIncrement++ 161 | s.list = append(s.list, t) 162 | return nil 163 | } 164 | 165 | func (s *MemTokenStore) GetByAccess(access string) (store.Token, error) { 166 | s.lock.RLock() 167 | defer s.lock.RUnlock() 168 | for _, v := range s.list { 169 | if v.Access == access { 170 | return v, nil 171 | } 172 | } 173 | return nil, oauth2.ErrInvalidAccessToken 174 | 175 | } 176 | func (s *MemTokenStore) GetByRefresh(refresh string) (store.Token, error) { 177 | s.lock.RLock() 178 | defer s.lock.RUnlock() 179 | for _, v := range s.list { 180 | if v.Refresh == refresh { 181 | return v, nil 182 | } 183 | } 184 | return nil, oauth2.ErrInvalidRefreshToken 185 | 186 | } 187 | func (s *MemTokenStore) GetByCode(code string) (store.Token, error) { 188 | s.lock.RLock() 189 | defer s.lock.RUnlock() 190 | for _, v := range s.list { 191 | if v.Code == code { 192 | return v, nil 193 | } 194 | } 195 | return nil, oauth2.ErrInvalidAuthorizeCode 196 | } 197 | func (s *MemTokenStore) CreateAndDel(tokenNew store.Token, tokenDel store.Token) error { 198 | new := tokenNew.(*MemToken) 199 | del := tokenDel.(*MemToken) 200 | s.lock.Lock() 201 | defer s.lock.Unlock() 202 | var ok bool 203 | for i, v := range s.list { 204 | if v.Id == del.Id { 205 | s.list = append(s.list[:i], s.list[i+1:]...) 206 | ok = true 207 | break 208 | } 209 | } 210 | if !ok { 211 | return fmt.Errorf("del token not found") 212 | } 213 | new.Id = s.autoIncrement 214 | s.autoIncrement++ 215 | 216 | s.list = append(s.list, new) 217 | return nil 218 | } 219 | func (s *MemTokenStore) GC(gcInterval int64) { 220 | s.lock.Lock() 221 | defer s.lock.Unlock() 222 | now := time.Now() 223 | newList := make([]*MemToken, len(s.list), cap(s.list)) 224 | for _, v := range s.list { 225 | if now.Before(v.AccessCreateAt.Add(time.Second * time.Duration(gcInterval))) { 226 | newList = append(newList, v) 227 | } 228 | } 229 | s.list = newList 230 | } 231 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego/context" 6 | "github.com/astaxie/beego/logs" 7 | "github.com/sagacioushugo/oauth2" 8 | "github.com/sagacioushugo/oauth2/manager" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func NewDefaultServer() *Server { 14 | config := oauth2.NewDefaultConfig() 15 | m := manager.NewManager(&config.ManagerConfig) 16 | go m.TokenGC() 17 | return &Server{ 18 | config: config, 19 | Manager: m, 20 | } 21 | } 22 | 23 | func NewServer(config *oauth2.Config) *Server { 24 | if config == nil { 25 | panic("oauth2 config must not be nil") 26 | } 27 | m := manager.NewManager(&config.ManagerConfig) 28 | 29 | return &Server{ 30 | config: config, 31 | Manager: m, 32 | } 33 | 34 | } 35 | 36 | type Server struct { 37 | config *oauth2.Config 38 | Manager *manager.Manager 39 | 40 | checkUserGrantAccessHandler CheckUserGrantAccessHandler 41 | 42 | checkUserPasswordHandler CheckUserPasswordHandler 43 | 44 | authenticateClientHandler AuthenticateClientHandler 45 | 46 | customizedCheckScopeHandler CustomizedCheckScopeHandler 47 | 48 | customizedRefreshingScopeHandler CustomizedRefreshingScopeHandler 49 | 50 | customizedAuthorizeErrHandler CustomizedAuthorizeErrHandler 51 | 52 | customizedClientCredentialsUserIdHandler CustomizedClientCredentialsUserIdHandler 53 | 54 | customizedTokenExtensionFieldsHandler CustomizedTokenExtensionFieldsHandler 55 | } 56 | 57 | /** 58 | handle authorize 59 | */ 60 | func (s *Server) Authorize(ctx *context.Context) { 61 | var resErr error 62 | var tokenData map[string]interface{} 63 | var req *oauth2.Request 64 | var userId string 65 | if req, resErr = s.validationAuthorizeRequest(ctx); resErr != nil { 66 | goto Resp 67 | } 68 | 69 | if h := s.checkUserGrantAccessHandler; h != nil { 70 | if userId, resErr = h(req, ctx); resErr != nil { 71 | goto Resp 72 | } else { 73 | req.UserId = userId 74 | } 75 | } else { 76 | resErr = fmt.Errorf("checkUserGrantAccessHandler is nil") 77 | goto Resp 78 | } 79 | tokenData, resErr = s.generateToken(req, ctx) 80 | 81 | Resp: 82 | if ctx.ResponseWriter.Started { 83 | return 84 | } 85 | if resErr != nil { 86 | if h := s.customizedAuthorizeErrHandler; h != nil { 87 | if herr := h(resErr, ctx); herr != nil { 88 | resErr = herr 89 | } 90 | } 91 | if ctx.ResponseWriter.Started { 92 | return 93 | } 94 | if err := RedirectError(req, resErr, ctx); err != nil { 95 | _ = ResponseErr(err, ctx) 96 | } 97 | 98 | } else { 99 | if req.State != "" { 100 | tokenData["state"] = req.State 101 | } 102 | if err := Redirect(req, tokenData, ctx); err != nil { 103 | _ = ResponseErr(err, ctx) 104 | } 105 | } 106 | } 107 | 108 | func (s *Server) validationAuthorizeRequest(ctx *context.Context) (req *oauth2.Request, err error) { 109 | var redirectUri = ctx.Input.Query("redirect_uri") 110 | var clientId = ctx.Input.Query("client_id") 111 | var state = ctx.Input.Query("state") 112 | var scope = ctx.Input.Query("scope") 113 | var responseType = oauth2.ResponseType(ctx.Request.FormValue("response_type")) 114 | var grantType oauth2.GrantType = "" 115 | var clientRedirectUris string 116 | 117 | req = &oauth2.Request{ 118 | RedirectUri: redirectUri, 119 | ResponseType: responseType, 120 | ClientId: clientId, 121 | State: state, 122 | Scope: scope, 123 | } 124 | // check invalid request 125 | if !(ctx.Request.Method == "GET" || ctx.Request.Method == "POST") || clientId == "" { 126 | err = oauth2.ErrInvalidRequest 127 | return 128 | } 129 | 130 | // check unsupported response type 131 | if !responseType.IsValid() { 132 | err = oauth2.ErrUnsupportedResponseType 133 | return 134 | } else if responseType == oauth2.Code { 135 | grantType = oauth2.AuthorizationCode 136 | 137 | } else if responseType == oauth2.Token { 138 | grantType = oauth2.Implicit 139 | } 140 | if _, ok := s.config.AllowGrantType[grantType]; !ok { 141 | err = oauth2.ErrUnsupportedResponseType 142 | return 143 | } 144 | req.GrantType = grantType 145 | 146 | // check invalid client 147 | if h := s.authenticateClientHandler; h != nil { 148 | var herr error 149 | if clientRedirectUris, herr = h(ctx, clientId); herr != nil { 150 | //err type should be oauth2.ErrInvalidClient or oauth2.ErrUnauthorizedClient 151 | err = herr 152 | return 153 | } 154 | } else { 155 | err = fmt.Errorf("authenticateClientHandler is nil") 156 | return 157 | } 158 | 159 | // check redirect uri 160 | if redirectUri == "" && !s.config.RedirectAllowEmpty { 161 | err = oauth2.ErrInvalidRedirectURI 162 | return 163 | } else { 164 | uris := strings.Split(clientRedirectUris, s.config.RedirectUriSep) 165 | if redirectUri == "" && len(uris) > 0 { 166 | redirectUri = uris[0] 167 | } else if redirectUri != "" { 168 | match := false 169 | for _, v := range uris { 170 | if v == redirectUri { 171 | match = true 172 | break 173 | } 174 | } 175 | if !match { 176 | err = oauth2.ErrInvalidRedirectURI 177 | return 178 | } 179 | } else { 180 | err = oauth2.ErrInvalidRedirectURI 181 | return 182 | } 183 | } 184 | 185 | // check invalid scope 186 | if h := s.customizedCheckScopeHandler; h != nil { 187 | if allowed, herr := h(scope, &grantType, ctx); herr != nil { 188 | err = herr 189 | return 190 | } else if !allowed { 191 | err = oauth2.ErrInvalidScope 192 | return 193 | } 194 | } 195 | 196 | return 197 | } 198 | 199 | /** 200 | handle token 201 | */ 202 | func (s *Server) Token(ctx *context.Context) { 203 | var req *oauth2.Request 204 | var resErr error 205 | var tokenData map[string]interface{} 206 | if req, resErr = s.validationTokenRequest(ctx); resErr != nil { 207 | goto Resp 208 | } 209 | tokenData, resErr = s.generateToken(req, ctx) 210 | 211 | Resp: 212 | if ctx.ResponseWriter.Started { 213 | return 214 | } 215 | if resErr != nil { 216 | if err := ResponseErr(resErr, ctx); err != nil { 217 | logs.Error(err) 218 | } 219 | } else { 220 | 221 | if err := ResponseToken(tokenData, nil, ctx); err != nil { 222 | logs.Error(err) 223 | } 224 | } 225 | 226 | } 227 | 228 | func (s *Server) validationTokenRequest(ctx *context.Context) (req *oauth2.Request, err error) { 229 | 230 | clientId, clientSecret, ok := ParseBasicAuth(ctx.Input.Header("Authorization")) 231 | if !ok { 232 | clientId = ctx.Input.Query("client_id") 233 | clientSecret = ctx.Input.Query("client_secret") 234 | } 235 | var code = ctx.Input.Query("code") 236 | var scope = ctx.Input.Query("scope") 237 | var refresh = ctx.Input.Query("refresh_token") 238 | var username = ctx.Input.Query("username") 239 | var password = ctx.Input.Query("password") 240 | var userId string 241 | var grantType = oauth2.GrantType(ctx.Input.Query("grant_type")) 242 | // check invalid request 243 | if !(ctx.Request.Method == "GET" || ctx.Request.Method == "POST") || clientId == "" { 244 | return nil, oauth2.ErrInvalidRequest 245 | } 246 | 247 | // check unsupported grant type 248 | if !grantType.IsValid() { 249 | return nil, oauth2.ErrUnsupportedGrantType 250 | } else if _, ok := s.config.AllowGrantType[grantType]; !ok { 251 | return nil, oauth2.ErrUnsupportedGrantType 252 | } 253 | 254 | // check invalid client 255 | if h := s.authenticateClientHandler; h != nil { 256 | if _, herr := h(ctx, clientId, clientSecret); herr != nil { 257 | return nil, herr 258 | } 259 | } else { 260 | return nil, fmt.Errorf("authenticateClientHandler is nil") 261 | } 262 | 263 | // check invalid scope 264 | if grantType != oauth2.AuthorizationCode && grantType != oauth2.RefreshToken { 265 | if h := s.customizedCheckScopeHandler; h != nil { 266 | if allowed, herr := h(scope, &grantType, ctx); herr != nil { 267 | return nil, herr 268 | } else if !allowed { 269 | return nil, oauth2.ErrInvalidScope 270 | } 271 | } 272 | } 273 | // AuthorizationCode and RefreshToken userId consists in their code or token 274 | switch grantType { 275 | case oauth2.AuthorizationCode: 276 | if code == "" { 277 | return nil, oauth2.ErrInvalidAuthorizeCode 278 | } 279 | case oauth2.PasswordCredentials: 280 | if h := s.checkUserPasswordHandler; h != nil { 281 | var herr error 282 | if userId, herr = h(username, password, ctx); herr != nil { 283 | return nil, herr 284 | } 285 | } else { 286 | return nil, fmt.Errorf("checkUserPasswordHandler is nil") 287 | } 288 | case oauth2.ClientCredentials: 289 | if h := s.customizedClientCredentialsUserIdHandler; h != nil { 290 | userId = h(clientId) 291 | } else { 292 | userId = fmt.Sprintf("client-%s", clientId) 293 | } 294 | case oauth2.RefreshToken: 295 | if refresh == "" { 296 | return nil, oauth2.ErrInvalidRefreshToken 297 | } 298 | } 299 | 300 | req = &oauth2.Request{ 301 | ClientId: clientId, 302 | ClientSecret: clientSecret, 303 | GrantType: grantType, 304 | Code: code, 305 | UserId: userId, 306 | Scope: scope, 307 | Refresh: refresh, 308 | } 309 | return req, nil 310 | 311 | } 312 | 313 | func (s *Server) generateToken(req *oauth2.Request, ctx *context.Context) (map[string]interface{}, error) { 314 | 315 | config := s.config.AllowGrantType[req.GrantType] 316 | 317 | token := s.Manager.TokenNew(ctx) 318 | 319 | now := time.Now() 320 | 321 | switch req.GrantType { 322 | case oauth2.AuthorizationCode: 323 | if req.ResponseType == oauth2.Code { 324 | token.SetUserId(req.UserId) 325 | token.SetClientId(req.ClientId) 326 | token.SetScope(req.Scope) 327 | token.SetCodeExpireIn(config.CodeExpire) 328 | token.SetCodeCreateAt(now) 329 | if err := s.Manager.GenerateCode(token, req, ctx); err != nil { 330 | return nil, err 331 | } 332 | } else { 333 | if code, err := s.Manager.TokenGetByCode(req.Code); err != nil { 334 | return nil, err 335 | } else if code == nil || code.GetClientId() != req.ClientId { 336 | return nil, oauth2.ErrInvalidAuthorizeCode 337 | } else if code.IsCodeExpired() { 338 | return nil, oauth2.ErrExpiredAuthorizeCode 339 | } else { 340 | token.SetUserId(code.GetUserId()) 341 | token.SetClientId(code.GetClientId()) 342 | token.SetScope(code.GetScope()) 343 | token.SetAccessExpireIn(config.AccessTokenExpire) 344 | token.SetAccessCreateAt(now) 345 | if config.IsGenerateRefresh { 346 | token.SetRefreshExpireIn(config.RefreshTokenExpire) 347 | token.SetRefreshCreateAt(now) 348 | } 349 | if err := s.Manager.GenerateTokenAndDelToken(token, code, req, ctx, config.IsGenerateRefresh); err != nil { 350 | return nil, err 351 | } 352 | } 353 | 354 | } 355 | case oauth2.RefreshToken: 356 | if refresh, err := s.Manager.TokenGetByRefresh(req.Refresh); err != nil { 357 | return nil, err 358 | } else if refresh == nil || refresh.GetClientId() != req.ClientId { 359 | return nil, oauth2.ErrInvalidRefreshToken 360 | } else if refresh.IsRefreshExpired() { 361 | return nil, oauth2.ErrExpiredRefreshToken 362 | } else { 363 | if h := s.customizedRefreshingScopeHandler; h != nil { 364 | if allowed, herr := h(req.Scope, refresh.GetScope(), ctx); herr != nil { 365 | return nil, herr 366 | } else if !allowed { 367 | return nil, oauth2.ErrInvalidScope 368 | } 369 | token.SetScope(req.Scope) 370 | } else { 371 | token.SetScope(refresh.GetScope()) 372 | } 373 | token.SetUserId(refresh.GetUserId()) 374 | token.SetClientId(refresh.GetClientId()) 375 | token.SetAccessExpireIn(config.AccessTokenExpire) 376 | token.SetAccessCreateAt(now) 377 | if config.IsGenerateRefresh { 378 | token.SetRefreshExpireIn(config.RefreshTokenExpire) 379 | if config.IsResetRefreshTime { 380 | token.SetRefreshCreateAt(now) 381 | } else { 382 | token.SetRefreshCreateAt(refresh.GetRefreshCreateAt()) 383 | 384 | } 385 | } 386 | if err := s.Manager.GenerateTokenAndDelToken(token, refresh, req, ctx, config.IsGenerateRefresh); err != nil { 387 | return nil, err 388 | } 389 | } 390 | default: 391 | // Implicit PasswordCredentials ClientCredentials 392 | token.SetScope(req.Scope) 393 | token.SetUserId(req.UserId) 394 | token.SetClientId(req.ClientId) 395 | token.SetAccessExpireIn(config.AccessTokenExpire) 396 | token.SetAccessCreateAt(now) 397 | if config.IsGenerateRefresh { 398 | token.SetRefreshExpireIn(config.RefreshTokenExpire) 399 | token.SetRefreshCreateAt(now) 400 | } 401 | if err := s.Manager.GenerateToken(token, req, ctx, config.IsGenerateRefresh); err != nil { 402 | return nil, err 403 | } 404 | } 405 | data := make(map[string]interface{}) 406 | 407 | if req.GrantType == oauth2.AuthorizationCode && req.ResponseType == oauth2.Code { 408 | data["code"] = token.GetCode() 409 | } else { 410 | data["access_token"] = token.GetAccess() 411 | data["token_type"] = s.config.TokenType 412 | data["expires_in"] = token.GetAccessExpireIn() 413 | 414 | if config.IsGenerateRefresh { 415 | data["refresh_token"] = token.GetRefresh() 416 | } 417 | 418 | if h := s.customizedTokenExtensionFieldsHandler; h != nil { 419 | ext := h(token, req, ctx) 420 | for k, v := range ext { 421 | if _, ok := data[k]; ok { 422 | continue 423 | } 424 | data[k] = v 425 | } 426 | } 427 | } 428 | 429 | return data, nil 430 | } 431 | 432 | func (s *Server) SetCheckUserGrantAccessHandler(h CheckUserGrantAccessHandler) { 433 | s.checkUserGrantAccessHandler = h 434 | } 435 | 436 | func (s *Server) SetCheckUserPasswordHandler(h CheckUserPasswordHandler) { 437 | s.checkUserPasswordHandler = h 438 | } 439 | 440 | func (s *Server) SetAuthenticateClientHandler(h AuthenticateClientHandler) { 441 | s.authenticateClientHandler = h 442 | } 443 | 444 | func (s *Server) SetCustomizedCheckScopeHandler(h CustomizedCheckScopeHandler) { 445 | s.customizedCheckScopeHandler = h 446 | } 447 | 448 | func (s *Server) SetCustomizedRefreshingScopeHandler(h CustomizedRefreshingScopeHandler) { 449 | s.customizedRefreshingScopeHandler = h 450 | } 451 | 452 | func (s *Server) SetCustomizedAuthorizeErrHandler(h CustomizedAuthorizeErrHandler) { 453 | s.customizedAuthorizeErrHandler = h 454 | } 455 | 456 | func (s *Server) SetCustomizedClientCredentialsUserIdHandler(h CustomizedClientCredentialsUserIdHandler) { 457 | s.customizedClientCredentialsUserIdHandler = h 458 | } 459 | 460 | func (s *Server) SetCustomizedTokenExtensionFieldsHandler(h CustomizedTokenExtensionFieldsHandler) { 461 | s.customizedTokenExtensionFieldsHandler = h 462 | } 463 | --------------------------------------------------------------------------------