JWT

概念:它是json web token的缩写,它将用户信息保存到token中,服务器不保存任何用户信息,服务器通过使用保存的秘钥验证token的正确性,只要正确即通过验证。
优点:
1.在分布式系统中很好的解决了单点登录的问题,很容易解决session共享的问题
2.因为json的通用性,所以JWT是可以跨语言支持的,像C#,JavaScript,NodeJS,PHP等许多语言都可以使用
3.因为由了payload部分,所以JWT可以在自身存储一些其它业务逻辑所必要的非敏感信息
4.便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的
5.它不需要在服务端保存会话信息,所以它易于应用的扩展
缺点
1.无法作废已颁布的令牌不易应对过期的数据
2.不应该在jwt的payload部分存储敏感信息,因为该部分是客户端可解密的部分
3.保护好secret私钥。该私钥非常重要
jwt消息构成,一个token 分为三部分:
 头部:两部分组成:声明类型,声明加密的算法 通常直接使用HMAC SHA256
完整的头部就像下面这样的JSON
{
     'typ':'JWT',
     'alg':'HS256'  
}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

 荷载 :基本上有两种类型的数据,标准中注册的声明的数据,自定义数据 两部分内部做base64加    密,最终进入jwt的chaims里存放


 签证  
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header(base64后的)
payload(base64后的)
secred    
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行secret组合加密,然后就构成了jwt的第三部分。
 注意:secret是保存在服务器端的,jwt的签发也是在服务端的,secret就是用来进行jwt的签发和jwt的验证,所以它就是你服务端的私钥,在任何场景都不应该流露出去,一旦客户端得知这个secret,那就意味着客户端可以自我签发jwt了

  三者中间用 . 隔开       例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 

基于token的鉴权机制:
类似于http的无状态    ,它不需要在服务端保存用户的认证信息或回话信息,这样的话 就不用去考虑在哪一台服务器登录了,方便扩展。
大致流程:
 用户通过名字和密码请求服务器
 服务器进行用户信息验证
 服务器通过验证发送给用户一个token
 客户端存储这个token,并在每次请求时附加这个token值
服务器验证token 返回数据

这个token在每次请求时 必须发送给服务器,它应该放在请求头中,另外服务器要支持跨域请求策略

JWT

 应用 
      一般是在请求头里加入Authorization,并加上Bearer标注:
fetch('api/user/1', {
  headers: {
    'Authorization': 'Bearer ' + token  这下我知道为何要加这个Bearer
  }
})


那么接下来 咱们操作一把:
pom依赖:(jjwt框架可以更好的耍jwt)

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.10.5</version>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.10.5</version>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.10.5</version>
    <scope>runtime</scope>
</dependency>

 

2.配置yml

auth:
  security:
    authentication:
      jwt:
        # This token must be encoded using Base64 (you can type `echo 'secret-key'|base64` on your command line)
        secret: MDFjODJhZDU1MmQyOWM2Mjg4ZDc1Y2YxZDk5ODNkMGQzZDMzOTZhMTFlYTQ2MTIyNTlkMmYyNzkzNjhlZDM0ZTg3NzJlMTUyMjIzMGQ2NzY0M2EyOTgwZTJiODNkYjVjMzBhNTI3NTY5NGRlNjRhZmNhZWYwYjRjNjE2MmY5YjA=   #这个secret在这个项目中唯一
        # Token is valid 24 hours
        token-validity-in-seconds: 86400
        token-validity-in-seconds-for-remember-me: 2592000

 

创建解析token:

@Component
public class TokenProvider {

    /**
     * 也可以将这三个字段放在一个实体类中去取对应的yml中的值
     */
    @Value("${auth.security.authentication.jwt.secret}")
    private String secret;
    @Value("${auth.security.authentication.jwt.token-validity-in-seconds}")
    private Long  validityInSeconds;
    @Value("${auth.security.authentication.jwt.token-validity-in-seconds-for-remember-me}")
    private Long  tokenValidityInSecondsForme;
    /**
     *    将secret值取到进行加密之后的值给了这个key 用加密之后的key值作为签证
     */
    private Key key;


