目录
一、什么是 JWT ?
二、什么时候使用 JWT ?
三、JWT 格式
1、Header
2、Payload
3、Signature
4、 JWT实现:
官网
- 官网 json Web Tokens - jwt.io
- RFC 7519文档 RFC 7519: JSON Web Token (JWT)
一、什么是 JWT ?
JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。
JWT可以使用密码(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
加签后的token 能够使用 JWT 里的算法验证 json 的完整性.
二、什么时候使用 JWT ?
- 授权
- 信息交换
- 使用方式:服务端根据规范生成一个令牌(token),并且发放给客户端(保存在客户端)。此时客户端请求服务端的时候就可以携带者令牌,以令牌来证明自己的身份信息。
- 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
JWT优势
简洁:可以通过URL、POST参数或者在HTTP Header发送,因为数据量小,传输速度也很快
自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库
因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式 都支持
不需要在服务端保存会话信息,特别适合用于分布式微服务。
更适合用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时使用token认证方 式会简单很多
单点登录友好:由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会 存在这些问题
三、JWT 格式
使用逗号分隔的三部分 :
- Header
- Payload
- Signature
token 格式:xxxxx.yyyyy.zzzzz
1、Header
Header 通常由 token 类型和签名算法名两部分组成.是token的第1部分
例如:
{ |
|
"alg": "HS256", // 签名算法 |
|
"typ": "JWT" // token类型 |
|
} |
然后, 这个JSON 使用 Base64Url 编码后放到 JWT 的第1部分.
2、Payload
Payload 是token的第2部分.包含了一些声明(claims).声明的名字必须是唯一的.
claims 是包含了 用户数据和其他数据的陈述,
有三种类型的声明:
- Registered Claim Name
预定义好的一些声明(如果有需要就使用,没需要可不使用):
- "iss"
- "sub"
- "aud"
- "exp"
- "nbf"
- "iat"
- "jti"
更多参见 JSON Web Token (JWT)
-
Public Claim Names
公共的声明,可以预先定义在 IANA JSON Web Token Registry 中,或者定义在1个能解决名字冲突的地方. -
Private Claim Names
双方共享数据使用的私有名字.既不在 Registered Claim Name 也不在 Public Claim Names 中.
payload 示例
{ |
|
"sub": "1234567890", |
|
"name": "John Doe", |
|
"admin": true |
|
} |
JSON
然后, 这个JSON 使用 Base64Url 编码后放到 JWT 的第2部分.
3、Signature
拿到编码后的 header 和 编码后的 payload 使用 密码进行签名.
使用 HMAC SHA256 加签示例:
HMACSHA256( |
base64UrlEncode(header) + "." + |
base64UrlEncode(payload), |
secret) |
Signature需要使用编码后的header和payload以及我们提供的一个秘钥,然后使用header中指定的签名算法进行签名,签名的作用是保证JWT没有被篡改过
HMACSHA256(base64UrlEncode(header)+“.”+base64UrlEncode(payload),secret)
实际上是对头部信息和负载内容进行签名,防止内容被篡改,如果有人对头部以及负载内容解码后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT上附带的签名是不一样的。如果要对新的头部和负载进行签名,由于不知道服务器加密时使用的秘钥,得出来的结果也是不一样的
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。
4、 JWT实现:
1、依赖引入
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt-jsonwebtoken.version}</version>
</dependency>
<dependency>
<groupId>***.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>RELEASE</version>
</dependency>
2、生成Token
// 签名密钥
private static final String SECRET = "!Doker$";
public String createToken(Map<String, Object> claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, Algorithm.HMAC256(SECRET))
.***pact();
}
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration * 1000);
}
3、刷新Token
public String RefreshToken(String token) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
final Claims claims = getAllClaimsFromToken(token);
claims.setIssuedAt(createdDate);
claims.setExpiration(expirationDate);
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, Secret)
.***pact();
}
4、token发送给前端
传入当前用户的功能与用户信息,登录名生成token,写入response的返回头中,前端获取后保存在前端的本地缓存中,后续前端请求要把token放在头header里。
//登录成功之后
List<Object> functs=(List<Object>) authResult.getAuthorities();
//当前功能列表
String loginName=authResult.getName();//登录名
Users obj=(Users)authResult.getPrincipal();//用户信息
String token=JwtUtil.createToken(loginName,functs,obj);
//生成token TOKEN_HEADER= Authorization TOKEN_PREFIX=Bearer token值
response.setHeader(JwtUtil.TOKEN_HEADER,JwtUtil.TOKEN_PREFIX+token);
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK); //个人编写的视图对象
DTO dto=new DTO<>();
dto.setCode("000000");
dto.setMessage("认证通过");
PrintWriter pw=response.getWriter();
pw.write(JsonUtil.set(dto));//写入json
pw.flush();//强制刷新
pw.close();//关闭流
5、验证用户请求携带token
String header = request.getHeader(JwtUtil.TOKEN_HEADER);
if (null == header || !header.toLowerCase().startsWith(JwtUtil.TOKEN_PREFIX)) {
// 如果头部 Authorization 未设置或者不是 basic 认证头部,则当前
// 请求不是该过滤器关注的对象,直接放行,继续filter chain 的执行
chain.doFilter(request, response);
return;
}
try {
String token = header.replace(JwtUtil.TOKEN_PREFIX, "");
// 验证token是否过期
if (JwtUtil.isExpiration(token)) {
throw new javax.security.sasl.AuthenticationException("token 验证不通过");
}
//檢查token是否能解析
Users user = (Users) JwtUtil.getUser(token);
if (null == user) {
throw new javax.security.sasl.AuthenticationException("token 验证不通过");
}
//验证成功