token验证机制及实现.md
在中心服务器模式下的客户端认证
又发现了一项之前在工行工作期间缺失的技术,到了互联网企业工作后,技术栈大大不同。http协议是无状态的,但是网站登录要求前后几次请求能被标志为同一个人发起,工行是在服务器端管理,负担重;互联网企业是在客户端自行管理,体现了一定的p2p思想
认证类型
-
每次请求都带上用户名和密码
-
优点:实现简单
-
缺点:频繁传输容易有安全风险;不能给第三方
-
缺点:问题是密码怎么存?如果每次都要输入用户名密码,用户体验也不好;如果存下来,有安全隐患
-
-
服务器集中维护session;客户端有对应的cookie保存session id
- 优点是一次登录后就不用输入密码了;但是缺点很多,首先是服务器内存压力大,要来维护session,其次是不能动态扩展,另外是存在浪费,比如用于直接关闭了浏览器后只能超时退出
-
token验证机制,反客为主,由客户端来管理和验证session
-
服务器压力小,客户来了给看一下,不用给设置15分钟超时之类的,不必时时刻刻管着客户的事情
-
相当于颁发了一张证书给客户端,里面规定了这个客户有效登录和超时时间,并有服务器对此的签名,然后服务器就不管了
-
这篇写的很好
sessionId方式和token方式区别
从认证看中心化和去中心化
-
工行的认证是完全的中心化
-
移动支付的方式是集群化,体现了一定的去中心化
-
以太坊的认证是完全的去中心化!!
JWT
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名
- 头部
{
"typ": "JWT",
"alg": "HS256" //签名算法
}
- 载荷
{ "iss": "Online JWT Builder", //签发者issue
"iat": 1416797419, //签发时间
"exp": 1448333419, //过期时间
"aud": "www.example.com", //接受方
"sub": "aaa@qq.com", //订阅者
"Email": "aaa@qq.com",
"Role": [ "Manager", "Project Administrator" ]
}
- 签名
有私钥的,对如上两个部分签名
用户登录时,提供了密码(口令),这个口令理论上只有用户本人知道,所以完成了认证,其实也可以用指纹
JDK 中提供了非常方便的 BASE64Encoder 和 BASE64Decoder,用它们可以非常方便的完成基于 BASE64 的编码和解码
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import io.jsonwebtoken.*;
import java.util.Date;
//Sample method to construct a JWT
private String createJWT(String id, String issuer, String subject, long ttlMillis) {
//The JWT signature algorithm we will be using to sign the token
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//We will sign our JWT with our ApiKey secret
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(apiKey.getSecret());
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setId(id) //会话id
.setIssuedAt(now) //签发时间
.setSubject(subject) //签发给谁
.setIssuer(issuer) //签发者
.signWith(signatureAlgorithm, signingKey);
//if it has been specified, let's add the expiration
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
//Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}
基于 sesssion :不带身份证,只脑袋记住身份证号码,到火车站报一下身份证号码,从后台调出照片比对一下。
基于token :随身带着身份证,以后你只要出示这张出示,带pos机本地检查一下(不需要连接后台),我就知道你一定是自己人。 就是身份证丢失了很麻烦
攻击防范
-
窃听:只能用https了,最新趋势是用硬件加速https
-
重放:加入时间戳,保证token快速过期
一般要两者结合起来
HS256就是 hmac_hash256,防止篡改(因为没有密码),防止服务器以外的人伪造(只有服务器知道密码)
token泄露或者sessionid泄露的问题:rfc的专家早就考虑了
RFC 6819 - OAuth 2.0 Threat Model and Security Considerations
- 对于链路传输层,rfc给的建议是:全部上https,走可靠链路。哪么中间就不会有人泄漏啦。(不提HeartBleed 啊,只是实现bug,不是协议bug,大规模https/TLS的hack还没有出现,我们可以认为https是安全的。此观点答主不和任何人辩论)
- 对于终端Endpoints的泄漏。不好意思。rfc啥都没说。意思就是,我们不care。
token的不可撤销(只能等待失效)和无法更改问题有解决方案了
用两套token: Access & refresh token签名tokens
-
Access token拥有比较短的生存时间, 可以被认作为一个无状态的可信任的字符串。临时通行证
-
Refresh token拥有比较长的生存时间,是用来换取access token的。refresh token应该可以被撤销(Database + cache). 长期身份证(只记载不变信息,比如id和姓名,数据库对应有记录其失效日期,每次开会兑换临时通行证)
|应用场景|Access Token| Refresh Token|
|------------|------------------|--------------------|
|银行应用|1分钟 |30分钟|
|普通应用|5分钟 |2小时|
|新闻应用|1小时 |2年|
签名和验签时间对比,果然相差很大
-
4096位RSA私钥公钥对 签名 --> 每个49.36ms, 验签 --> 每个 0.6667ms
-
2048位RSA私钥公钥对 签名 --> 每个6.43ms, 验签 --> 每个 0.1844ms
-
1024位RSA私钥公钥对 签名 --> 每个1.088ms, 验签 --> 每个 0.0548ms
几种鉴别方式比较
系统和用户有边界。用户怎么证明“我是我”?
信息私密性分组,“只有你才知道的秘密”,私密体是信息
-
用户名密码。简短,唯一用户才知道,需要用户名和密码的对应关系。私密性。需要用户说出“我是谁”
-
公钥私钥,这个像是用户名和密码的超级升级版本
-
短信验证。手机号就是用户名,短信验证码就是密码。也是唯一用户才知道,需要用户名和密码的对应关系。
-
动态密码生成器(比如将军令)。其实也是靠初始序号的秘密性。虽然是动态,本质还是密码。
信物 “只有你才拥有的物品”,私密体是某个物品
-
古老的信物。某只特殊的鞋子,玉镯。。(这个东西要比较特别才行,若干年后无论谁拿到了,就是认这个物品)
-
U盾。只要无法克隆这个U盾,就是安全的。
-
生物识别,指纹,人脸,虹膜。用独一无二的生物特征。不需要用户说出“我是谁”
-
token(介于信息和物品之间),是服务器颁发的防伪证书。不需要用户说出“我是谁”,但是需要上送用户id,时间,权限等核对一下
- token比较特殊,第一次,用户要用户名密码或者其他方式认证后,才颁发token;他没法单独使用;其他都可以单独使用