基于Redis实现的单态登录-附代码

1.现有情况

我们有客户端PC端和App端。

  • 1.App端要实现单设备登录,App端账号在一台设备上登录后,其他App端该账号token自动失效。

  • 2.PC端,一个客户端只能登录一个账号。

  • 3.根据不同的业务和登录方式获取不同的token失效时间

2.redis设计

2.1 Redis Key|Value设计

RedisKey.REDIS_JOIN :定义的特殊链接字符常量。

KeyValue备注
RedisKey.CLIENT_USER_TOKEN, clientId + RedisKey.REDIS_JOIN + accountIdtoken根据clientId和accountId存放token
RedisKey.CLIENT_TOKEN+ RedisKey.REDIS_JOIN +clientIdtoken方便清除在同一个客户端登录的其他账号token
RedisKey.TOKEN_ACCOUNT_ID+ RedisKey.REDIS_JOIN +tokenaccountId根据token存放accountId
RedisKey.ACCOUNT_LOGIN_CLIENTS+ RedisKey.REDIS_JOIN +accountIdclientId集合存放accountId登陆的客户端id(App端传来的client保存时增加后缀clientIdSuffix)

2.2 流程思路

/**
 * 生成token, 种缓存并清除统一账号下已登录的token
 *
 * @param signInType
 * @param accountId
 * @param clientId
 * @param clientIdSuffix 用于区分系统(APP或PC,PC默认为null)
 * @return
 */
方法:generateToken(int signInType, String accountId, String clientId, String business, String clientIdSuffix)

//清除在同一个客户端登录的其他账号token
RedisKey.CLIENT_TOKEN, clientId                                                                                                                                           tokenInSameClientKey

//根据其他账号token清除存放的accountId
RedisKey.TOKEN_ACCOUNT_ID, token                                                                           

//根据其他账号accountId和clientId清除存放的token
RedisKey.CLIENT_USER_TOKEN, clientId + RedisKey.REDIS_JOIN + accountId                                                                   redisExecutorKey



clientIdSuffix   !=null     说明是App端传来的clientId,需要通过清除accountId清除App其他设备的token

RedisKey.ACCOUNT_LOGIN_CLIENTS, accountId      拿到account登录的设备id集合:clientIds


遍历clientIds——>item,判断item是否包含clientIdSuffix前缀(item.contains(clientIdSuffix))
包含的话,根据clientId和acountId删除旧的token

// set new token
redis.setString(redisExecutorKey, token, timeout);
redis.setString(tokenInSameClientKey, token, timeout);
redis.setString(redis.generateKey(RedisKey.TOKEN_ACCOUNT_ID, token), accountId, timeout);

//添加clientId
recordLoginClients(accountId, clientId, timeout, clientIdSuffix);

3代码实现

import org.apache.commons.lang3.StringUtils;
import java.util.UUID;
public class ModelUtil {
     //用于生成token
    public static String uuid() {
        return UUID.randomUUID().toString().replace("-", "");
    }

}
@Service
public class TokenDomainService {
    @Resource
    private RedisClientService redis;

    @Autowired
    MerchantConfiguration merchantConfiguration;

    /**
     * 生成token, 种缓存并清除统一账号下已登录的token
     *
     * @param signInType
     * @param accountId
     * @param clientId
     * @param clientIdSuffix 用于区分系统(APP或PC,PC默认为null)
     * @return
     */
    public String generateToken(int signInType, String accountId, String clientId, String business, String clientIdSuffix) {
        //生成token
        String token = ModelUtil.uuid();
        //根据不同的业务和登录方式获取失效时间
        int timeout = getTokenTimeout(signInType, business);

        // 清除在同一个客户端登录的其他账号token
        Key tokenInSameClientKey = redis.generateKey(RedisKey.CLIENT_TOKEN, clientId);

        String tokenInSameClient = redis.get(tokenInSameClientKey);
        if (!StringUtils.isEmpty(tokenInSameClient)) {
            redis.delete(tokenInSameClientKey);

            Key redisExecutorForOtherTokenKey = redis.generateKey(RedisKey.TOKEN_ACCOUNT_ID, tokenInSameClient);

            String prevAccountId = redis.get(redisExecutorForOtherTokenKey);

            if (!StringUtils.isEmpty(prevAccountId)) {
                redis.delete(redisExecutorForOtherTokenKey);
                redis.delete(redis.generateKey(RedisKey.CLIENT_USER_TOKEN, clientId + RedisKey.REDIS_JOIN + prevAccountId));
            }
        }

        // remove old token for same clientId&accountId
        Key redisExecutorKey = redis.generateKey(RedisKey.CLIENT_USER_TOKEN, clientId + RedisKey.REDIS_JOIN + accountId);

        String oldToken = redis.get(redisExecutorKey);
        if (!StringUtils.isEmpty(oldToken)) {
            redis.delete(redis.generateKey(RedisKey.TOKEN_ACCOUNT_ID, oldToken));
            LogUtils.INFO.info("单态登录清除token:{}", oldToken);
        }


        //remove same clientType&accountId
        if (StringUtils.equals(clientIdSuffix, ClientTypeEnum.MERCHANT_APP.getValue())) {
            clearAllToken(accountId, clientIdSuffix);
        }


        // set new token
        redis.setString(redisExecutorKey, token, timeout);
        redis.setString(tokenInSameClientKey, token, timeout);
        redis.setString(redis.generateKey(RedisKey.TOKEN_ACCOUNT_ID, token), accountId, timeout);


        recordLoginClients(accountId, clientId, timeout, clientIdSuffix);
        return token;
    }

