单点登录是一个我们平常经常能见到的功能,就是一个账号同一时间只能在一台设备上登录,后登录的设备会挤掉先登录的设备,比如我们的手机qq,如果在自己手机上登录了自己的qq账号,然后另一部手机上接着登录自己的qq,那么自己手机上的qq就会下线。
实现单点登录
实现单点登录的思路很简单,就是一个账号对应的token同一时间只有一个生效,也就是说每次用户登录除了生成一个token保存起来,还要删除掉之前的token。
- 用户登录,服务器生成token保存至redis(设置有效时间),并将token返回给前端,用户之后的每次请求需要携带token。
- 在拦截器中校验请求头中的token,判断token是否有效,每一次有效的请求都刷新token的有效时间。
- 同一个账号再次被登录时,删除之前的token,并生成新的token重复1操作。
步骤看起来都很简单,但有一个问题需要注意,每一次校验token是需要去redis中查询的,也就是说设置的key应该为token,value为userId(或者其它唯一标识),那么这个时候如何在登录时做到删除当前的token呢?如果只是有一个token-id的对应关系好像确实没办法获取到该账号当前token,所以还需要一个id-token的对应关系,可以直接通过id拿到token。
这里附上我实现token操作部分的代码,拦截器部分可以有很多种实现方式,我是用的echo框架的中间件来实现,如果需要的话评论区说一声我贴上来。
CreateToken(key string, expiration time.Duration) string
:创建token,我在创建token的时候就会去判断是否存在旧的token然后删除。
ValidToken(token string) (id int)
:验证token是否有效,0为失效。
DelToken(token string)
:删除redis中的token信息。
/**
* @Author lanni
* @Description 创建token
* @Date 2020/6/18 15:50
* @Param key:登陆凭证(id),expiration:过期时间
* @return token:令牌
**/
func CreateToken(key string, expiration time.Duration) string {
//生成token,这里生成token可以自己选择一个算法来生成,只要是长度足够的随机字符串就ok
token := jwt.GenerateToken(key)
//redis记录该账号登录状态
client := redis.Open()
defer client.Close()
client.Set(token, key, expiration)
//获取原token
oldToken := client.Get(key)
//如果原token不为空则删除
if oldToken != "" {
client.Del(oldToken)
}
//保存或更新该账号的当前登录token,用于单点登录
client.Set(key, token, expiration)
return token
}
/**
* @Author lanni
* @Description 验证token是否过期
* @Date 2020/6/18 16:03
* @Param
* @return 返回token对应的id,查询为空时返回0
**/
func ValidToken(token string) (id int) {
//查询
client = redis.Open()
defer client.Close()
id, err := strconv.Atoi(client.Get(token))
if id != 0 && err == nil {
return
}
return 0
}
/**
* @Author lanni
* @Description 删除sso中的token信息
* @Date 2020/7/9 15:32
* @Param
* @return
**/
func DelToken(token string) {
client := redis.Open()
defer client.Close()
//获取原token对应的id
id := client.Get(token)
//token时限设置为1s
client.Set(token, id, time.Second)
client.Set(id, token, time.Second)
}