目录
一、什么是 JWT ?
二、什么时候使用 JWT ?
三、JWT 格式
1、Header
2、Payload
3、Signature
4、 JWT实现:
官网
官网 JSON Web Tokens - jwt.ioRFC 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 格式
使用逗号分隔的三部分 :
HeaderPayloadSignaturetoken 格式: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>com.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)) .compact(); } 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) .compact(); }
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));//写入jsonpw.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 验证不通过");} //验证成功