    /**
     * 根据业务线及登陆类型区分token有效期
     *
     * @param signInType
     * @param business
     * @return
     */
    public int getTokenTimeout(int signInType, String business) {
        AuthTokenConfig authTokenConfig = merchantConfiguration.getAuthTokenConfig();
        int timeout;

        if (SignInTypeEnum.PASSWORD.getValue().equals(signInType)) {
            timeout = authTokenConfig.getPwdTokenTimeout();
            if (!StringUtils.isEmpty(business) && MapUtils.isNotEmpty(authTokenConfig.getBusinessPwdTokenTimeout())) {
                if (authTokenConfig.getBusinessPwdTokenTimeout().get(business) != null) {
                    timeout = authTokenConfig.getBusinessPwdTokenTimeout().get(business);
                }
            }
        } else {
            timeout = authTokenConfig.getSmsTokenTimeout();
            if (!StringUtils.isEmpty(business) && MapUtils.isNotEmpty(authTokenConfig.getBusinessSmsTokenTimeout())) {
                if (authTokenConfig.getBusinessSmsTokenTimeout().get(business) != null) {
                    timeout = authTokenConfig.getBusinessSmsTokenTimeout().get(business);
                }
            }
        }

        return timeout;
    }

    /**
     * 记录账号当前登录的 clientId, 方便修改登录信息时候 清除所有登录端的token
     *
     * @param accountId
     * @param clientId
     * @param timeout
     * @param clientIdSuffix (不同端添加不同后缀,PC端默认不添加)
     */
    private void recordLoginClients(String accountId, String clientId, int timeout, String clientIdSuffix) {
        if (StringUtils.isNotEmpty(clientIdSuffix)) {
            clientId = clientId + MerchantConsts.splitStr.underLine + clientIdSuffix;
        }
        Key key = redis.generateKey(RedisKey.ACCOUNT_LOGIN_CLIENTS, accountId);
        redis.sadd(key, clientId);
        redis.expire(redis.generateKey(RedisKey.ACCOUNT_LOGIN_CLIENTS, accountId), timeout);
    }

    /**
     * 从token中解析账号id
     *
     * @param token
     * @return
     */
    public String extractAccountId(String token) {
        Key key = redis.generateKey(RedisKey.TOKEN_ACCOUNT_ID, token);
        return redis.get(key);
    }

    /**
     * 清除账号在所有端登录的token
     *
     * @param accountId
     */
    public void clearAllToken(String accountId, String clientIdSuffix) {
        Key key = redis.generateKey(RedisKey.ACCOUNT_LOGIN_CLIENTS, accountId);
        Set<String> clientIds = redis.smembers(key);
        if (CollectionUtils.isEmpty(clientIds)) {
            return;
        }

        //根据端类型删token
        if (StringUtils.isNotEmpty(clientIdSuffix)) {
            clientIds.forEach(clientIdWithSuffix -> {

                if (StringUtils.isNotEmpty(clientIdWithSuffix) && clientIdWithSuffix.contains(clientIdSuffix)) {

                    String[] clientIdArray = clientIdWithSuffix.split(MerchantConsts.splitStr.underLine);
                    if (clientIdArray.length == 2) {
                        String clientId = clientIdArray[0];
                        Key oldTokenKey = redis.generateKey(RedisKey.CLIENT_USER_TOKEN, clientId + RedisKey.REDIS_JOIN + accountId);
                        String oldToken = redis.get(oldTokenKey);
                        if (StringUtils.isNotEmpty(oldToken)) {
                            redis.delete(redis.generateKey(RedisKey.TOKEN_ACCOUNT_ID, oldToken));
                        }
                        redis.delete(oldTokenKey);
                    }
                }

            });
        } else {
            //不区分端类型删token
            clientIds.forEach(clientId -> {
                if (clientId.contains(ClientTypeEnum.MERCHANT_APP.getValue())) {
                    String[] clientIdArray = clientId.split(MerchantConsts.splitStr.underLine);
                    if (clientIdArray.length == 2) {
                        String realClientId = clientIdArray[0];
                        Key oldTokenKey = redis.generateKey(RedisKey.CLIENT_USER_TOKEN, realClientId + RedisKey.REDIS_JOIN + accountId);
                        String oldToken = redis.get(oldTokenKey);
                        if (StringUtils.isNotEmpty(oldToken)) {
                            redis.delete(redis.generateKey(RedisKey.TOKEN_ACCOUNT_ID, oldToken));
                        }
                        redis.delete(oldTokenKey);
                    }
                } else {
                    Key oldTokenKey = redis.generateKey(RedisKey.CLIENT_USER_TOKEN, clientId + RedisKey.REDIS_JOIN + accountId);
                    String oldToken = redis.get(oldTokenKey);
                    if (StringUtils.isNotEmpty(oldToken)) {
                        redis.delete(redis.generateKey(RedisKey.TOKEN_ACCOUNT_ID, oldToken));
                    }
                    redis.delete(oldTokenKey);
                }
            });
        }


    }

    /**
     * 清除token,账号登出调用
     *
     * @param token
     * @param clientId
     * @param accountId
     */
    public void clearToken(String token, String clientId, String accountId) {
        redis.delete(redis.generateKey(RedisKey.TOKEN_ACCOUNT_ID, token));
        redis.delete(redis.generateKey(RedisKey.CLIENT_USER_TOKEN, clientId + RedisKey.REDIS_JOIN + accountId));
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值