rest token的一个实现

最近工作需要开发基于无状态的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版权协议,转载请附上原文出处链接和本声明。