下面内容参考文章JWT详细讲解(保姆级教程)-阿里云开发者社区 (aliyun.com)
JSON Web Token(JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于作为 JSON 对象在各方之间安全地传输信息。此信息可以被验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。
三部分组成,通过 .
分割开
xxxxx.yyyyy.zzzzz
报头通常由两部分组成: Token的类型(即 JWT)和所使用的签名算法(如 HMAC SHA256或 RSA)。
{
"alg": "HS256",
"typ": "JWT"
}
最终这个 JSON 将由base64进行加密(该加密是可以对称解密的),用于构成 JWT 的第一部分,eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9就是base64加密后的结果。
Token的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和其他数据的语句。有三种类型的声明: registered claims, public claims, and private claims。
例如:
{
"sub": "1234567890",// 注册声明
"name": "John Doe",// 公共声明
"admin": true // 私有声明
}
这部分的声明也会通过base64进行加密,最终形成JWT的第二部分。
这些是一组预定义的声明,它们
不是强制性的,而是推荐的
,以提供一组有用的、可互操作的声明
。
例如:
注意:声明名称只有三个字符,因为 JWT 意味着是紧凑的。
使用 JWT 的人可以随意定义这些声明(
可以自己声明一些有效信息如用户的id,name等,但是不要设置一些敏感信息,如密码
)。但是为了避免冲突,应该在 JWT注册表中定义它们,或者将它们定义为包含抗冲突名称空间的 URI。
这些是创建用于在同意使用它们的各方之间共享信息的习惯声明,既不是注册声明,也不是公开声明(
私人声明是提供者和消费者所共同定义的声明
)。
注意:对于已签名的Token,这些信息虽然受到保护,不会被篡改,但任何人都可以阅读。除非加密,否则不要将机密信息放在 JWT 的有效负载或头元素中。
要创建Signature,您必须获取编码的标头(header)、编码的有效载荷(payload)、secret、标头中指定的算法,并对其进行签名。
例如,如果您想使用 HMAC SHA256算法,签名将按以下方式创建:
HMACSHA256(
base64UrlEncode(header) + "." +base64UrlEncode(payload),
secret
)
上面的JSON将会通过HMACSHA256算法结合secret进行加盐签名(私钥加密),其中header和payload将通过base64UrlEncode()方法进行base64加密然后通过字符串拼接 "."
生成新字符串,最终生成JWT的第三部分SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了
JWT输出是三个由点分隔的 Base64-URL 字符串,这些字符串可以在 HTML 和 HTTP 环境中轻松传递,同时与基于 XML 的标准(如 SAML)相比更加紧凑。
真实情况,一般是在请求头里加入Authorization,并加上Bearer标注最后是JWT(格式:Authorization: Bearer <token>
):
使用的是JJWT框架
)先导入JJWT的依赖(JJWT是JWT的框架)
<!--JWT(Json Web Token)登录支持-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
测试代码如下:
public class JjwtTest {
@Test
public void generateToken() {
// JWT头部分信息【Header】
Map<String, Object> header = new HashMap<>();
header.put("alg", "HS256");
header.put("typ", "JWT");
// 载核【Payload】
Map<String, Object> payload = new HashMap<>();
payload.put("sub", "1234567890");
payload.put("name","John Doe");
payload.put("admin",true);
// 声明Token失效时间
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,300);// 300s
// 生成Token
String token = Jwts.builder()
.setHeader(header)// 设置Header
.setClaims(payload) // 设置载核
.setExpiration(instance.getTime())// 设置生效时间
.signWith(SignatureAlgorithm.HS256,"secret") // 签名,这里采用私钥进行签名,不要泄露了自己的私钥信息
.compact(); // 压缩生成xxx.xxx.xxx
System.out.println(token);
}
}
结果如下:
使用的是JJWT框架
)@Test
public void getInfoByJwt() {
// 生成的token
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTY2MzI5NzQzMX0.Ju5EzKBpUnuIRhDG1SU0NwMGsd9Jl_8YBcMM6PB2C20";
// 解析head信息
JwsHeader jwsHeader = Jwts
.parser()
.setSigningKey("secret")
.parseClaimsJws(token)
.getHeader();
System.out.println(jwsHeader); // {typ=JWT, alg=HS256}
System.out.println("typ:"+jwsHeader.get("typ"));
// 解析Payload
Claims claims = Jwts
.parser()
.setSigningKey("secret")
.parseClaimsJws(token)
.getBody();
System.out.println(claims);// {sub=1234567890, name=John Doe, admin=true, exp=1663297431}
System.out.println("admin:"+claims.get("admin"));
// 解析Signature
String signature = Jwts
.parser()
.setSigningKey("secret")
.parseClaimsJws(token)
.getSignature();
System.out.println(signature); // Ju5EzKBpUnuIRhDG1SU0NwMGsd9Jl_8YBcMM6PB2C20
}
结果:
基于session认证的登入系统存在的问题:
每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于token的鉴权机制,就没有这些问题:
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
作者:Dearmadman
链接:https://www.jianshu.com/p/576dbf44b2ae
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。