技术标签: 登录 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();
}
}
文章浏览阅读815次。在手册中没有见到专门讲响应的地方。只有手册-》架构-》API友好 和 手册-》控制器-》Rest控制器中有一点。tp5中的响应,其实就是方便我们输出各种格式1、路由(用的是强制模式)2、控制器中的代码3、json和jsonp的区别,用dump()打印;jsonp和json不一样的地方,用红框圈出来了..._tp 立即响应
文章浏览阅读599次。如图。磁盘,每个扇区的容量是一致的
文章浏览阅读227次。0. 什么是BIF?BIF 就是 Built-in Functions,内置函数。为了方便程序员快速编写脚本程序(脚本就是要编程速度快快快!!!),Python 提供了非常丰富的内置函数,我们只需要直接调用即可,例如 print() 的功能是“打印到屏幕”,input() 的作用是接收用户输入(注:Python3 用 input() 取代了 Python2 的 raw_input(),用法如有不懂请看视频讲解)。太多BIF学不过来怎么办?看不懂英文说明怎么办?Python3的资料太少怎么办?没事,有_小甲鱼python 作业
文章浏览阅读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 删除注释后 报错
文章浏览阅读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
文章浏览阅读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
文章浏览阅读1.3w次。以下内容是在开发中本人经常使用的方式,现总结如下:jsp页面中: //el表达式 获取请求参数var id = ${param.id}; var id = <%=request.getParameter("id")%> html页面中: //使用js 获取参数值function getQueryVariable(va..._<%=传获取的参数</div>
文章浏览阅读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 修改数据 语句
文章浏览阅读122次。前几日,网上流出两张微信截图,有行业人士说5G会让Wi-Fi死掉,消息一出,引起了行业内的热议。众所周知5G速度很快,但是怎么突然又牵扯出与Wi-Fi的恩怨呢?行业人士说5G出现,Wi-Fi必死。后续手机将不需要Wi-Fi芯片安装在上面。这些观点论据是否能站得住脚?包括乐鑫科技、全志科技、博通集成这一类有Wi-Fi芯片的公司未来会不会受到5G的冲击?据了解,该资深市场总监在电话里说,目前家庭的Wi..._5g wi-fi产业
文章浏览阅读2.1k次。什么是灰度发布呢?要想了解这个问题就要先明白什么是灰度。灰度从字面意思理解就是存在于黑与白之间的一个平滑过渡的区域,所以说对于互联网产品来说,上线和未上线就是黑与白之分,而实现未上线功能平稳过渡的一种方式就叫做灰度发布。在了解了什么是灰度发布的定义以后,就可以来了解一下灰度发布的具体操作方法了。_灰度开发
文章浏览阅读896次。简述:在k8s早期版本中,对资源的监控使用的是heapster的资源监控工具。但是从 Kubernetes 1.8 开始,Kubernetes 通过 Metrics API 获取资源使用指标,例如容器 CPU 和内存使用情况。这些度量指标可以由用户直接访问,例如通过使用kubectl top 命令,或者使用集群中的控制器。 Metrics API: 通过 Metrics ...
文章浏览阅读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数量