基于token的登录管理(多设备登录、单设备登录) 您所在的位置:网站首页 京东一个账号两个设备消息会同步吗 基于token的登录管理(多设备登录、单设备登录)

基于token的登录管理(多设备登录、单设备登录)

2024-07-10 18:17| 来源: 网络整理| 查看: 265

详情参见: https://gitee.com/xxssyyyyssxx/token

不管是客户端接口还是网页H5接口,一般我们都需要登录验证,即要求所有的接口访问都必须在登录之后,以确认身份,防止非法调用。一般的流程都是登录的时候返回一个代表此登录的token,以后所有接口都带上此token,在所有接口调用之前拦截验证,一般都是通过AOP或者一个Filter、拦截器来实现。而退出的时候调用接口将此token删除即可。一般地,为了对接口侵入最小,能做到统一处理,可以将此token放在header中。token一般都会设置一个有效期,过期了直接提示调用者需要登录以控制条转到登录页面引导登录。

服务端设计:

/** * token管理器 * @author xiongshiyan */ public interface TokenManager { /** * 生成token * @param m 实体 * @return token值 */ String createToken(M m); /** * 根据token获取 * @param token token * @return 根据token获取的实体 */ M findByToken(String token); /** * 更新token的过期 * @param token token */ void updateExpires(String token); /** * 删除 * @param token token * @return 删除是否成功 */ boolean deleteToken(String token); /** * 产生token * @param m 实体 * @return token */ String getToken(M m); /** * 踢人 * @param m 实体 * @param newToken 新token * @param doMore 还要做的事情 */ default void kickingOld(M m, String newToken, Runnable doMore){ kickingOld(m , newToken); if(null != doMore){ doMore.run(); } } /** * 踢人 * @param m 实体 * @param newToken token */ void kickingOld(M m, String newToken); }

M代表登录实体,也可以是能代表登录人的唯一标识,使用泛型指定。createToken用于生成并保存token,findByToken用于通过token找到登录实体,updateExpires用于更新token的过期时间,deleteToken用于删除token(登录退出的时候),getToken生成token字符串。一般验证token是几乎每个接口都会用,所以必须保证速度,可以采用redis来保存。

/** * 基于redis的token管理器基类 * @author xiongshiyan at 2018/8/15 , contact me with email [email protected] or phone 15208384257 */ public abstract class AbstractRedisTokenManager implements TokenManager { protected RedisUtil redisUtil; public AbstractRedisTokenManager(RedisUtil redisUtil){ this.redisUtil = redisUtil; } @SuppressWarnings("unchecked") @Override public M findByToken(String token) { if(null == token){ return null; } Object o = redisUtil.get(token); if(null == o){ return null; } return (M) o; } @Override public boolean deleteToken(String token){ Object o = redisUtil.get(token); if(null == o){ return false; } redisUtil.del(token); return true; } }

此基类实现了一些公共的方法,继承此类实现剩余的方法即可。

1.允许多设备登录的实现。这种情形下,token可以随意生成,只要保证不重复即可。

2.只允许单设备登录,即所谓的登录踢人,在登录的时候验证是够已经登录,如果已经登录就给出提示或者直接踢人登录。这种情形下,需要根据登录的标识确认是否已经登录,有两种解决方式,一种是在保存token的时候,既保存token》》实体的关系,还需要保存实体标识与token的关系;另外一种解决方式是token与实体标识强相关,根据实体标识即可算出token。

以下的实现既支持多设备登录又支持单设备登录,设置multi即可。

