zonyitoo / sysubbs-apiserver

API Server for SYSUBBS
2 stars 1 forks source link

加密认证流程 #1

Open zonyitoo opened 11 years ago

zonyitoo commented 11 years ago

登录认证流程

由于后台目前只能接受Plain Text认证,所以需要在认证流程开始前把数据加密,在服务器端解密。加密算法使用RSA128认证过程中传输的数据都放在Authorization头中传输过程中把Authorization头中信息使用base64.urlsafe_b64encode编码,流程如下:

前提:服务器生成PublicKey: PrivateKey对,存在配置文件中(固定)

  1. 客户端发起认证流程,向服务器请求server_publickey,并把自己的client_publickey发给服务器,请求格式为:
{ "client_publickey": "..." }

密钥使用PKCS#1格式传输。

  1. 服务器接收客户端请求,返回login_tokenserver_publickey,格式为
{
        "server_publickey": "...",
        "login_token": "550e8400-e29b-41d4-a716-446655440000",
}

状态码200,密钥使用PKCS#1格式传输。服务器生成一个UUID作为login_token,在Redis中储存键值对

"auth:login_token:[login_token]" : { "client_publickey": "..." }

其中[login_token]为生成的login_token,并设置过期时间为120分钟。即120分钟内必须完成这次登录流程,否则回到第1步。

  1. 客户端使用server_publickey对发送数据进行加密,发送数据格式为:
{
        "username": "",
        "password": "",
        "login_token": ""
        “nounce”: “单次唯一值(timestamp,服务端可以用次值作为初始的nounce值)”
}

其中login_token为第2步时从服务器得到。

  1. 服务器使用client_publickey加密回复数据,回复数据格式为:
{
        "access_token": "00000000-0000-0000-C000-000000000046",
        "expire": 134314132421,
}

服务器在此步生成一个UUID作为access_token,在Redis中储存键值对

"auth:access_token:[access_token]" :
    {
        "client_publickey": "...",
        "username": "...",
        "cookie": "..."
        "nounce": "上次客户端发送的nounce值,用于验证唯一性",
        "..." : "其余用户信息"
    }

其中[access_token]为生成的access_token,过期时间为30天。登录成功后,此次登录所使用的login_token即可销毁。

回复的body部分

{
    "success": true,
    "errmsg": "ok"
}

每次认证所使用的login_token都是不同的,服务器上会对它设置过期时间,若第3步中login_token已过期,则服务器回复:

{
    "success": false,
    "errmsg": "Login token expired."
}

状态码401,客户端重新发起流程即可。

{
    "success": false,
    "errmsg": ""
}

状态码为400,客户端需要重新发起流程。

加密传输

在成功获得access_token后,每次的请求都要把access_token使用server_publickey加密后置于HTTP头Authorization域中,格式为

{  "access_token": "...", "nounce": "timestamp, 发起请求的时间戳" }

服务器获取到access_token后,到Redis中寻找Keyauth:access_token:[access_token],其中[access_token]为获取到的access_token,若该Key存在,则用户已登录;否则用户未登录或认证已过期,统一返回未认证,而nounce发起请求时的时间戳,精确到秒。服务器将收到的时间戳与上一次的nounce值比较,若相等或小于上一次的nounce,则认为认证已过期。

若认证已过期,服务器会即时返回

{
    "success": false,
    "errmsg": "Unauthorized"
}

状态码401

ragnraok commented 11 years ago

其实我觉得没有必要设置token的过期时间,token一开始的目的只是为了防止多终端登录而已,我们只需要保证这个token的唯一性就OK了,然后在登录的时候根据这个token来生成一对密钥,然后服务端要保存好每个客户端对应的token,server_public_key,server_secret_key,client_public_key。

zonyitoo commented 11 years ago

服务端不必针对每个Client生成一对密钥的吧,而且client_public_key在登录完成之后没有必要再保存

如果token不过期,那样token被盗之后就坏了。过期之后让客户端重新登录也不是难事

ragnraok commented 11 years ago

针对每个Client生成一对密钥的目的不是为了应对多终端登录的情况么?不然多终端登录同一个用户所生成的密钥对都是一样的,这样设置个clientID就没有用了,如果你真的想要token过期的话,把token的过期时间设置成一个很大的数把,比如两个星期甚至一个月什么的,太短比如一天还不如不过期

zonyitoo commented 11 years ago

nounce值每次都不同,而服务器能做的就是验证其与上一次不同,那样攻击将非常简单:

  1. 窃取你这一次的Authorization
  2. 等客户端发出一次请求后,我再使用这个Authorization头去请求

如此即成功攻击。

zonyitoo commented 11 years ago

每次将timestamp都发送过去,然后服务器收到这个timestamp之后,跟服务器的当前timestamp比对,如果差值在一定范围内,则认证通过

这个方法最具可操作性,这个差值可设为一个商定的值,如3分钟。需要长时间的请求可压缩。

其实,如果上传图片的菊花转了2分钟还没反应的话,那已经是超越人类可忍受的极限了。

ragnraok commented 11 years ago

你所说的只检查上一次所会出现的问题,我也想过,有种办法就是记录所有的nounce的值,或者只记录一段请求之内的,可是对于我们这种bbs应用来说,我觉得就记录上一次就OK了,因为我们的应用估计还不用这么高的安全系数。

没有绝对的安全,我们只需要让破解变得稍微困难点就OK了 在没有https的情况下,可以认为无论怎样做都是可破解的。

zonyitoo commented 11 years ago

那就用类似将军令吧,相对于发随机字符串,开销比较小也比较容易实现。发Timestamp的方法那就真的要持续监听了,就只有几分钟的时间可以伪装。

还可以再加一层,在Redis储存上一次请求的Timestamp,每一次请求的时间戳要求一定要比上一次的时间戳大,那样即使监听到也没用了,因为我都用了一次了,你用不了了。

ragnraok commented 11 years ago

行,那就直接存timestamp吧,其实用将军令那个方法也会有问题啊,因为你在一个time range里面的值都是一样的,那我们就直接要求为timestamp咯,如果这次发送的值小于等于上次的就认为认证不通过了。我先改下协议

zonyitoo commented 11 years ago

我加了一句:用base64 encode一下传输的信息,因为rsa加密之后的信息好像不太适合放在Header直接传

ragnraok commented 11 years ago

好,我先改下auth.py吧,不过似乎现在github push不了。。

zonyitoo commented 11 years ago

已经改好了,你检查一下,你有testcase么?测一个

ragnraok commented 11 years ago

我把base64的decode和encode都重新放在__rsa128_encrypt_str__rsa128_decrypt_str这两个方法里面了,测试的话我晚点再撸吧。