【学习笔记】原来SpringSession的SessionID是不能直接改的

今天在研究如何防止Cookie篡改,

防止篡改Cookie,最好的方法当然是利用非对称加密,通过私钥加密这个cookie的值,进行一个数字签名的大动作,然后再将cookie的值是签名的内容+要被签名的内容+公钥组成,再次返回的时候验证,就验证这个签名的内容解出来之后是否与要被签名的内容相同,相同则这个cookie没有被篡改

[Signature 类]

public final boolean verify(byte[] signature) throws SignatureException {
        if (state == VERIFY) {
            return engineVerify(signature);
        }
        throw new SignatureException("object not initialized for " +
                                     "verification");
    }

protected boolean engineVerify(byte[] sigBytes)
                throws SignatureException {
            try {
                byte[] out = cipher.doFinal(sigBytes);
                byte[] dataBytes = data.toByteArray();
                data.reset();
                return MessageDigest.isEqual(out, dataBytes);
            } catch (BadPaddingException e) {
                // e.g. wrong public key used
                // return false rather than throwing exception
                return false;
            } catch (IllegalBlockSizeException e) {
                throw new SignatureException("doFinal() failed", e);
            }
        }


 public static boolean isEqual(byte[] digesta, byte[] digestb) {
        if (digesta == digestb) return true;
        if (digesta == null || digestb == null) {
            return false;
        }

        int lenA = digesta.length;
        int lenB = digestb.length;

        if (lenB == 0) {
            return lenA == 0;
        }

        int result = 0;
        result |= lenA - lenB;

        // time-constant comparison
        for (int i = 0; i < lenA; i++) {
            // If i >= lenB, indexB is 0; otherwise, i.
            int indexB = ((i - lenB) >>> 31) * i;
            result |= digesta[i] ^ digestb[indexB];
        }
        return result == 0;
    }


可以看到下面验签(也就是sigBytes与data进行比较,data就是传入的原内容)的过程其实就是逐位做异或,只要有1位为1,result 就为1,然后就匹配失败

(异或 :1^0 = 1 , 1^1 = 0 , 0^1 = 1 , 0^0 = 0)

Java支持数字签名的算法有三种:

RSA 将两个大素数相乘十分容易,但反过来想要对它们的乘积进行因式分解会比较困难

DSA 只用签名而不能加密或解密,比RSA要快,主要就是两个素数公开,这样,当使用别人的p和q时,即使不知道私钥,你也能确认它们是否是随机产生的,还是作了手脚。

ECDSA 椭圆曲线签名算法:椭圆曲线上的离散对数问题,主要就是知道基点(椭圆曲线上的点)和某个数的乘积 以及 基点 很难推出 这个 某个数 来,难度相较于上面两个都要难很多;除了一个个遍历没有更快的办法,但如果是大数就会需要遍历很久

那么我利用用户信息对这个Cookie加密,那岂不是就安全了?

然后当我乐呵乐呵地尝试去修改sessionId的生成方法时,发现我使用的是Spring-session-redis,而在RedisHttpSessionConfiguration 中有个RedisOperationSessionRepository 的session仓库,

而这个RedisOperationSessionRepository 就是存放各种session 的地方

内部有个方法是CreateSession,这里就是session 生成的地方

public RedisOperationsSessionRepository.RedisSession createSession() {
        Duration maxInactiveInterval = Duration.ofSeconds(this.defaultMaxInactiveInterval != null ? (long)this.defaultMaxInactiveInterval : 1800L);
        RedisOperationsSessionRepository.RedisSession session = new RedisOperationsSessionRepository.RedisSession(maxInactiveInterval);
        session.flushImmediateIfNecessary();
        return session;
    }

关键是这里有个RedisSession 

而这个RedisSession 就是这个仓库中的session,也就是Spring-session 用来替换原生的session 的session

再往下看会发现,这个RedisSession是final修饰的!

 final class RedisSession implements Session {
        private final MapSession cached;

这意味着什么? 是的!final修饰的类不可继承,final修饰的方法不可重写

看到上面还有个MapSession 是干啥的?它不仅是private 修饰的还是final修饰的,private意味着即使redissession是可继承的,你也拿不到它,private修饰的成员变量是让你可以拥有,只是可以拥有,final 修饰意味着它 ! 也!不 ! 可! 以! 改!写!

再往下看:这个就是RedisOperationSessionRepository createSession时提到的方法!

 RedisSession(Duration maxInactiveInterval) {
            this((MapSession)(new MapSession()));
            this.cached.setMaxInactiveInterval(maxInactiveInterval);
            this.delta.put("creationTime", this.getCreationTime().toEpochMilli());
            this.delta.put("maxInactiveInterval", (int)this.getMaxInactiveInterval().getSeconds());
            this.delta.put("lastAccessedTime", this.getLastAccessedTime().toEpochMilli());
            this.isNew = true;
        }

内部是不是有个MapSession的实例?

跳到MapSession里看看,这个到底和Redissession 有什么关系?

public final class MapSession implements Session, Serializable {    
    public MapSession() {
        this(generateId());
    }
    

    public MapSession(String id) {
        this.sessionAttrs = new HashMap();
        this.creationTime = Instant.now();
        this.lastAccessedTime = this.creationTime;
        this.maxInactiveInterval = Duration.ofSeconds(1800L);
        this.id = id;
        this.originalId = id;
    }



    private static String generateId() {
        return UUID.randomUUID().toString();
    }
}

可以看到其实就是生成了一个Session,还包括SessionId的生成,也就是说这个RedisSession就是个大session,内部有个小Session,而这个session就是真正的Session,毕竟SessionID也就是在这生成的

最后我的修改SessionID的方法宣告失败~不过或许有其他可以修改SessionID的“曲线救国”的方式 还有待寻找

参考:

奇妙的安全旅行之DSA算法 - 知乎

ECDSA(椭圆曲线数字签名算法) - 简书

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

54V

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值