Token认证机制
Token的优势
- 可实现多种客户端的统一会话管理
- 降低与其业务系统的耦合
- 很容易加入第三方认证的支持
生成Token
服务器端 : 按规则生成token信息缓存在redis中, 同时返回给客户端
客户端: 请求登录成功保存token, 并附加在下次请求的http信息头中, 以供服务器验证
置换Token
设置定期刷新会话, 防止被恶意盗取
Token数据结构
客户端标识-USERCODE - USERID -CREATIONDATE-RONDEM(6位)
Token示例
- TokenService
@Service
public class TokenServiceImpl implements TokenService{
@Resource
private RedisAPI redisAPI;
/**
* token: 客户端标识-USERCODE - USERID -CREATIONDATE-RONDEM(6位)
* @param userAgent
* @param admin
* @return
*/
@Override
public String generateToken(String userAgent, WebAdmin admin) {
StringBuilder sb = new StringBuilder();
UserAgent agent = UserAgent.parseUserAgentString(userAgent);
//移动设备
if(agent.getOperatingSystem().isMobileDevice()) {
sb.append("MOBILE-");
} else
sb.append("PC-");
sb.append(MD5Util.getMD5(admin.getLoginCode(), 16)).append("-");
sb.append(admin.getId().toString()).append("-");
sb.append(new SimpleDateFormat("yyyyMMddHHmmsss").format(new Date())).append("-");
sb.append(MD5Util.getMD5(userAgent, 6));
return sb.toString();
}
/**
* 需要针对不同的客户端 需要不同的过期处理
* @param token
* @param admin
*/
@Override
public void save(String token, WebAdmin admin) {
if(token.startsWith("PC-")) {
redisAPI.set(token, JSONObject.toJSONString(admin), 2 * 60 * 60);
} else {
//移动端不需要过期
redisAPI.set(token, JSONObject.toJSONString(admin));
}
}
@Override
public boolean validate(String userAgent, String token) {
if(!redisAPI.keyExist(token)) {
return false;
}
String md5Agent = token.split("-")[4];
//判断是不是同一个浏览器
if(!md5Agent.equals(MD5Util.getMD5(userAgent, 6))) {
return false;
}
//不需要考虑过期时间 一方面如果是pc端过期 redis会直接清理 如果是移动端也不用考虑
return true;
}
}
注: 需要引入1个依赖: 至于redis和MD5,你使用token肯定是引入了,这里就不赘述了
<dependency> <groupId>nl.bitwalker</groupId> <artifactId>UserAgentUtils</artifactId> <version>1.2.4</version> </dependency>
验证Token
客户端: 将token附加到请求的header中
服务端: 从header取出token, 将其和redis中key-value进行比对
代码在validate方法中
删除Token
直接手动调用redis的del的方法即可
重置Token
这个场景是, 用户每次请求业务时,都应该校验其token的有效期, 如果约定时间有效(一般是半个小时) 那么你可以访问, 如果无效那么 我需要置换token 具体细节如下
//保护期30分钟
private long protectedTime = 30 * 60 * 1000;
//延迟
private int delayTime = 2 * 60;
@Override
public String reload(String userAgent, String token) throws Exception {
//1验证token是否有效
if (!redisAPI.keyExist(token)) {
throw new Exception("token 无效!");
}
//2.是否到了置换时间
String genDateStr = token.split("-")[3];
Date genDate = new SimpleDateFormat("yyyyMMddHHmmsss").parse(genDateStr);
//经过的时间
long pass = Calendar.getInstance().getTimeInMillis() - genDate.getTime();
//判断是否在保护期之内
if(pass < protectedTime) {
throw new Exception("token置换保护期, 无法置换! 剩余" + (protectedTime - pass ) / 1000);
}
//生成新的tokne
WebAdmin admin = JSONObject.parseObject(redisAPI.get(token), WebAdmin.class);
String newToken = generateToken(userAgent, admin);
//3.老的token 延迟2分钟过期
redisAPI.set(token, redisAPI.get(token), delayTime);
//4.新的token保存
save(newToken, admin);
return newToken;
}
一般置换Token是放在定时任务中
说说心里话
最近又到了秋招的环节了, 笔者作为一名即将步入职场的新盆友, 正在疯狂吸收"全(栈)站"的知识, 虽然有些疲倦 但是每次掌握一个技能然后分享给大家这个阶段还是挺快乐的, 希望我的童鞋们多多捧场 让我更有信心更新下去, I need You!