项目总结30:token的实现以及原理(附源码)

什么是token

  一句话概括:token是身份令牌,用于客户端请求服务端时提供身份证明;

  再具体点(以APP为例):

    • 用户通过账号和密码登陆APP后,服务端会返回一个参数给客户端,假设服务端很粗暴的将userId(即数据库中的用户id)传给用户,用户接下请求接口,每次只需要传这个userId给服务端,就这一证明自己的身份,这样是可以实现(身份证明)功能;
    • 但是,直接将userId暴露给用户值非常危险的,相当于每次别人问你谁,你就把身份证给他看一样;
    • 于是,一个更合理的方案出现了,用户登陆APP时,服务端传一个根据userId进行加密得到的字符串给用户即可,这个字符串就是token,用户每次请求接口只需要那这个加密过的字符串就可以了,既能证明身份,又安全保密;

token的实现原理

  1. 生成token:用户请求登陆接口,从数据库正确获取userId后,将userId加密生成token,我们以token为key,userId为value,将数据保存在redis中(或者数据库中),然后将token返给用户;
  2. 校验token:用户请求需要身份校验的接口时,直接将第1步返回的token,作为参数传给服务端;服务端拿到token后,去redis(或者数据库)中根据token找到对应的userId,即完成了身份校验;
  3. 更新token:redis(或者数据库)记录token,是有时效性的(为了安全起见), 每次校验token成功时,需要刷新一下这个时间;有点类似于屏保,2分钟内不触屏就自动上锁,但是一旦2分钟内触屏了,就重新开始计时;
  4. 备注:实际在redis中保存token时,并不会直接以userId为value,而是将token和userId封装成一个对象,作为value保存(参考AccessToken类);

源码解析

  AccessToken类:普通POJO

package com.hs.web.common.token;

import java.io.Serializable;

import com.hs.common.util.encrypt.EncryptUtil;

/**
 * Token WMS管理实体
 * 
 * @comment
 * @update
 */
public class AccessToken implements Serializable {

    private static final long serialVersionUID = 4759692267927548118L;

    private String token;// AccessToken字符串

    private String userId;

    
    public AccessToken(){
    }
    
    public AccessToken(String userId){
        this.userId = userId;
        this.token = EncryptUtil.encrypt(userId);
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

}

 

  RedisKeySuffixEnum:枚举类,如果一个项目中需要不同类型的token,可以在枚举类中枚举每一个token在保存时的key的前缀和value的有效时间

 

package com.hs.web.model;

public enum RedisKeySuffixEnum {

    REGISTER_MOBILE_CODE("fmk_register_mobile_code_", 30 * 60), // register_mobile_code_{mobile}格式

    CHANGE_PASSWORD_CODE("fmk_change_password_code_", 30 * 60), //change_password_code_{mobile}格式


    USER_TOKEN("fmk_user_token_", 60 * 60 *24 * 7); // user_token_{mobile}格式
    

    private String key;
    private long expireTime; 
    
    private RedisKeySuffixEnum(String key, long expireTime){
        this.key = key;
        this.expireTime = expireTime;
    }
    
    public String getKey(){
        return key;
    }

    public long getExpireTime() {
        return expireTime;
    }
    

}

 

  AccessTokenManager:单例模式,用于管理token,包括创建、查找、更新token三个功能

package com.hs.web.common.token;

import java.lang.reflect.Field;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

import com.hs.common.util.redis.RedisUtil;
import com.hs.web.model.RedisKeySuffixEnum;

/**
 * 用户Token管理工具
 * 
 * @comment
 * @update
 */
public class AccessTokenManager {
    
    private static AccessTokenManager instance = new AccessTokenManager();
    
    private AccessTokenManager(){
    }
    
    //单例模式
    public static AccessTokenManager getInstance(){
        return instance;
    }

    //获取token:根据token获取用户id
    public AccessToken getToken(String token){
        if(!StringUtils.isBlank(token) && 
            RedisUtil.exists(RedisKeySuffixEnum.USER_TOKEN.getKey() + token)){
            //AccessToken accessToken = (AccessToken) RedisUtil.get(RedisKeySuffixEnum.USER_TOKEN.getKey() + token);
            AccessToken accessToken = convertAccessToken(RedisUtil.get(RedisKeySuffixEnum.USER_TOKEN.getKey() + token));
            return accessToken;
        }
        return null;
    }
    
    //根据用户id生成token,并保存在redis中
    public String putToken(String userId){
        AccessToken token = new AccessToken(userId);
        RedisUtil.set(RedisKeySuffixEnum.USER_TOKEN.getKey() + token.getToken(), 
                token, RedisKeySuffixEnum.USER_TOKEN.getExpireTime());
        
        return token.getToken();
    }
    //每次用户使用某一个token时,自动刷新该token的有效时间
    public void updateToken(String token){
        if(!StringUtils.isBlank(token) && 
            RedisUtil.exists(RedisKeySuffixEnum.USER_TOKEN.getKey() + token)){
            AccessToken assessToken = (AccessToken) RedisUtil.get(RedisKeySuffixEnum.USER_TOKEN.getKey() + token);
            if(assessToken == null){
                return;
            }
            RedisUtil.set(RedisKeySuffixEnum.USER_TOKEN.getKey() + token, token, 
                    RedisKeySuffixEnum.USER_TOKEN.getExpireTime());
        }
    }
    

    /**
     * 反射转换:解决因类加载器不同导致的转换异常
     * com.hs.web.common.token.AccessToken cannot be cast to com.hs.web.common.token.AccessToken
     * 
     */
    private AccessToken convertAccessToken(Object redisObject){
        AccessToken at = new AccessToken();
        at.setToken(ReflectUtils.getFieldValue(redisObject,"token")+"");
        at.setUserId(ReflectUtils.getFieldValue(redisObject,"userId")+"");
        return at;
    }

}
//本类私用反射方法—————目的是为了解决因为redis和java虚拟机类加载机制不一样,而引起的对同一类的引用却无法转换的异常;
class ReflectUtils{
 public static Object getFieldValue(Object obj, String fieldName){
     if(obj == null){  
         return null ;  
     }    
     Field targetField = getTargetField(obj.getClass(), fieldName);  
       
     try {  
         return FieldUtils.readField(targetField, obj, true ) ;  
     } catch (IllegalAccessException e) {  
         e.printStackTrace();  
     }   
     return null ;
 }
 
 public static Field getTargetField(Class<?> targetClass, String fieldName) {  
     Field field = null;  

     try {  
         if (targetClass == null) {  
             return field;  
         }  

         if (Object.class.equals(targetClass)) {  
             return field;  
         }  

         field = FieldUtils.getDeclaredField(targetClass, fieldName, true);  
         if (field == null) {  
             field = getTargetField(targetClass.getSuperclass(), fieldName);  
         }  
     } catch (Exception e) {  
     }  

     return field;  
 }
}

 

posted on 2019-06-09 22:36  我不吃番茄  阅读(1851)  评论(0编辑  收藏  举报