Skip to content

Commit d8a260f

Browse files
committed
add redis cluster support
1 parent de39c2c commit d8a260f

File tree

6 files changed

+293
-175
lines changed

6 files changed

+293
-175
lines changed

.travis.yml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
language: go
2+
sudo: false
3+
go_import_path: gopkg.in/go-oauth2/redis.v3
4+
go:
5+
- 1.9
6+
services:
7+
- redis-server
8+
before_install:
9+
- go get -t -v ./...
10+
11+
script:
12+
- go test -race -coverprofile=coverage.txt -covermode=atomic
13+
14+
after_success:
15+
- bash <(curl -s https://codecov.io/bash)

README.md

+19-16
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
# Redis Storage for OAuth 2.0
1+
# Redis Storage for [OAuth 2.0](https://github.com/go-oauth2/oauth2)
22

3-
> Based on the redis token storage
4-
5-
[![License][License-Image]][License-Url]
6-
[![ReportCard][ReportCard-Image]][ReportCard-Url]
7-
[![GoDoc][GoDoc-Image]][GoDoc-Url]
3+
[![Build][Build-Status-Image]][Build-Status-Url] [![Codecov][codecov-image]][codecov-url] [![ReportCard][reportcard-image]][reportcard-url] [![GoDoc][godoc-image]][godoc-url] [![License][license-image]][license-url]
84

95
## Install
106

117
``` bash
12-
$ go get -u -v gopkg.in/go-oauth2/redis.v1
8+
$ go get -u -v gopkg.in/go-oauth2/redis.v3
139
```
1410

1511
## Usage
@@ -18,18 +14,21 @@ $ go get -u -v gopkg.in/go-oauth2/redis.v1
1814
package main
1915

2016
import (
21-
"gopkg.in/go-oauth2/redis.v1"
17+
"gopkg.in/go-oauth2/redis.v3"
2218
"gopkg.in/oauth2.v3/manage"
2319
)
2420

2521
func main() {
2622
manager := manage.NewDefaultManager()
23+
2724
// use redis token store
28-
manager.MustTokenStorage(redis.NewTokenStore(&redis.Config{
25+
manager.MustTokenStorage(redis.NewRedisStore(&redis.Options{
2926
Addr: "127.0.0.1:6379",
27+
DB: 15,
3028
}))
3129

32-
// ...
30+
// use redis cluster store
31+
// redis.NewRedisClusterStore()
3332
}
3433
```
3534

@@ -39,9 +38,13 @@ func main() {
3938
Copyright (c) 2016 Lyric
4039
```
4140

42-
[License-Url]: http://opensource.org/licenses/MIT
43-
[License-Image]: https://img.shields.io/npm/l/express.svg
44-
[ReportCard-Url]: https://goreportcard.com/report/github.com/go-oauth2/redis
45-
[ReportCard-Image]: https://goreportcard.com/badge/github.com/go-oauth2/redis
46-
[GoDoc-Url]: https://godoc.org/github.com/go-oauth2/redis
47-
[GoDoc-Image]: https://godoc.org/github.com/go-oauth2/redis?status.svg
41+
[Build-Status-Url]: https://travis-ci.org/go-oauth2/redis
42+
[Build-Status-Image]: https://travis-ci.org/go-oauth2/redis.svg?branch=master
43+
[codecov-url]: https://codecov.io/gh/go-oauth2/redis
44+
[codecov-image]: https://codecov.io/gh/go-oauth2/redis/branch/master/graph/badge.svg
45+
[reportcard-url]: https://goreportcard.com/report/gopkg.in/go-oauth2/redis.v3
46+
[reportcard-image]: https://goreportcard.com/badge/gopkg.in/go-oauth2/redis.v3
47+
[godoc-url]: https://godoc.org/gopkg.in/go-oauth2/redis.v3
48+
[godoc-image]: https://godoc.org/gopkg.in/go-oauth2/redis.v3?status.svg
49+
[license-url]: http://opensource.org/licenses/MIT
50+
[license-image]: https://img.shields.io/npm/l/express.svg

config.go renamed to options.go

+63-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88
"github.com/go-redis/redis"
99
)
1010

11-
// Config Redis parameter options
12-
type Config struct {
11+
// Options Redis parameter options
12+
type Options struct {
1313
// The network type, either tcp or unix.
1414
// Default is tcp.
1515
Network string
@@ -68,7 +68,7 @@ type Config struct {
6868
TLSConfig *tls.Config
6969
}
7070

71-
func (o *Config) redisOptions() *redis.Options {
71+
func (o *Options) redisOptions() *redis.Options {
7272
return &redis.Options{
7373
Network: o.Network,
7474
Addr: o.Addr,
@@ -88,3 +88,63 @@ func (o *Config) redisOptions() *redis.Options {
8888
TLSConfig: o.TLSConfig,
8989
}
9090
}
91+
92+
// ClusterOptions are used to configure a cluster client and should be
93+
// passed to NewClusterClient.
94+
type ClusterOptions struct {
95+
// A seed list of host:port addresses of cluster nodes.
96+
Addrs []string
97+
98+
// The maximum number of retries before giving up. Command is retried
99+
// on network errors and MOVED/ASK redirects.
100+
// Default is 8.
101+
MaxRedirects int
102+
103+
// Enables read-only commands on slave nodes.
104+
ReadOnly bool
105+
// Allows routing read-only commands to the closest master or slave node.
106+
RouteByLatency bool
107+
// Allows routing read-only commands to the random master or slave node.
108+
RouteRandomly bool
109+
110+
// Following options are copied from Options struct.
111+
112+
OnConnect func(*redis.Conn) error
113+
114+
MaxRetries int
115+
MinRetryBackoff time.Duration
116+
MaxRetryBackoff time.Duration
117+
Password string
118+
119+
DialTimeout time.Duration
120+
ReadTimeout time.Duration
121+
WriteTimeout time.Duration
122+
123+
// PoolSize applies per cluster node and not for the whole cluster.
124+
PoolSize int
125+
PoolTimeout time.Duration
126+
IdleTimeout time.Duration
127+
IdleCheckFrequency time.Duration
128+
}
129+
130+
func (o *ClusterOptions) redisClusterOptions() *redis.ClusterOptions {
131+
return &redis.ClusterOptions{
132+
Addrs: o.Addrs,
133+
MaxRedirects: o.MaxRedirects,
134+
ReadOnly: o.ReadOnly,
135+
RouteByLatency: o.RouteByLatency,
136+
RouteRandomly: o.RouteRandomly,
137+
OnConnect: o.OnConnect,
138+
MaxRetries: o.MaxRetries,
139+
MinRetryBackoff: o.MinRetryBackoff,
140+
MaxRetryBackoff: o.MaxRetryBackoff,
141+
Password: o.Password,
142+
DialTimeout: o.DialTimeout,
143+
ReadTimeout: o.ReadTimeout,
144+
WriteTimeout: o.WriteTimeout,
145+
PoolSize: o.PoolSize,
146+
PoolTimeout: o.PoolTimeout,
147+
IdleTimeout: o.IdleTimeout,
148+
IdleCheckFrequency: o.IdleCheckFrequency,
149+
}
150+
}

redis.go

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package redis
2+
3+
import (
4+
"time"
5+
6+
"github.com/go-redis/redis"
7+
"github.com/json-iterator/go"
8+
"gopkg.in/oauth2.v3"
9+
"gopkg.in/oauth2.v3/models"
10+
"gopkg.in/oauth2.v3/utils/uuid"
11+
)
12+
13+
var (
14+
_ oauth2.TokenStore = &TokenStore{}
15+
jsonMarshal = jsoniter.Marshal
16+
jsonUnmarshal = jsoniter.Unmarshal
17+
)
18+
19+
// NewRedisStore create an instance of a redis store
20+
func NewRedisStore(opts *Options) *TokenStore {
21+
if opts == nil {
22+
panic("options cannot be nil")
23+
}
24+
return NewRedisStoreWithCli(redis.NewClient(opts.redisOptions()))
25+
}
26+
27+
// NewRedisStoreWithCli create an instance of a redis store
28+
func NewRedisStoreWithCli(cli *redis.Client) *TokenStore {
29+
return &TokenStore{
30+
cli: cli,
31+
}
32+
}
33+
34+
// NewRedisClusterStore create an instance of a redis cluster store
35+
func NewRedisClusterStore(opts *ClusterOptions) *TokenStore {
36+
if opts == nil {
37+
panic("options cannot be nil")
38+
}
39+
return NewRedisClusterStoreWithCli(redis.NewClusterClient(opts.redisClusterOptions()))
40+
}
41+
42+
// NewRedisClusterStoreWithCli create an instance of a redis cluster store
43+
func NewRedisClusterStoreWithCli(cli *redis.ClusterClient) *TokenStore {
44+
return &TokenStore{
45+
cli: cli,
46+
}
47+
}
48+
49+
type clienter interface {
50+
Get(key string) *redis.StringCmd
51+
TxPipeline() redis.Pipeliner
52+
Del(keys ...string) *redis.IntCmd
53+
Close() error
54+
}
55+
56+
// TokenStore redis token store
57+
type TokenStore struct {
58+
cli clienter
59+
}
60+
61+
// Close close the store
62+
func (s *TokenStore) Close() error {
63+
return s.cli.Close()
64+
}
65+
66+
// Create Create and store the new token information
67+
func (s *TokenStore) Create(info oauth2.TokenInfo) (err error) {
68+
ct := time.Now()
69+
jv, err := jsonMarshal(info)
70+
if err != nil {
71+
return
72+
}
73+
74+
pipe := s.cli.TxPipeline()
75+
if code := info.GetCode(); code != "" {
76+
pipe.Set(code, jv, info.GetCodeExpiresIn())
77+
} else {
78+
basicID := uuid.Must(uuid.NewRandom()).String()
79+
aexp := info.GetAccessExpiresIn()
80+
rexp := aexp
81+
82+
if refresh := info.GetRefresh(); refresh != "" {
83+
rexp = info.GetRefreshCreateAt().Add(info.GetRefreshExpiresIn()).Sub(ct)
84+
if aexp.Seconds() > rexp.Seconds() {
85+
aexp = rexp
86+
}
87+
pipe.Set(refresh, basicID, rexp)
88+
}
89+
90+
pipe.Set(info.GetAccess(), basicID, aexp)
91+
pipe.Set(basicID, jv, rexp)
92+
}
93+
94+
if _, verr := pipe.Exec(); verr != nil {
95+
err = verr
96+
}
97+
return
98+
}
99+
100+
// remove
101+
func (s *TokenStore) remove(key string) (err error) {
102+
_, verr := s.cli.Del(key).Result()
103+
if verr != redis.Nil {
104+
err = verr
105+
}
106+
return
107+
}
108+
109+
// RemoveByCode Use the authorization code to delete the token information
110+
func (s *TokenStore) RemoveByCode(code string) (err error) {
111+
err = s.remove(code)
112+
return
113+
}
114+
115+
// RemoveByAccess Use the access token to delete the token information
116+
func (s *TokenStore) RemoveByAccess(access string) (err error) {
117+
err = s.remove(access)
118+
return
119+
}
120+
121+
// RemoveByRefresh Use the refresh token to delete the token information
122+
func (s *TokenStore) RemoveByRefresh(refresh string) (err error) {
123+
err = s.remove(refresh)
124+
return
125+
}
126+
127+
func (s *TokenStore) getData(key string) (ti oauth2.TokenInfo, err error) {
128+
result := s.cli.Get(key)
129+
if verr := result.Err(); verr != nil {
130+
if verr == redis.Nil {
131+
return
132+
}
133+
err = verr
134+
return
135+
}
136+
iv, err := result.Bytes()
137+
if err != nil {
138+
return
139+
}
140+
var tm models.Token
141+
if verr := jsonUnmarshal(iv, &tm); verr != nil {
142+
err = verr
143+
return
144+
}
145+
ti = &tm
146+
return
147+
}
148+
149+
func (s *TokenStore) getBasicID(token string) (basicID string, err error) {
150+
tv, verr := s.cli.Get(token).Result()
151+
if verr != nil {
152+
if verr == redis.Nil {
153+
return
154+
}
155+
err = verr
156+
return
157+
}
158+
basicID = tv
159+
return
160+
}
161+
162+
// GetByCode Use the authorization code for token information data
163+
func (s *TokenStore) GetByCode(code string) (ti oauth2.TokenInfo, err error) {
164+
ti, err = s.getData(code)
165+
return
166+
}
167+
168+
// GetByAccess Use the access token for token information data
169+
func (s *TokenStore) GetByAccess(access string) (ti oauth2.TokenInfo, err error) {
170+
basicID, err := s.getBasicID(access)
171+
if err != nil || basicID == "" {
172+
return
173+
}
174+
ti, err = s.getData(basicID)
175+
return
176+
}
177+
178+
// GetByRefresh Use the refresh token for token information data
179+
func (s *TokenStore) GetByRefresh(refresh string) (ti oauth2.TokenInfo, err error) {
180+
basicID, err := s.getBasicID(refresh)
181+
if err != nil || basicID == "" {
182+
return
183+
}
184+
ti, err = s.getData(basicID)
185+
return
186+
}

token_test.go renamed to redis_test.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,24 @@ import (
44
"testing"
55
"time"
66

7-
"gopkg.in/go-oauth2/redis.v1"
7+
"gopkg.in/go-oauth2/redis.v3"
88
"gopkg.in/oauth2.v3/models"
99

1010
. "github.com/smartystreets/goconvey/convey"
1111
)
1212

13+
const (
14+
addr = "localhost:6379"
15+
db = 15
16+
)
17+
1318
func TestTokenStore(t *testing.T) {
1419
Convey("Test redis token store", t, func() {
15-
cfg := &redis.Config{
16-
Addr: "127.0.0.1:6379",
20+
opts := &redis.Options{
21+
Addr: addr,
22+
DB: db,
1723
}
18-
store, err := redis.NewTokenStore(cfg)
24+
store, err := redis.NewRedisStore(opts)
1925
So(err, ShouldBeNil)
2026

2127
Convey("Test authorization code store", func() {

0 commit comments

Comments
 (0)