package cn.palmte.anfang.service.token.impl; import cn.palmte.anfang.redis.RedisUtil; import cn.palmte.anfang.service.token.AbstractRedisTokenManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * API接口token管理器主要逻辑实现【支持多设备登录、踢人】 * @author xiongshiyan at 2018/8/15 , contact me with email [email protected] or phone 15208384257 */ @SuppressWarnings("unchecked") public abstract class BaseMultiTokenManager extends AbstractRedisTokenManager { private static final Logger logger = LoggerFactory.getLogger(BaseMultiTokenManager.class); private long apiExpires; /** * 多设备登录 ? */ private boolean multi = false; public BaseMultiTokenManager(RedisUtil redisUtil, long apiExpires , boolean multi) { super(redisUtil); this.apiExpires = apiExpires; this.multi = multi; } @Override public String createToken(M m) { String token = getToken(m); redisUtil.set(token , m , apiExpires); logger.info("createToken token = {} , m={}" , token , m.toString()); return token; } @Override public void updateExpires(String token){ if(null == token){ return; } redisUtil.expire(token , apiExpires); if(multi){ return; } //单设备需要额外同步更新 M m = (M) redisUtil.get(token); if(null != m){ redisUtil.expire(key(m) , apiExpires); } } @Override public boolean deleteToken(String token) { //单设备需要额外删除 M m = (M) redisUtil.get(token); boolean b = super.deleteToken(token); if(multi){ return b; } String key = key(m); if(null != m){ redisUtil.del(key); } logger.info("deleteToken token = {} , key={}" , token , key); return b; } @Override public void kickingOld(M m, String newToken) { if(multi){ throw new IllegalStateException("多设备登录情况不允许踢人"); } //1.删除以前登录人的token,以前的人就通不过校验 String withPrefix = key(m); String oldToken = (String) redisUtil.get(withPrefix); if(null == oldToken){ return; } deleteToken(oldToken); //2.重新建立实体和新token的联系 //单设备需要额外保存标识和token的关系 logger.info("kickingOld key={} , value={}" , withPrefix , newToken); redisUtil.set(withPrefix, newToken , apiExpires); } /** * 根据实体或者标识获取key * @param m 实体或者标识 * @return 返回保存标识和token关系的key */ abstract protected String key(M m); }

直接使用实体比如手机号的

/** * 客户端API接口token管理器【支持多设备登录】 * @author xiongshiyan at 2018/8/15 , contact me with email [email protected] or phone 15208384257 */ public class ApiTokenManager extends BaseMultiTokenManager { private String apiTokenPrefix; public ApiTokenManager(RedisUtil redisUtil, String apiTokenPrefix, long apiExpires , boolean multi) { super(redisUtil, apiExpires, multi); this.apiTokenPrefix = apiTokenPrefix; } public ApiTokenManager(RedisUtil redisUtil, String apiTokenPrefix, long apiExpires) { super(redisUtil , apiExpires , false); this.apiTokenPrefix = apiTokenPrefix; } @Override public String getToken(String m){ return apiTokenPrefix + "-" + nowStr() + CommonUtil.randomString(16); } private String nowStr(){ return DatetimeUtils.toStr(new Date() , DatetimeUtils.SDF_DATETIME_SHORT); } @Override protected String key(String s) { return apiTokenPrefix + "-" + s; } }

用实体的情况

package cn.palmte.anfang.service.token.impl.model; import cn.palmte.anfang.model.Member; import cn.palmte.anfang.redis.RedisUtil; import cn.palmte.anfang.service.token.impl.BaseMultiTokenManager; import top.jfunc.common.datetime.DatetimeUtils; import top.jfunc.common.utils.CommonUtil; import java.util.Date; /** * 客户端API接口token管理器【支持多设备登录】 * @author xiongshiyan at 2018/8/15 , contact me with email [email protected] or phone 15208384257 */ public class ApiTokenManager extends BaseMultiTokenManager { private String apiTokenPrefix; public ApiTokenManager(RedisUtil redisUtil, String apiTokenPrefix, long apiExpires , boolean multi) { super(redisUtil, apiExpires, multi); this.apiTokenPrefix = apiTokenPrefix; } public ApiTokenManager(RedisUtil redisUtil, String apiTokenPrefix, long apiExpires) { super(redisUtil, apiExpires, false); this.apiTokenPrefix = apiTokenPrefix; } @Override public String getToken(Member m){ return apiTokenPrefix + "-" + nowStr() + CommonUtil.randomString(16); } private String nowStr(){ return DatetimeUtils.toStr(new Date() , DatetimeUtils.SDF_DATETIME_SHORT); } @Override protected String key(Member member){ return apiTokenPrefix + "-" + member.getPhone(); } }

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有