最近工作需要开发基于无状态的api,平时使用的是spring mvc,感觉没有找到相关比较好的实现的开源项目,所以就自己实现了一个来管理token。
逻辑:大概是当通过用户验证后创建一个用户tokenmodel并存入tokenmodel仓库。然后返回给用户有用户信息和当前时间轴生成的加密字符,当带有token的请求到来时,根据请求参数中的用户从仓库中拿出相关用户信息并根据之前生成token的算法生成字符串并与用户请求中的token对比。
代码:
/**
* TokenHandler
*
* 生产token和校验token
*
* @author doob
* @date 16/9/10
*/
public interface TokenHandler {
/**
* 添加一个用户的tokenmodel,并返回给用户token
* @param tokenModel
* @return 登录后反馈给用户端的token
*/
String addToken(TokenModel tokenModel);
/**
* 检查token是否有效
* @param userId 用户唯一标示
* @param token 请求上传token
* @return
*/
boolean checkToken(String userId , String token);
}
/**
* TokenModelRepository
*
* 隐藏tokenmodel储存的具体实现
*
* @author doob
* @date 16/9/9
*/
public interface TokenModelRepository {
/**
* 根据用户名获取tokenmodel
* @param userId
* @return
*/
TokenModel get(String userId);
/**
* 保存一个tokenmodel
*
* @param tokenModel
* @return
*/
boolean save(TokenModel tokenModel);
/**
* 删除一个用户tokenmodel
*
* @param userId
* @return
*/
TokenModel delete(String userId);
/**
* 设置token超时时间
*
* @param seconds 多少秒
* @return
*/
boolean setTokenTimeout(long seconds);
}
import com.mofang.common.Encryptor; //加密的具体算法类
import com.mofang.common.api.signature.Timestamp; //获取当前时间轴的类
/**
* TokenModel
*
* token实体类,用于储存token相关信息
*
* @author doob
* @date 16/9/9
*/
public class TokenModel {
private String userId;
private long createTime;
private long lastTime;
public TokenModel(String userId) {
this.userId = userId;
this.createTime = Timestamp.nowSeconds();
this.lastTime = this.createTime;
}
public String getToken(){
String original = userId+TokenPrimer.getPrimer(createTime)+createTime;
return Encryptor.input(original).md5().hex();
}
public TokenModel updateLastTime(){
this.lastTime = Timestamp.nowSeconds();
return this;
}
public long getLastTime() {
return lastTime;
}
public long getCreateTime() {
return createTime;
}
public void setCreateTime(long createTime) {
this.createTime = createTime;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}
/**
* TokenPrimer
*
* 用户token密文时使用的引子字符串
*
* @author doob
* @date 16/9/10
*/
public class TokenPrimer{
private static final String[] primers = new String[]{
"test1","test2","test3"
};
/**
* 根据用户tokenmodel创建时间决定使用那个引子
* @param createTime
* @return
*/
public static String getPrimer(long createTime){
int index = (int)createTime%primers.length;
return primers[index];
}
}
import com.mofang.common.api.signature.Timestamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* MemoryTokenModelRepository
*
* tokenmodel储存在内存中的的具体实现
*
* @author doob
* @date 16/9/9
*/
public class MemoryTokenModelRepository implements TokenModelRepository {
private static Logger logger = LoggerFactory.getLogger(TokenModelRepository.class);
private static volatile long timeout = 10*60; //token有限期
private static final long checkTime = 60;
private transient static final ConcurrentHashMap<String,TokenModel> tokenRepository = new ConcurrentHashMap<>();
static {
/**
* 定期检查token,实现token过期时间
*/
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate( () -> { //lambda
logger.debug("start scan user token , token repository length is {}",tokenRepository.size());
Iterator<Map.Entry<String,TokenModel>> iterator = tokenRepository.entrySet().iterator();
while (iterator.hasNext()){
long now = Timestamp.nowSeconds();
Map.Entry<String,TokenModel> token = iterator.next();
long tokenLastTime = token.getValue().getLastTime();
if (now - tokenLastTime > timeout){
String k = token.getKey();
TokenModel v = token.getValue();
tokenRepository.remove(k);
logger.debug("user[{}] token is timeout , user token lasttime is {}s , total user {}s",
k, v.getLastTime(), v.getLastTime() - v.getCreateTime());
}
}
}, checkTime, checkTime, TimeUnit.SECONDS);
}
/**
* 单列模式
*/
private MemoryTokenModelRepository() {
}
private static volatile MemoryTokenModelRepository memoryTokenModelRepository;
public static MemoryTokenModelRepository getMemoryTokenModelRepository(){
if (memoryTokenModelRepository == null) {
synchronized (MemoryTokenModelRepository.class) {
if (memoryTokenModelRepository == null) {
memoryTokenModelRepository = new MemoryTokenModelRepository();
}
}
}
return memoryTokenModelRepository;
}
@Override
public TokenModel get(String userId) {
return tokenRepository.get(userId);
}
@Override
public boolean save(TokenModel tokenModel) {
tokenRepository.put(tokenModel.getUserId(),tokenModel);
return true;
}
@Override
public TokenModel delete(String userId) {
return this.tokenRepository.remove(userId);
}
@Override
public boolean setTokenTimeout(long seconds) {
this.timeout = seconds;
return false;
}
}
/**
* DefaultTokenHandlerImpl
*
* @author doob
* @date 16/9/10
*/
public class DefaultTokenHandlerImpl implements TokenHandler {
static TokenModelRepository tokenModelRepository = MemoryTokenModelRepository.getMemoryTokenModelRepository();
public DefaultTokenHandlerImpl(){}
public DefaultTokenHandlerImpl(TokenModelRepository tmr){
this.tokenModelRepository = tmr;
}
@Override
public String addToken(TokenModel tokenModel) {
if (tokenModelRepository.get(tokenModel.getUserId()) == null){
tokenModelRepository.save(tokenModel);
}
return createTokenString(tokenModel);
}
@Override
public boolean checkToken(String userId, String token) {
if (userId == null ) return false;
TokenModel tokenModel = tokenModelRepository.get(userId);
boolean result = tokenModel == null ? false : createTokenString(tokenModel).equals(token);
if (result == true){
tokenModel.updateLastTime();
}
return result;
}
/**
* 生成密文token的逻辑
* @param tokenModel
* @return
*/
private String createTokenString(TokenModel tokenModel){
return tokenModel == null ? null : Encryptor.input(tokenModel.getUserId()
+ TokenPrimer.getPrimer(tokenModel.getCreateTime())
+ tokenModel.getCreateTime()).sha256().hex();
}
}
//spring 的 Interceptor
/**
* CheckApiTokenInterceptor
*
* 检查url以api开头的所有访问(除login)的token是否合法
*
* @author doob
* @date 16/9/10
*/
public class CheckApiTokenInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(this.getClass());
public static final String API_TOKEN_KEY = "api_token_key";
public static final String API_TOKEN_USER = "api_token_user";
@Autowired
TokenHandler th;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
if (request.getServletPath().contains("api/login")){
return true;
}
String agent = request.getHeader("User-Agent");
logger.debug(agent);
String token = request.getHeader(API_TOKEN_KEY);
String username = request.getHeader(API_TOKEN_USER);
if (token != null && th.checkToken(username,token)){
return true;
}else {
response.setHeader(HttpHeaders.CONTENT_TYPE,"application/json;charset=UTF-8");
response.getOutputStream().write(JSON.toJSONBytes(JSONResult.error("auth error")));
return false;
}
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
/**
* DefaultTokenHandlerBeans
* 把默认的实现注入spring,若有自己实现注入自己实现的bean
* @author doob
* @date 16/9/11
*/
@Component
public class DefaultTokenHandlerBeans {
@Bean
public TokenHandler getTokenHandler(){
return new DefaultTokenHandlerImpl();
}
@Bean
public TokenModelRepository getTokenModelRepository(){
return MemoryTokenModelRepository.getMemoryTokenModelRepository();
}
}
不知道这样子写token验证合不合适,希望高手指教^_^
版权声明:本文为fudali133原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。