    @PostConstruct  //构造方法之后,servlet的init之前执行
    public void init() {
        System.out.println("执行啦。。init。。");
        byte[] keyBytes = Decoders.BASE64.decode(secret);
       //将secret加密之后在初始化时就给了key 加密解密这个值得相同唯一
        this.key = Keys.hmacShaKeyFor(keyBytes);
        System.out.println(key);

        validityInSeconds = 1000 * validityInSeconds;
        tokenValidityInSecondsForme = 1000 * tokenValidityInSecondsForme;

    }

/**
     * 解析jwt字符串
     * @param token
     * @return
     * @throws NullPointerException
     */
    public Map<String,Object> parseToken(String token) throws NullPointerException{
        System.out.println("我在调用 这个解析的方法");
        token = token.replaceAll(" ", "").substring(6);
        System.out.println(token);
        System.out.println(key);
        Claims claims = Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(token)
                .getBody();
        if(claims.isEmpty()){
            throw new NullPointerException();
        }
        //取出生成时存入的map的值
       return claims;

    }

    public String pusuCreateToken(Map<String, Object> claimMap) {
          String topicKey = (String) claimMap.get("topicKey");
          return getJwtString(topicKey, claimMap);
      }
/**
     * 生成jwt字符串
     * @param str  自定义str 作为sub条件
     * @param claimMap 用户登录之后返回的信息字段 进行加密 进行传输  解析之后会取到
     * @return
     */
    public String getJwtString(String str,Map<String, Object> claimMap){
        //当前毫秒数加过期时间 就是这个jwt的过期时间
        long time = System.currentTimeMillis() + this.validityInSeconds;
        Date validity = new Date(time);
        return  Jwts.builder()
                .setSubject(str)
                .addClaims(claimMap)
                .signWith(key, SignatureAlgorithm.HS512)
                .setExpiration(validity)
                .compact();
    }


}

 

JWT之token过期 ------ 检验token是否过期的方法:

public boolean validateToken(String authToken) {
    try {
        Jwts.parser().setSigningKey(key).parseClaimsJws(authToken);
        return true;
    } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
        log.info("Invalid JWT signature.");
        log.trace("Invalid JWT signature trace: {}", e);
    } catch (ExpiredJwtException e) {
        log.info("Expired JWT token.");
        log.trace("Expired JWT token trace: {}", e);
    } catch (UnsupportedJwtException e) {
        log.info("Unsupported JWT token.");
        log.trace("Unsupported JWT token trace: {}", e);
    } catch (IllegalArgumentException e) {
        log.info("JWT token compact of handler are invalid.");
        log.trace("JWT token compact of handler are invalid trace: {}", e);
    }
    return false;
}

 

如果token过期的话 就需要将其中的荷载信息 即生成token时的map信息取出来,然后拿着这个新的map去重新生成新的token。
坑:如果token已经过期了,那么调用解析token的方法,就会报错,无法解析,只能通过自己手动去取出token的第二部分荷载,然后去生成新的token。
因为token的第二部分是由base64加密而来,所以直接取到第二部分解密就行

@PostMapping(value = "/pubSub/replaceToken")
@Timed
public ResponseEntity<?> replaceToken(@Validated @RequestBody ClientTokenVM vm) {
    String newToken = null;
    try {
//这是校验
        tokenProvider.validate(vm.getToken());//如果过期直接执行catch
    } catch (ExpiredJwtException e) {
        //如果是过期token ,取出map信息。
        String[] splitToken = vm.getToken().split("\\.");
        try {
            byte[]  bt = (new BASE64Decoder()).decodeBuffer(splitToken[1]);
            //将其转成map
            Gson gson = new Gson();
            Map claims = gson.fromJson( new String(bt), HashMap.class);
            //重新去更换token并返回
       newToken = tokenProvider.pusuCreateToken(claims);
        } catch (IOException e1) {
            log.error("base64解码异常");
        }
    }
    return ResponseEntity.ok(JsonResult.ok().build(newToken));
}

(内涵小知识:将string类型的数据转换为map,前提是该字符串必须为json格式,利用gson进行转换很方便呀)