zoukankan      html  css  js  c++  java
  • 项目总结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;  
     }
    }
  • 相关阅读:
    增加一个基类没有的方法
    修改或加强基类的属性
    linux rm命令详解
    Apache的配置httpd.conf杂谈
    解决 You don't have permission to access / on this server. 错误的另一方法
    ubuntu下成功配置LAMP 并安装PHPMyadmin
    C#连接SQLite的方法
    内存使用大比拼 之 String – StringBuffer
    非常喜欢Gedit,绝不逊色EditPlus!
    关于内存
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/10995141.html
Copyright © 2011-2022 走看看