goauth2由于作者使用诡计,导致如果goauth2启用redis缓存token与code后,导致android客户端不能在获取token了,这里就分享一下查这个bug的过程。
首先,bug的表现为获取token失败,具体的原因是因为当goauth2产生了token与code后,但是只能存储code而不能存储token,这样验证的时候由于无法从redis服务器获取token,所以就失败了,客户端就无法访问受保护的资源了。
func (ac *RedisAuthCache) RegisterAccessToken(clientID, scope, token string) (ttype string, expiry int64, err error) { vars := map[string]string{ "clientID": clientID, "scope": scope, } val, err := json.Marshal(vars) if err != nil { log.Println("Error Marshalling variables for Redis Set", err) return "", 0, err } key := tokenKey(token) err = ac.db.Set(key, val) if err != nil { log.Println("Error performing Redis-Set", err) return "", 0, err } valid, err := ac.db.Expire(key, int64(ac.TokenExpiry)) if err != nil { log.Println("Error performing Redis-Expire", err) return "", 0, err } else if !valid { err = errors.New("Invalid return from setting code expiration.") log.Println("Error performing Redis-Expire", err) return "", 0, err } return "bearer", ac.TokenExpiry, nil }
我最先怀疑的是到底是key不能设置还是token不能设置,因此做了几种尝试,ac.db.Set("token:257fc9823289eaabc4a28aff190b59b468bffb53", val)拿上一次产生的token三硬编码,发现可以设置;但如果函数调用时用同样的key就不能设置;于是当取回tokenKey后,我就破坏key,发现这个key就能设置,这彻底让我迷惑了,于是怀疑是goauth使用的redis有问题,又换了一种redis,可是问题依旧。无语了,灵感一现,注意到set了token后,就设置了Expire,会不会是这行代码导致无法设置token,果断注释代码,发现token就保存住了,一下子就猜测bug原因,会不会是设置了Expire,这个是负责token的超时,如果超时设置成了0,那么刚设置完token,又马上告诉redis token的超时为0,所以就消失了,再看源码
func NewRedisAuthCacheWithClient(addr string, dbnum int, pass string) *RedisAuthCache { return &RedisAuthCache{ db: redis.New(addr, dbnum, pass), CodeExpiry: 260, TokenExpiry: 0, } }
果然,TokenExpiry默认为0。
我为什么要特别说明这个问题呢,因为严格来说作者并没有写错任何一行代码,TokenExpiry给予一个保守的默认值,作者也没有错,可是这些没有错加在一起就成全了这个bug。这个bug非常有趣的地方在于,好比redis是个箱子,程序逻辑一半是往箱子里放苹果,而另一个逻辑就是吃掉这个苹果,可是我们却疑问苹果去那了?最后redis躺枪,我们怪redis这个怪物偷吃了苹果。