Skip to content

Commit

Permalink
add redis cluster support
Browse files Browse the repository at this point in the history
  • Loading branch information
LyricTian committed Sep 7, 2018
1 parent de39c2c commit d8a260f
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 175 deletions.
15 changes: 15 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
language: go
sudo: false
go_import_path: gopkg.in/go-oauth2/redis.v3
go:
- 1.9
services:
- redis-server
before_install:
- go get -t -v ./...

script:
- go test -race -coverprofile=coverage.txt -covermode=atomic

after_success:
- bash <(curl -s https://codecov.io/bash)
35 changes: 19 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
# Redis Storage for OAuth 2.0
# Redis Storage for [OAuth 2.0](https://github.com/go-oauth2/oauth2)

> Based on the redis token storage
[![License][License-Image]][License-Url]
[![ReportCard][ReportCard-Image]][ReportCard-Url]
[![GoDoc][GoDoc-Image]][GoDoc-Url]
[![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]

## Install

``` bash
$ go get -u -v gopkg.in/go-oauth2/redis.v1
$ go get -u -v gopkg.in/go-oauth2/redis.v3
```

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

import (
"gopkg.in/go-oauth2/redis.v1"
"gopkg.in/go-oauth2/redis.v3"
"gopkg.in/oauth2.v3/manage"
)

func main() {
manager := manage.NewDefaultManager()

// use redis token store
manager.MustTokenStorage(redis.NewTokenStore(&redis.Config{
manager.MustTokenStorage(redis.NewRedisStore(&redis.Options{
Addr: "127.0.0.1:6379",
DB: 15,
}))

// ...
// use redis cluster store
// redis.NewRedisClusterStore()
}
```

Expand All @@ -39,9 +38,13 @@ func main() {
Copyright (c) 2016 Lyric
```

[License-Url]: http://opensource.org/licenses/MIT
[License-Image]: https://img.shields.io/npm/l/express.svg
[ReportCard-Url]: https://goreportcard.com/report/github.com/go-oauth2/redis
[ReportCard-Image]: https://goreportcard.com/badge/github.com/go-oauth2/redis
[GoDoc-Url]: https://godoc.org/github.com/go-oauth2/redis
[GoDoc-Image]: https://godoc.org/github.com/go-oauth2/redis?status.svg
[Build-Status-Url]: https://travis-ci.org/go-oauth2/redis
[Build-Status-Image]: https://travis-ci.org/go-oauth2/redis.svg?branch=master
[codecov-url]: https://codecov.io/gh/go-oauth2/redis
[codecov-image]: https://codecov.io/gh/go-oauth2/redis/branch/master/graph/badge.svg
[reportcard-url]: https://goreportcard.com/report/gopkg.in/go-oauth2/redis.v3
[reportcard-image]: https://goreportcard.com/badge/gopkg.in/go-oauth2/redis.v3
[godoc-url]: https://godoc.org/gopkg.in/go-oauth2/redis.v3
[godoc-image]: https://godoc.org/gopkg.in/go-oauth2/redis.v3?status.svg
[license-url]: http://opensource.org/licenses/MIT
[license-image]: https://img.shields.io/npm/l/express.svg
66 changes: 63 additions & 3 deletions config.go → options.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"github.com/go-redis/redis"
)

// Config Redis parameter options
type Config struct {
// Options Redis parameter options
type Options struct {
// The network type, either tcp or unix.
// Default is tcp.
Network string
Expand Down Expand Up @@ -68,7 +68,7 @@ type Config struct {
TLSConfig *tls.Config
}

func (o *Config) redisOptions() *redis.Options {
func (o *Options) redisOptions() *redis.Options {
return &redis.Options{
Network: o.Network,
Addr: o.Addr,
Expand All @@ -88,3 +88,63 @@ func (o *Config) redisOptions() *redis.Options {
TLSConfig: o.TLSConfig,
}
}

// ClusterOptions are used to configure a cluster client and should be
// passed to NewClusterClient.
type ClusterOptions struct {
// A seed list of host:port addresses of cluster nodes.
Addrs []string

// The maximum number of retries before giving up. Command is retried
// on network errors and MOVED/ASK redirects.
// Default is 8.
MaxRedirects int

// Enables read-only commands on slave nodes.
ReadOnly bool
// Allows routing read-only commands to the closest master or slave node.
RouteByLatency bool
// Allows routing read-only commands to the random master or slave node.
RouteRandomly bool

// Following options are copied from Options struct.

OnConnect func(*redis.Conn) error

MaxRetries int
MinRetryBackoff time.Duration
MaxRetryBackoff time.Duration
Password string

DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration

// PoolSize applies per cluster node and not for the whole cluster.
PoolSize int
PoolTimeout time.Duration
IdleTimeout time.Duration
IdleCheckFrequency time.Duration
}

func (o *ClusterOptions) redisClusterOptions() *redis.ClusterOptions {
return &redis.ClusterOptions{
Addrs: o.Addrs,
MaxRedirects: o.MaxRedirects,
ReadOnly: o.ReadOnly,
RouteByLatency: o.RouteByLatency,
RouteRandomly: o.RouteRandomly,
OnConnect: o.OnConnect,
MaxRetries: o.MaxRetries,
MinRetryBackoff: o.MinRetryBackoff,
MaxRetryBackoff: o.MaxRetryBackoff,
Password: o.Password,
DialTimeout: o.DialTimeout,
ReadTimeout: o.ReadTimeout,
WriteTimeout: o.WriteTimeout,
PoolSize: o.PoolSize,
PoolTimeout: o.PoolTimeout,
IdleTimeout: o.IdleTimeout,
IdleCheckFrequency: o.IdleCheckFrequency,
}
}
186 changes: 186 additions & 0 deletions redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package redis

import (
"time"

"github.com/go-redis/redis"
"github.com/json-iterator/go"
"gopkg.in/oauth2.v3"
"gopkg.in/oauth2.v3/models"
"gopkg.in/oauth2.v3/utils/uuid"
)

var (
_ oauth2.TokenStore = &TokenStore{}
jsonMarshal = jsoniter.Marshal
jsonUnmarshal = jsoniter.Unmarshal
)

// NewRedisStore create an instance of a redis store
func NewRedisStore(opts *Options) *TokenStore {
if opts == nil {
panic("options cannot be nil")
}
return NewRedisStoreWithCli(redis.NewClient(opts.redisOptions()))
}

// NewRedisStoreWithCli create an instance of a redis store
func NewRedisStoreWithCli(cli *redis.Client) *TokenStore {
return &TokenStore{
cli: cli,
}
}

// NewRedisClusterStore create an instance of a redis cluster store
func NewRedisClusterStore(opts *ClusterOptions) *TokenStore {
if opts == nil {
panic("options cannot be nil")
}
return NewRedisClusterStoreWithCli(redis.NewClusterClient(opts.redisClusterOptions()))
}

// NewRedisClusterStoreWithCli create an instance of a redis cluster store
func NewRedisClusterStoreWithCli(cli *redis.ClusterClient) *TokenStore {
return &TokenStore{
cli: cli,
}
}

type clienter interface {
Get(key string) *redis.StringCmd
TxPipeline() redis.Pipeliner
Del(keys ...string) *redis.IntCmd
Close() error
}

// TokenStore redis token store
type TokenStore struct {
cli clienter
}

// Close close the store
func (s *TokenStore) Close() error {
return s.cli.Close()
}

// Create Create and store the new token information
func (s *TokenStore) Create(info oauth2.TokenInfo) (err error) {
ct := time.Now()
jv, err := jsonMarshal(info)
if err != nil {
return
}

pipe := s.cli.TxPipeline()
if code := info.GetCode(); code != "" {
pipe.Set(code, jv, info.GetCodeExpiresIn())
} else {
basicID := uuid.Must(uuid.NewRandom()).String()
aexp := info.GetAccessExpiresIn()
rexp := aexp

if refresh := info.GetRefresh(); refresh != "" {
rexp = info.GetRefreshCreateAt().Add(info.GetRefreshExpiresIn()).Sub(ct)
if aexp.Seconds() > rexp.Seconds() {
aexp = rexp
}
pipe.Set(refresh, basicID, rexp)
}

pipe.Set(info.GetAccess(), basicID, aexp)
pipe.Set(basicID, jv, rexp)
}

if _, verr := pipe.Exec(); verr != nil {
err = verr
}
return
}

// remove
func (s *TokenStore) remove(key string) (err error) {
_, verr := s.cli.Del(key).Result()
if verr != redis.Nil {
err = verr
}
return
}

// RemoveByCode Use the authorization code to delete the token information
func (s *TokenStore) RemoveByCode(code string) (err error) {
err = s.remove(code)
return
}

// RemoveByAccess Use the access token to delete the token information
func (s *TokenStore) RemoveByAccess(access string) (err error) {
err = s.remove(access)
return
}

// RemoveByRefresh Use the refresh token to delete the token information
func (s *TokenStore) RemoveByRefresh(refresh string) (err error) {
err = s.remove(refresh)
return
}

func (s *TokenStore) getData(key string) (ti oauth2.TokenInfo, err error) {
result := s.cli.Get(key)
if verr := result.Err(); verr != nil {
if verr == redis.Nil {
return
}
err = verr
return
}
iv, err := result.Bytes()
if err != nil {
return
}
var tm models.Token
if verr := jsonUnmarshal(iv, &tm); verr != nil {
err = verr
return
}
ti = &tm
return
}

func (s *TokenStore) getBasicID(token string) (basicID string, err error) {
tv, verr := s.cli.Get(token).Result()
if verr != nil {
if verr == redis.Nil {
return
}
err = verr
return
}
basicID = tv
return
}

// GetByCode Use the authorization code for token information data
func (s *TokenStore) GetByCode(code string) (ti oauth2.TokenInfo, err error) {
ti, err = s.getData(code)
return
}

// GetByAccess Use the access token for token information data
func (s *TokenStore) GetByAccess(access string) (ti oauth2.TokenInfo, err error) {
basicID, err := s.getBasicID(access)
if err != nil || basicID == "" {
return
}
ti, err = s.getData(basicID)
return
}

// GetByRefresh Use the refresh token for token information data
func (s *TokenStore) GetByRefresh(refresh string) (ti oauth2.TokenInfo, err error) {
basicID, err := s.getBasicID(refresh)
if err != nil || basicID == "" {
return
}
ti, err = s.getData(basicID)
return
}
14 changes: 10 additions & 4 deletions token_test.go → redis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,24 @@ import (
"testing"
"time"

"gopkg.in/go-oauth2/redis.v1"
"gopkg.in/go-oauth2/redis.v3"
"gopkg.in/oauth2.v3/models"

. "github.com/smartystreets/goconvey/convey"
)

const (
addr = "localhost:6379"
db = 15
)

func TestTokenStore(t *testing.T) {
Convey("Test redis token store", t, func() {
cfg := &redis.Config{
Addr: "127.0.0.1:6379",
opts := &redis.Options{
Addr: addr,
DB: db,
}
store, err := redis.NewTokenStore(cfg)
store, err := redis.NewRedisStore(opts)
So(err, ShouldBeNil)

Convey("Test authorization code store", func() {
Expand Down
Loading

0 comments on commit d8a260f

Please sign in to comment.