Skip to content

Commit

Permalink
Merge pull request #1 from LyricTian/master
Browse files Browse the repository at this point in the history
提交包实现文件
  • Loading branch information
LyricTian committed May 26, 2016
2 parents 05eaef0 + 36978b2 commit 5aa7f10
Show file tree
Hide file tree
Showing 30 changed files with 1,709 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ _testmain.go
*.exe
*.test
*.prof

# OSX
*.DS_Store
89 changes: 89 additions & 0 deletions README.md
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.
153 changes: 153 additions & 0 deletions authorizationCode.go
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
}
101 changes: 101 additions & 0 deletions authorizationCodeGenerate.go
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
}
39 changes: 39 additions & 0 deletions authorizationCodeGenerate_test.go
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)
})
})
})
}
Loading

0 comments on commit 5aa7f10

Please sign in to comment.