基于token的登录管理(多设备登录、单设备登录)_多端登录 token管理-程序员宅基地

技术标签: 登录  JavaEE  SpringBoot扩展  token管理器  token  

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

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

服务端设计:

/**
 * token管理器
 * @author xiongshiyan
 */
public interface TokenManager<M> {
    /**
     * 生成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<M> implements TokenManager<M> {
    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<M> extends AbstractRedisTokenManager<M> {
    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<String> {
    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<Member> {
    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();
    }
}

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xxssyyyyssxx/article/details/83743739

智能推荐

(6)学tp5之响应_tp 立即响应-程序员宅基地

文章浏览阅读815次。在手册中没有见到专门讲响应的地方。只有手册-》架构-》API友好 和 手册-》控制器-》Rest控制器中有一点。tp5中的响应,其实就是方便我们输出各种格式1、路由(用的是强制模式)2、控制器中的代码3、json和jsonp的区别,用dump()打印;jsonp和json不一样的地方,用红框圈出来了..._tp 立即响应

为什么每个扇区的容量一致-程序员宅基地

文章浏览阅读599次。如图。磁盘,每个扇区的容量是一致的

小甲鱼|Python002|用Python设计第一个游戏| 课后测试题及答案_小甲鱼python 作业-程序员宅基地

文章浏览阅读227次。0. 什么是BIF?BIF 就是 Built-in Functions,内置函数。为了方便程序员快速编写脚本程序(脚本就是要编程速度快快快!!!),Python 提供了非常丰富的内置函数,我们只需要直接调用即可,例如 print() 的功能是“打印到屏幕”,input() 的作用是接收用户输入(注:Python3 用 input() 取代了 Python2 的 raw_input(),用法如有不懂请看视频讲解)。太多BIF学不过来怎么办?看不懂英文说明怎么办?Python3的资料太少怎么办?没事,有_小甲鱼python 作业

关于ETTERCAP0.8.3报错问题_ettercap 删除注释后 报错-程序员宅基地

文章浏览阅读2.2k次。今天使用了一下ETTERCAP0.8.3发现了一个问题root@debian:/home/debian# sudo ettercap -Gettercap 0.8.2 copyright 2001-2015 Ettercap Development TeamNo protocol specifiedGTK+ failed to initialize. Is X running?搜索了很久,终于在git上找到了解决方案:就是输入xhost local:root这里要注意一点,不_ettercap 删除注释后 报错

Github连接不上问题_fastgithub 找不到任何可成功连接的ip-程序员宅基地

文章浏览阅读3.7k次,点赞3次,收藏4次。文章目录解决方法过程中问题解决方法在电脑的 C:\Windows\System32\drivers\etc 文件夹下修改或者添加140.82.114.3 github.com(ip地址需要查询)199.232.69.194 github.global.ssl.fastly.net(ip地址需要查询)查询github.com地址: https://www.ipaddress.com/ 过程中问题问题:host文件修改保存后只能另存为解决方法:修改并保存host文件..._fastgithub 找不到任何可成功连接的ip

mybatis注解方式和xml方式的使用-程序员宅基地

文章浏览阅读1.7k次。Mybatis的注解方式的使用: 1 package com.hikvision.building.cloud.neptune.device.biz.domain.mapper; 2 3 import java.util.Date; 4 import java.util.List; 5 6 import org.apache...._mybatis注解中使用xml@update xml

随便推点

浅谈获取url传递的参数值的几种方式_<%=传获取的参数-程序员宅基地

文章浏览阅读1.3w次。以下内容是在开发中本人经常使用的方式,现总结如下:jsp页面中: //el表达式 获取请求参数var id = ${param.id}; var id = &lt;%=request.getParameter("id")%&gt; html页面中: //使用js 获取参数值function getQueryVariable(va..._<%=传获取的参数</div>

solr 实现数据的删除和修改_solr 修改数据 语句-程序员宅基地

文章浏览阅读9.3k次。修改主方法public int saveContent(String enterpriseId, String enterpriseName, String lableType, String resouce, String pubDate,String content) {int state = 0;LBHttpSolrServer server = SolrUtil.g_solr 修改数据 语句

惊人一谈:5G出现将让Wi-Fi产业消亡?-程序员宅基地

文章浏览阅读122次。前几日,网上流出两张微信截图,有行业人士说5G会让Wi-Fi死掉,消息一出,引起了行业内的热议。众所周知5G速度很快,但是怎么突然又牵扯出与Wi-Fi的恩怨呢?行业人士说5G出现,Wi-Fi必死。后续手机将不需要Wi-Fi芯片安装在上面。这些观点论据是否能站得住脚?包括乐鑫科技、全志科技、博通集成这一类有Wi-Fi芯片的公司未来会不会受到5G的冲击?据了解,该资深市场总监在电话里说,目前家庭的Wi..._5g wi-fi产业

什么是灰度发布?能给技术开发带来什么价值_灰度开发-程序员宅基地

文章浏览阅读2.1k次。什么是灰度发布呢?要想了解这个问题就要先明白什么是灰度。灰度从字面意思理解就是存在于黑与白之间的一个平滑过渡的区域,所以说对于互联网产品来说,上线和未上线就是黑与白之分,而实现未上线功能平稳过渡的一种方式就叫做灰度发布。在了解了什么是灰度发布的定义以后,就可以来了解一下灰度发布的具体操作方法了。_灰度开发

k8s资源指标API及metrics-server资源监控-程序员宅基地

文章浏览阅读896次。简述:在k8s早期版本中,对资源的监控使用的是heapster的资源监控工具。但是从 Kubernetes 1.8 开始,Kubernetes 通过 Metrics API 获取资源使用指标,例如容器 CPU 和内存使用情况。这些度量指标可以由用户直接访问,例如通过使用kubectl top 命令,或者使用集群中的控制器。 Metrics API: 通过 Metrics ...

ES Java API - 获取索引下数据量_java操作es查询索引中的doc数量-程序员宅基地

文章浏览阅读1.5w次。需求 获取ES某个索引下的数据总量代码示例引包import net.sf.json.JSONObject;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.elasticsearch.action.ActionFuture;import org.e_java操作es查询索引中的doc数量

推荐文章

热门文章

相关标签