[慕课电商项目] 03. token与横向越权以及对guava 缓存的改进

一、token 与横向越权

场景:
  当一个用户验证了密保问题,然后进行修改密码的时候,需要向后台传入用户名与新密码。此时,后台接受用户名和新密码,并在数据库中根据用户名来 update 新密码。但,后台仅仅是这样的话,普通的用户也可以通过接口向后台传其他的用户名,并带上自己设置的密码。

  用户与用户处在同一级别,他们之间不合规的操作,再加上后台逻辑的不完整,就容易导致用户与用户之间发生横向越权。

   如何解决?
  • 在用户验证问题,输入密保答案,提交到后台验证后。后台不单单只返回一个验证成功或失败,后台还要生成一个有 过期时间 的 uuid ,作为 token 保存起来,并返回给前台。
  • 前台把用户名、新密码以及从后台传回来的 token 一起再提交给后台,后台对比 token ,然后再对数据库进行操作。
   如何实现 ?

   比较容易理解的做法就是:在用户名、密码、答案验证通过后,把 username 和生成的 token 做成 map(key-value) 并保存起来。

   但是这个 map 保存在哪里比较合适呢?因为这个 token 要设置一个存活时间,在存活时间内才有效,否则,token 要是一直有效的话就可以一直通过验证,成功回答一次问题就一直能改密码了。所以,这个 一次性 的 token 放在数据库中显然是不合适的。

   最好的做法是放在缓存中。

二、guava 缓存及改进

1.关于Guava缓存的简单介绍:


2.原项目代码中的缓存类 TokenCache 及需要改进的点
(1) 原代码 :
public class TokenCache {

    private static Logger logger = LoggerFactory.getLogger(TokenCache.class);

    public static final String TOKEN_PREFIX = "token_";

    //LRU算法
    private static LoadingCache<String,String> localCache = CacheBuilder.newBuilder()
    .initialCapacity(1000).maximumSize(10000).expireAfterAccess(12, TimeUnit.HOURS)
            .build(new CacheLoader<String, String>() {
                //默认的数据加载实现,当调用get取值的时候,如果key没有对应的值,就调用这个方法进行加载.
                @Override
                public String load(String s) throws Exception {
                    return "null";
                }
            });

    public static void setKey(String key,String value){
        localCache.put(key,value);
    }

    public static String getKey(String key){
        String value = null;
        try {
            value = localCache.get(key);
            if("null".equals(value)){
                return null;
            }
            return value;
        }catch (Exception e){
            logger.error("localCache get error",e);
        }
        return null;
    }
}
(2) 需要改进的点 :
  • 重写的加载函数没有必要
       原来的写法是:重写的加载函数在查找不到该 key 时,返回 “null” ,注意不是 null,是字符串 “null”,默认的方法是直接返回 null。
       我们仔细看下他的逻辑,在返回字符串 “null” 作为 value 后,再用 “null”.equals(value), 如果 true 的话,再返回空指针 null,否则返回value。从这里我们就可以看到了,默认的获取 value 的函数没必要重写,我们直接返回从缓存中获取到的 value,并直接 return value 即可。这和原代码重写了获取 value 函数的最终结果是一样的。
  • guava的回收
    • expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。
    • expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收。

逻辑 :
   在 service 层,我们在验证 username question answer 后生成一个 uuid 作为 token ,并把(username,token)put 到 guava 缓存中,然后返回给用户浏览器。用户携带着 username newPassword token 来重置密码,我们首先从 guava 缓存中获取 username 对应的 token,对比两个 token 然后决定是否在数据库中更新。

   所以,在整个过程中,token 先被 put,然后 get,以后就不会再使用了。如果我们定的回收时间段为30分钟,那么采用 expireAfterAccess 的话,在被 get 后还将继续在缓存中保留30分钟,这和我们的预期设想是不一样的。如果我们采用 expireAfterWrite ,那么 token 在被 put 进缓存后30分钟就将被回收。这两个做法明显第二种更符合我们的预期。所以,我们需要把原项目中使用的 expireAfterAccess 改为 expireAfterWrite。

   此外,还有更进一步的做法:通过此 token 成功修改了密码之后,就可以把此 token 直接清除掉,这样就完全符合我们的预期了。

改进后的代码:
public class TokenCache {

    private static Logger logger = LoggerFactory.getLogger(TokenCache.class);


    /**
     * 添加缓存的前缀
     */
    public static final String TOKEN_PREFIX = "token_username_";

    private static LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder()
            //初始化的容量
            .initialCapacity(1000)
            /*最大缓存的数目,当缓存中的数目逼近这个数值时,
             guava会使用LRU算法来清理(Least Recently Used 最近最少使用)*/
            .maximumSize(10000)
            //在被写入缓存30分钟后将会被回收掉
            .expireAfterWrite(30, TimeUnit.MINUTES)
            //这里使用默认的实现,当查不到该key对应的value时,返回null
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String s) throws Exception {
                    return null;
                }
            });

    /**
     * description: 把传来的 用户名 和 token 插入到缓存中
     *
     * @param key 用户名
     * @param value token
     * @return void
     */
    public static void put(String key, String value) {
        loadingCache.put(key + TokenCache.TOKEN_PREFIX, value);
    }

    /**
     * description: 根据用户名 获取对应的token
     *
     * @param key 用户名
     * @return java.lang.String
     */
    public static String get(String key) {
        String value = null;
        try {
            value = loadingCache.get(key + TokenCache.TOKEN_PREFIX);
        } catch (Exception e) {
            logger.error("method: get() : " + e);
        }
        return value;
    }

    /**
     * 描述:在确定该key对应的token不必再用后, 就清除掉
     *
     * @param key 用户名作为key
     * @return void
     */
    public static void invalidateToken(String key) {
        try {
            loadingCache.invalidate(key + TokenCache.TOKEN_PREFIX);
        } catch (Exception e) {
            logger.error("TokenCache.invalidateToken Execption",e);
        }
    }
}
3.关于guava 缓存的深入认识 :
4. 最后的总结 :

   目前我的并发编程基本零基础,在对 guava 缓存的分析中,有刷新、加载、回收机制,这些对于数据的处理非常重要。但是因为这些涉及到并发编程,我甚至连一个测试类都写不出来。

   虽然我有想深入了解代码背后的逻辑的想法,甚至会设想出各种情况,但是现在自己的水平还远不够,只能浅尝辄止。接下来做完这个项目之后,要把重心放在基础知识上,毕竟 crud 是做不完的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,112评论 18 139
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,050评论 8 265
  • 跳台阶问题 题目描述: 一个台阶总共有 n 级,如果一次可以跳 1 级,也可以跳 2 级。求总共有多少种跳法,并分...
    MinoyJet阅读 1,219评论 0 0
  • 早上起晚,匆匆洗漱完,还好我动作快,按照平时的时间出门了,坐上公车,和往常一样,看一看想看的文章,想想今天都29号...
    二豆君阅读 799评论 0 1
  • 不知道为什么我就想去大城市受苦受累。 想满足自己虚荣心罢了!!
    shyla_7阅读 148评论 0 0