-
Notifications
You must be signed in to change notification settings - Fork 566
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from LyricTian/master
提交包实现文件
- Loading branch information
Showing
30 changed files
with
1,709 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,3 +22,6 @@ _testmain.go | |
*.exe | ||
*.test | ||
*.prof | ||
|
||
# OSX | ||
*.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
Golang OAuth 2.0 | ||
================ | ||
|
||
[![GoDoc](https://godoc.org/gopkg.in/oauth2.v1?status.svg)](https://godoc.org/gopkg.in/oauth2.v1) | ||
[![Go Report Card](https://goreportcard.com/badge/gopkg.in/oauth2.v1)](https://goreportcard.com/report/gopkg.in/oauth2.v1) | ||
|
||
> 基于Golang实现的OAuth 2.0协议相关操作,包括:令牌(或授权码)的生成、存储、验证操作以及更新令牌、废除令牌; 具有简单、灵活的特点; 其中所涉及的相关http请求操作在这里不做处理; 支持授权码模式、简化模式、密码模式、客户端模式; 默认使用MongoDB存储相关信息 | ||
获取 | ||
---- | ||
|
||
```bash | ||
$ go get -v gopkg.in/oauth2.v1 | ||
``` | ||
|
||
范例 | ||
---- | ||
|
||
> 数据初始化:初始化相关的客户端信息 | ||
```go | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"gopkg.in/oauth2.v1" | ||
) | ||
|
||
func main() { | ||
mongoConfig := oauth2.NewMongoConfig("mongodb://127.0.0.1:27017", "test") | ||
// 创建默认的OAuth2管理实例(基于MongoDB) | ||
manager, err := oauth2.CreateDefaultOAuthManager(mongoConfig, "", "", nil) | ||
if err != nil { | ||
panic(err) | ||
} | ||
// 模拟授权码模式 | ||
// 使用默认参数,生成授权码 | ||
code, err := manager.GetACManager(). | ||
GenerateCode("clientID_x", "userID_x", "http://www.example.com/cb", "scopes") | ||
if err != nil { | ||
panic(err) | ||
} | ||
// 生成访问令牌及更新令牌 | ||
genToken, err := manager.GetACManager(). | ||
GenerateToken(code, "http://www.example.com/cb", "clientID_x", "clientSecret_x", true) | ||
if err != nil { | ||
panic(err) | ||
} | ||
// 检查访问令牌 | ||
checkToken, err := manager.CheckAccessToken(genToken.AccessToken) | ||
if err != nil { | ||
panic(err) | ||
} | ||
// TODO: 使用用户标识、申请的授权范围响应数据 | ||
fmt.Println(checkToken.UserID, checkToken.Scope) | ||
// 申请一个新的访问令牌 | ||
newToken, err := manager.RefreshAccessToken(checkToken.RefreshToken, "scopes") | ||
if err != nil { | ||
panic(err) | ||
} | ||
fmt.Println(newToken.AccessToken, newToken.ATExpiresIn) | ||
// TODO: 将新的访问令牌响应给客户端 | ||
} | ||
``` | ||
|
||
执行测试 | ||
---- | ||
|
||
```bash | ||
$ go test -v | ||
# 或 | ||
$ goconvey --port=9090 | ||
``` | ||
|
||
License | ||
------- | ||
|
||
``` | ||
Copyright 2016.All rights reserved. | ||
``` | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at | ||
|
||
``` | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
``` | ||
|
||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package oauth2 | ||
|
||
import ( | ||
"time" | ||
|
||
"gopkg.in/LyricTian/lib.v2" | ||
) | ||
|
||
// NewACManager 创建授权码模式管理实例 | ||
// oaManager OAuth授权管理 | ||
// config 配置参数(nil则使用默认值) | ||
func NewACManager(oaManager *OAuthManager, config *ACConfig) *ACManager { | ||
if config == nil { | ||
config = new(ACConfig) | ||
} | ||
if config.RandomCodeLen == 0 { | ||
config.RandomCodeLen = DefaultRandomCodeLen | ||
} | ||
if config.ACExpiresIn == 0 { | ||
config.ACExpiresIn = DefaultACExpiresIn | ||
} | ||
if config.ATExpiresIn == 0 { | ||
config.ATExpiresIn = DefaultATExpiresIn | ||
} | ||
if config.RTExpiresIn == 0 { | ||
config.RTExpiresIn = DefaultRTExpiresIn | ||
} | ||
acManager := &ACManager{ | ||
oAuthManager: oaManager, | ||
config: config, | ||
} | ||
return acManager | ||
} | ||
|
||
// ACManager 授权码模式管理(Authorization Code Manager) | ||
type ACManager struct { | ||
oAuthManager *OAuthManager // 授权管理 | ||
config *ACConfig // 配置参数 | ||
} | ||
|
||
// GenerateCode 生成授权码 | ||
// clientID 客户端标识 | ||
// userID 用户标识 | ||
// redirectURI 重定向URI | ||
// scopes 应用授权标识 | ||
func (am *ACManager) GenerateCode(clientID, userID, redirectURI, scopes string) (code string, err error) { | ||
cli, err := am.oAuthManager.ValidateClient(clientID, redirectURI) | ||
if err != nil { | ||
return | ||
} | ||
acInfo := ACInfo{ | ||
ClientID: cli.ID(), | ||
UserID: userID, | ||
RedirectURI: redirectURI, | ||
Scope: scopes, | ||
Code: lib.NewRandom(am.config.RandomCodeLen).NumberAndLetter(), | ||
CreateAt: time.Now().Unix(), | ||
ExpiresIn: time.Duration(am.config.ACExpiresIn) * time.Second, | ||
} | ||
id, err := am.oAuthManager.ACStore.Put(acInfo) | ||
if err != nil { | ||
return | ||
} | ||
acInfo.ID = id | ||
code, err = am.oAuthManager.ACGenerate.Code(&acInfo) | ||
return | ||
} | ||
|
||
// GenerateToken 生成令牌 | ||
// code 授权码 | ||
// redirectURI 重定向URI | ||
// clientID 客户端标识 | ||
// clientSecret 客户端秘钥 | ||
// isGenerateRefresh 是否生成更新令牌 | ||
func (am *ACManager) GenerateToken(code, redirectURI, clientID, clientSecret string, isGenerateRefresh bool) (token *Token, err error) { | ||
acInfo, err := am.getACInfo(code) | ||
if err != nil { | ||
return | ||
} else if acInfo.RedirectURI != redirectURI { | ||
err = ErrACInvalid | ||
return | ||
} else if acInfo.ClientID != clientID { | ||
err = ErrACInvalid | ||
return | ||
} | ||
cli, err := am.oAuthManager.ClientStore.GetByID(acInfo.ClientID) | ||
if err != nil { | ||
return | ||
} else if clientSecret != cli.Secret() { | ||
err = ErrCSInvalid | ||
return | ||
} | ||
createAt := time.Now().Unix() | ||
basicInfo := NewTokenBasicInfo(cli, acInfo.UserID, createAt) | ||
atValue, err := am.oAuthManager.TokenGenerate.AccessToken(basicInfo) | ||
if err != nil { | ||
return | ||
} | ||
tokenValue := Token{ | ||
ClientID: acInfo.ClientID, | ||
UserID: acInfo.UserID, | ||
AccessToken: atValue, | ||
ATCreateAt: createAt, | ||
ATExpiresIn: time.Duration(am.config.ATExpiresIn) * time.Second, | ||
Scope: acInfo.Scope, | ||
CreateAt: createAt, | ||
Status: Actived, | ||
} | ||
if isGenerateRefresh { | ||
rtValue, rtErr := am.oAuthManager.TokenGenerate.RefreshToken(basicInfo) | ||
if rtErr != nil { | ||
err = rtErr | ||
return | ||
} | ||
tokenValue.RefreshToken = rtValue | ||
tokenValue.RTCreateAt = createAt | ||
tokenValue.RTExpiresIn = time.Duration(am.config.RTExpiresIn) * time.Second | ||
} | ||
id, err := am.oAuthManager.TokenStore.Create(tokenValue) | ||
if err != nil { | ||
return | ||
} | ||
tokenValue.ID = id | ||
token = &tokenValue | ||
return | ||
} | ||
|
||
// getACInfo 根据授权码获取授权信息 | ||
func (am *ACManager) getACInfo(code string) (info *ACInfo, err error) { | ||
if code == "" { | ||
err = ErrACNotFound | ||
return | ||
} | ||
acID, err := am.oAuthManager.ACGenerate.Parse(code) | ||
if err != nil { | ||
return | ||
} | ||
acInfo, err := am.oAuthManager.ACStore.TakeByID(acID) | ||
if err != nil { | ||
return | ||
} | ||
acValid, err := am.oAuthManager.ACGenerate.Verify(code, acInfo) | ||
if err != nil { | ||
return | ||
} | ||
if !acValid || | ||
(acInfo.CreateAt+int64(acInfo.ExpiresIn/time.Second)) < time.Now().Unix() { | ||
err = ErrACInvalid | ||
return | ||
} | ||
info = acInfo | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package oauth2 | ||
|
||
import ( | ||
"bytes" | ||
"encoding/base64" | ||
"errors" | ||
"strconv" | ||
"strings" | ||
|
||
"gopkg.in/LyricTian/lib.v2" | ||
) | ||
|
||
// ACGenerate 授权码生成接口(Authorization Code Generate) | ||
type ACGenerate interface { | ||
// Code 根据授权码相关信息生成授权码 | ||
Code(info *ACInfo) (string, error) | ||
|
||
// Parse 解析授权码,返回授权信息ID | ||
Parse(code string) (int64, error) | ||
|
||
// Verify 验证授权码的有效性 | ||
Verify(code string, info *ACInfo) (bool, error) | ||
} | ||
|
||
// NewDefaultACGenerate 创建默认的授权码生成方式 | ||
func NewDefaultACGenerate() ACGenerate { | ||
return &ACGenerateDefault{} | ||
} | ||
|
||
// ACGenerateDefault 默认的授权码生成方式 | ||
type ACGenerateDefault struct{} | ||
|
||
func (ag *ACGenerateDefault) genToken(info *ACInfo) (string, error) { | ||
var buf bytes.Buffer | ||
_, _ = buf.WriteString(info.ClientID) | ||
_ = buf.WriteByte('_') | ||
_, _ = buf.WriteString(info.UserID) | ||
_ = buf.WriteByte('\n') | ||
_, _ = buf.WriteString(strconv.FormatInt(info.CreateAt, 10)) | ||
_ = buf.WriteByte('\n') | ||
_, _ = buf.WriteString(info.Code) | ||
md5Val, err := lib.NewEncryption(buf.Bytes()).MD5() | ||
if err != nil { | ||
return "", err | ||
} | ||
buf.Reset() | ||
md5Val = md5Val[:15] | ||
return md5Val, nil | ||
} | ||
|
||
// Code Authorization code | ||
func (ag *ACGenerateDefault) Code(info *ACInfo) (string, error) { | ||
tokenVal, err := ag.genToken(info) | ||
if err != nil { | ||
return "", err | ||
} | ||
val := base64.URLEncoding.EncodeToString([]byte(tokenVal + "." + strconv.FormatInt(info.ID, 10))) | ||
return strings.TrimRight(val, "="), nil | ||
} | ||
|
||
func (ag *ACGenerateDefault) parse(code string) (id int64, token string, err error) { | ||
codeLen := len(code) % 4 | ||
if codeLen > 0 { | ||
codeLen = 4 - codeLen | ||
} | ||
code = code + strings.Repeat("=", codeLen) | ||
codeVal, err := base64.URLEncoding.DecodeString(code) | ||
if err != nil { | ||
return | ||
} | ||
tokenVal := strings.SplitN(string(codeVal), ".", 2) | ||
if len(tokenVal) != 2 { | ||
err = errors.New("Token is invalid") | ||
return | ||
} | ||
id, err = strconv.ParseInt(tokenVal[1], 10, 64) | ||
if err != nil { | ||
return | ||
} | ||
token = tokenVal[0] | ||
return | ||
} | ||
|
||
// Parse Parse authorization code | ||
func (ag *ACGenerateDefault) Parse(code string) (id int64, err error) { | ||
id, _, err = ag.parse(code) | ||
return | ||
} | ||
|
||
// Verify Verify code | ||
func (ag *ACGenerateDefault) Verify(code string, info *ACInfo) (valid bool, err error) { | ||
_, token, err := ag.parse(code) | ||
if err != nil { | ||
return | ||
} | ||
tokenVal, err := ag.genToken(info) | ||
if err != nil { | ||
return | ||
} | ||
return token == tokenVal, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package oauth2_test | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"gopkg.in/LyricTian/lib.v2" | ||
"gopkg.in/oauth2.v1" | ||
|
||
. "github.com/smartystreets/goconvey/convey" | ||
) | ||
|
||
func TestACGenerate(t *testing.T) { | ||
Convey("Authorization code generate test", t, func() { | ||
acGenerate := oauth2.NewDefaultACGenerate() | ||
info := &oauth2.ACInfo{ | ||
ID: 1, | ||
ClientID: "123456", | ||
UserID: "999999", | ||
Code: lib.NewRandom(6).NumberAndLetter(), | ||
CreateAt: time.Now().Unix(), | ||
} | ||
Convey("Generate code", func() { | ||
code, err := acGenerate.Code(info) | ||
So(err, ShouldBeNil) | ||
So(code, ShouldNotBeBlank) | ||
Convey("Parse code", func() { | ||
id, err := acGenerate.Parse(code) | ||
So(err, ShouldBeNil) | ||
So(id, ShouldEqual, 1) | ||
}) | ||
Convey("Verify code", func() { | ||
valid, err := acGenerate.Verify(code, info) | ||
So(err, ShouldBeNil) | ||
So(valid, ShouldBeTrue) | ||
}) | ||
}) | ||
}) | ||
} |
Oops, something went wrong.