**jwt定义:**JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
JWT本质:就是一个字符串,由三部分组成,头部、载荷与签名。
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{"typ":"JWT","alg":"HS256"}
在头部中指明了,签名算法是HS256算法。默认采用base64编码,编码后的字符串为:
http://tool.oschina.net/encrypt?type=3
{"typ":"JWT","alg":"HS256"} 明文
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 密文
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
(1)标准中注册的声明(建议但不强制使用)
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
(2)公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密
(3)私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。
定义一个payload:
{"sub":"1234567890","name":"John Doe","admin":true}
然后将其进行base64编码,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
http://tool.oschina.net/encrypt?type=2
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 使用 HS256算法,使用secret, 生成签证
123456
5b2a3bdc166345add37e238e01c9c5ca3dea1bf5fbc81f619e34fc66e147ffde
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.5b2a3bdc166345add37e238e01c9c5ca3dea1bf5fbc81f619e34fc66e147ffde
io.jsonwebtoken
jjwt
0.6.0
public static void main(String[] args) {
JwtBuilder jwtBuilder = Jwts.builder().setId("1164032971834003456")
.setSubject("小刘")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256,"liubijun");
System.out.println(jwtBuilder.compact());
}
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMTY0MDMyOTcxODM0MDAzNDU2Iiwic3ViIjoi5bCP5YiYIiwiaWF0IjoxNTY2MzY4ODk2fQ.39RBcIhd0OiVW9BkA_JyBoUyMrRl-eEo-UHuUQ_BC2U
public static void main(String[] args) {
Claims claims = Jwts.parser()
.setSigningKey("liubijun").parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMTY0MDMyOTcxODM0MDAzNDU2Iiwic3ViIjoi5bCP5YiYIiwiaWF0IjoxNTY2MzY4ODk2fQ.39RBcIhd0OiVW9BkA_JyBoUyMrRl-eEo-UHuUQ_BC2U").getBody();
System.out.println("用户ID:"+ claims.getId());
System.out.println("用户名称:"+ claims.getSubject());
System.out.println("登录时间:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ").format(claims.getIssuedAt()));
}
package com.liu.common.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 令牌生成和解析工具
*/
@Data
@ConfigurationProperties("jwt.config")
public class JwtUtil {
private String key ;
private long ttl ;//一个小时
/**
* 生成JWT
*
* @param id
* @param subject
* @return
*/
public String createJWT(String id, String subject, String roles) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder().setId(id)
.setSubject(subject)
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);
if (ttl > 0) {
builder.setExpiration( new Date( nowMillis + ttl));
}
return builder.compact();
}
/**
* 解析JWT
* @param jwtStr
* @return
*/
public Claims parseJWT(String jwtStr){
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtStr)
.getBody();
}
}
在我们的项目中经常会用到权限的,验证。为了能够一劳永逸,我们使用拦截器来进行jwt的解析。
注意:无论解析是否成功,我们都要放行,毕竟不是所以的业务都需要权限验证。我们是在需要解析后,将将解析的结果放置到request域中,这样我们就可以通过获得request域,来获取前面传来的权限相关信息。本例是在service层 做的权限。
在jwt token解析的过程中,如果用户过期,则解析会报错。记得处理下异常,要不然报错一堆。
package com.liu.user.interceptor;
import com.liu.common.utils.JacksonUtil;
import com.liu.common.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Created by Administrator on 2019/8/21 0021.
*/
@Component
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("进入了权限拦截器{}");
// 这里主要做的是做权限的解析,而不是拦截用户请求
String header =request.getHeader("Authorization");
log.info("header{}",StringUtils.isEmpty(header));
if(!StringUtils.isEmpty(header) && header.startsWith("Bearer ")){
final String token = header.substring(7);
try{
Claims claims = jwtUtil.parseJWT(token);
log.info("claims{}",JacksonUtil.printJson(claims));
if(claims!=null){
if("admin".equals(claims.get("roles"))){//如果是管理员
request.setAttribute("admin_claims", claims);
}
if("user".equals(claims.get("roles"))){//如果是用户
request.setAttribute("user_claims", claims);
}
}
}catch (Exception e){
throw new RuntimeException("令牌不正确");
}
}
return true;
}
}
public void sendSMS(String mobile){
Claims claims = (Claims) request.getAttribute("admin_claims");
if(claims==null){
throw new RuntimeException(CodeEnum.ACCESS_ERROR.getMessage());
}
String random =RandomStringUtils.randomNumeric(6);
log.info("验证码为:{}",random);
redisTemplate.boundHashOps(RedisKeyEnum.CAPTCHA_CODE.getKey()).put(mobile,random);//保存验证码到redis
Map map = new HashMap<>();
map.put("code",random);
map.put("mobile",mobile);
rabbitTemplate.convertAndSend("CAPTCHA_CODE",map); //发送验证码到rabbitmq
}
我们可以看到在拦截器,或者是service层,我们都直接抛出了 异常,我们可以写一个全局的异常捕获类,来处理我们抛出的异常
package com.liu.user.exception;
import com.liu.common.entity.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* 本文只是没有系统的处理异常,一般应用系统中,我们都会定义多个异常类,来处理不同的异常情况。
然后通过异常处理类,处理不同种类的异常。
*/
@RestControllerAdvice
public class MyGlobalExceptionHandler {
@ExceptionHandler({Exception.class})
public Result handException(HttpServletRequest request ,Exception e) throws Exception{
return new Result(false,10001,e.getMessage());
}
}
本小结,讲述完毕。