項目總結30:token的實現以及原理(附源碼)


項目總結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;  
 }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM