基于token的认证
langu_xyz

说到token就必然绕不开cookie和session。

cookie:由服务端给客户端颁发的一张通行证,用来验证客户端的身份,本质上是一段在浏览器上以KV形式存储的文本数据,包含了session相关信息。用于解决HTTP协议无状态的问题,所以cookie是一个会话跟踪机制,是有状态的。

session:当客户端请求服务端通过验证后,服务端会生成保存身份认证相关的session数据,并将session相关信息写入cookie返回给客户端,然后客户端将cookie保存到本地。之后两端就通过核对session信息来确认可信状态。session 可能会存储在内存、磁盘、数据库里,可能需要在服务端定期的去清理过期的 session。

0x02 token

既然有了cookie/session为啥还需要token呢

优点

1、无状态、可扩展

2、安全性

3、可扩展性

4、多平台跨域/单点登陆

5、基于标准

6、缓解服务器内存压力/增大服务器计算压力

格式

UID + TIME + SIGN [+ OTHER]

0x03 实施

JSON Web Tokens(JWT)

组成

  • header

用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等

1
2
3
4
{
"typ": "JWT",
"alg": "HS256"
}

base64一下

ewogICJ0eXAiOiAidG9rZW7nsbvlnosiLAogICJhbGciOiAi562+5ZCN566X5rOVIgp9

  • payload

标准文档:https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1

可以在其中添加这些字段

1
2
3
4
5
6
7
iss:Issuer,发行者
sub:Subject,主题
aud:Audience,观众
exp:Expiration time,过期时间
nbf:Not before
iat:Issued at,发行时间
jti:JWT ID
1
2
3
4
5
Map<String , Object> payload=new HashMap<String, Object>();
Date date=new Date();
payload.put("uid", "007");
payload.put("iat", date.getTime());
payload.put("ext",date.getTime()+1000*60*60);

上边代码中添加的字段如下

1
2
3
4
5
{
"iat": 当前时间,
"exp": 过期时间,
"uid": "007"
}

base64编码

ewogICAgImlhdCI6IOW9k+WJjeaXtumXtCwKICAgICJleHAiOiDov4fmnJ/ml7bpl7QsCiAgICAidWlkIjogIjAwNyIKfQ==

这样payload就生成好了

  • signature

将header和payload生成的base64编码通过.连接起来,如下

ewogICJ0eXAiOiAidG9rZW7nsbvlnosiLAogICJhbGciOiAi562+5ZCN566X5rOVIgp9.ewogICAgImlhdCI6IOW9k+WJjeaXtumXtCwKICAgICJleHAiOiDov4fmnJ/ml7bpl7QsCiAgICAidWlkIjogIjAwNyIKfQ==

然后再定义一个secret,如下

secret

通过header中定义的HS256算法以secret为密钥进行加密得到signature

81faa5ef7b7596783cb3ed2f75618def367a9b7f8490047cb12880d895b794eb

此时JWT就生成了,base64(header).base64(payload) .signature

像这样ewogICJ0eXAiOiAidG9rZW7nsbvlnosiLAogICJhbGciOiAi562+5ZCN566X5rOVIgp9.ewogICAgImlhdCI6IOW9k+WJjeaXtumXtCwKICAgICJleHAiOiDov4fmnJ/ml7bpl7QsCiAgICAidWlkIjogIjAwNyIKfQ==.81faa5ef7b7596783cb3ed2f75618def367a9b7f8490047cb12880d895b794eb

当然这种方式不能在token中携带敏感信息,例如密码

0x04 应用

  • 单点登陆

Set-Cookie: jwt=yyy.zzz.xxx; HttpOnly; max-age=980000; domain=.taobao.com

  • API 调用/授权

https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=xxxx

  • 支付验证(一次性)

  • 串行服务调用

    一次性有效,再次生成token时以用户账户和第一次token为key,update该记录来判断

  • 敏感接口多次调用

0x05 代码

生成代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static final JWSHeader header=new JWSHeader(JWSAlgorithm.HS256, JOSEObjectType.JWT, null, null, null, null, null, null, null, null, null, null, null);

/**
* 生成token,该方法只在用户登录成功后调用
*
* @param Map集合,可以存储用户id,token生成时间,token过期时间等自定义字段
* @return token字符串,若失败则返回null
*/
public static String createToken(Map<String, Object> payload) {
String tokenString=null;
// 创建一个 JWS object
JWSObject jwsObject = new JWSObject(header, new Payload(new JSONObject(payload)));
try {
// 将jwsObject 进行HMAC签名
jwsObject.sign(new MACSigner(SECRET));
tokenString=jwsObject.serialize();
} catch (JOSEException e) {
System.err.println("签名失败:" + e.getMessage());
e.printStackTrace();
}
return tokenString;
}

校验代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static Map<String, Object> validToken(String token) {
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
JWSObject jwsObject = JWSObject.parse(token);
Payload payload = jwsObject.getPayload();
JWSVerifier verifier = new MACVerifier(SECRET);
if (jwsObject.verify(verifier)) {
JSONObject jsonOBj = payload.toJSONObject();
// token校验成功(此时没有校验是否过期)
resultMap.put("state", TokenState.VALID.toString());
// 若payload包含ext字段,则校验是否过期
if (jsonOBj.containsKey("ext")) {
long extTime = Long.valueOf(jsonOBj.get("ext").toString());
long curTime = new Date().getTime();
// 过期了
if (curTime > extTime) {
resultMap.clear();
resultMap.put("state", TokenState.EXPIRED.toString());
}
}
resultMap.put("data", jsonOBj);
} else {
// 校验失败
resultMap.put("state", TokenState.INVALID.toString());
}
} catch (Exception e) {
//e.printStackTrace();
// token格式不合法导致的异常
resultMap.clear();
resultMap.put("state", TokenState.INVALID.toString());
}
return resultMap;
}

参考:
http://blog.leapoahead.com/2015/09/06/understanding-jwt/
https://github.com/bigmeow/JWT

  • Post title:基于token的认证
  • Post author:langu_xyz
  • Create time:2018-05-18 21:00:00
  • Post link:https://blog.langu.xyz/基于token的认证/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.