一、 对Token的认识
我们都知道HTTP协议是无状态的,这种无状态意味着程序需要验证每一步请求,从而辨别客户端的身份。
这种方式一般我们都是通过存储Session来完成。
基于服务器验证方式暴露的问题:
1. Session:每次认证用户发起请求时,服务器需要去创建一条记录来存储信息。在请求用户增加时,内存的开销也会不断增加。
2. 可扩展性:在服务端内存中使用Session存储登录信息,伴随着可扩展性的问题。
3. CORS(跨域资源共享):跨域资源的共享存在问题,在使用Ajax抓取另一个域的资源会出现禁止请求的情况。
4. CSRF(跨站请求伪造):用户在访问银行网站时,很容易受到跨站请求伪造的攻击,并且能够被利用其访问其他的网站。
在这些问题中,可扩展性是最突出的。
Token的使用的特点:
1. 支持跨域访问
2. 无状态:Token机制在服务端不需要存储session信息,token自身包含了登录用户的信息,只需要在客户端的Cookie或本地介质存储信息。
3. 更适合CDN:可以通过内容分发网络请求服务端的所有资料,服务端提供API即可。
4. 去耦:不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成。
5. CSRF:跨站请求伪造的问题得以防范。
6. 性能提升
7. 基于标准化:API可以采用标准的JWT(JSON Web Token)。
二、 基于JWT的Token认证机制的实现
1. 一个JWT实际上就是一个字符串,它由三部分组成:头部、载荷和签名。
头部(Header)
JWT的头部承载两部分信息:
① 声明类型,这里是JWT
② 声明加密的算法,通常直接使用HAMC SHA256
{
'type':'JWT',
'alg':'HS256'
}
载荷(Payload)
① 标准中注册的声明
② 公共的声明
③ 私有的声明
标准中注册的声明(建议但不强制使用):
① iss:JWT签发者
② sub:JWT所面向的用户
③ aud:接收JWT的一方
④ exp:JWT的过期时间,这个过期时间必须要大于签发时间
⑤ nbf:定义在什么时间之前,该JWT都是不可用的
⑥ iat:JWT的签发时间
⑦ jti:JWT的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
示例:
{
"name":"zwz",
"age":"18"
}
签名(Signature)
JWT的第三部分是一个签证信息:
① header(base64后的)
② payload(base64后的)
③ secret
这个部分需要base64加密后的header和base64加密后的payload使用,连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密
密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以需要保护好。
三、 Java方式实现
1. Maven依赖的添加
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.1.0</version>
</dependency>
加密代码:
public class JwtToken {
public static String createToken() throws Exception{
Map<String, Object> map = new HashMap<String, Object>();
map.put("alg", "HS256");
map.put("typ", "JWT");
String token = JWT.create()
.withHeader(map)//header
.withClaim("name", "zwz")//payload
.withClaim("age", "18")
.sign(Algorithm.HMAC256("secret"));//加密
return token;
}
}
验证代码:
public static void verifyToken(String token,String key) throws Exception{
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(key))
.build();
DecodedJWT jwt = verifier.verify(token);
Map<String, Claim> claims = jwt.getClaims();
System.out.println(claims.get("name").asString());
}
payload部分,JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。