JTW

什么是JTW(Json Web Token),通过数字签名的方式,以JSON对象为载体,在不同服务端之间安全传输信息

 

JWT有什么作用,JWT最常见的场景就授权认证,一旦用户登录,后续每个请求都将包含JWT,系统在每次

处理用户的请求之前,都要先进行JWT安全校验,通过之后在进行处理

 

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

 

令牌组成:

  1. 标头(Header)
  2. 有效载荷(Payload)
  3. 签名(Signature)

token格式:head.payload.singurater 如:xxxxx.yyyy.zzzz

 

Header:有令牌的类型和所使用的签名算法,如HMAC、SHA256、RSA

使用Base64编码组成;(Base64是一种编码,不是一种加密过程,可以被翻译成原来的样子)

{
    "alg" : "HS256",
    "type" : "JWT"
}

 

Payload :有效负载,包含声明;声明是有关实体(通常是用户)和其他数据的声明,不放用户敏感的信息,如密码。同样使用Base64编码

{
    "sub" : "123",
    "name" : "John Do",
    "admin" : true
}

 

Signature :前面两部分都使用Base64进行编码,前端可以解开知道里面的信息。Signature需要使用编码后的header和payload
加上我们提供的一个密钥,使用header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);

 

示例:

    private long time=24*60*60*1000;
    private String signature="user";

    //加密
    @Test
    public void jwt(){
        JwtBuilder builder = Jwts.builder();
        String jwtToken=builder
                //Header
                .setHeaderParam("type","JWT") //token 类型
                .setHeaderParam("alg","HS256") //使用的算法
                //payload 我们的数据
                .claim("username","leivAckerman") //用户信息(用户名称)
                .claim("role","root") //用户信息(用户的角色)
                .setSubject("admin") //主题
                .setExpiration(new Date(System.currentTimeMillis()+time)) //设置保存的时间为一天
                .setId(UUID.randomUUID().toString().replace("-",""))
                //signature 签名
                .signWith(SignatureAlgorithm.HS256,signature)
                //拼接
                .compact();

        System.out.println(jwtToken);
    }

    //解密
    @Test
    public void parse(){
        String token="eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ." +
                "eyJ1c2VybmFtZSI6ImxlaXZBY2tlcm1hbiIsInJvbGUi" +
                "OiJyb290Iiwic3ViIjoiYWRtaW4iLCJleHAiOjE2NTQ0" +
                "Mzg2MDUsImp0aSI6ImM4ZTE0MDY2ZjQzMTQyYTQ4MzFk" +
                "OGI4NzI1OGQ4NDQwIn0.yG3EIyCPJOYtnvdNxrq3IgFsJA" +
                "Kn9RvDOioXXg1VQ3g";
        JwtParser parser = Jwts.parser();
        Jws<Claims> claimsJws = parser.setSigningKey(signature).parseClaimsJws(token);

        //header
        JwsHeader header = claimsJws.getHeader();
        //body
        Claims body = claimsJws.getBody(); //拿到数据
        String username=body.get("username",String.class); //获取用用户信息
        String role=body.get("role",String.class);
        String subject = body.getSubject(); //主题
        Date expiration = body.getExpiration(); //保存时间
        String id = body.getId(); //id

        System.out.println(username+" | "+role+" | "+subject+" | "+expiration+" | "+id);
    }

 

工具类

package com.gzy.summ1tzuul.component;

import com.gzy.summ1tzuul.configuration.security.MyAuthenticationException;
import com.gzy.summ1tzuul.entity.User;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.TextCodec;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Gaoziyang
 * @version 1.0
 * @since JDK13
 * date: 2020/05/24 11:31
 * Description
 */

/**
 * 基于Spring Security的一个Jwt工具类,封装了操作Token的各种方法
 * - 使用Redis作为服务端存储Token的缓存
 * - 提供了操作Redis缓存的方法
 * - 可以自定义有效期和加密算法
 *
 */
public class JwtUtils {
    /*================================= 属性 =================================*/
    //Jwt的加密密钥
    private static String secretKey = "DefaultJwtSecretKey";
    //使用BASE64加密后的Jwt的加密密钥
    private static final String BASE64_SECRET_KEY = TextCodec.BASE64.encode(secretKey);
    //Token的过期时间,默认为一周(单位为毫秒)
    private static long expirationTime = 60 * 1000 * 60 * 24 * 7L;
    //Token的加密算法,默认使用HS256
    private static SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    //服务端的Token缓存,默认使用Redis
    private static RedisTemplate<String, Object> tokenCache = new RedisTemplate<>();

    /*================================= 方法 =================================*/

    /**
     * 根据用户名生成Token字符串
     * Claims中存储的信息:
     * - Id -> id(用户id)
     * - Username -> username(用户名)
     * - Nickname -> nickname(用户昵称)
     * - Telephone -> telephone(用户手机号)
     * - iat(ISSUED_AT) -> 签发时间
     * - exp(EXPIRATION) -> 过期时间
     * Token只存储用户的基本信息,其他信息通过数据库查询获取(例如:用户权限、用户详细资料)
     * @param userDetails 用于生成Token的用户信息
     * @return 要生成的Token字符串
     */
    public static String generateToken(UserDetails userDetails) {
        User user = (User) userDetails;
        //用于存储Payload中的信息
        Map<String, Object> claims = new HashMap<>();
        String username = user.getUsername();
        //设置有效载荷(Payload)
        claims.put("Id", user.getId());
        claims.put("Username", username);
        claims.put("Nickname", user.getNickName());
        claims.put("Telephone", user.getTelephone());
        //签发时间
        claims.put(Claims.ISSUED_AT, new Date());
        //过期时间
        Date expirationDate = new Date(System.currentTimeMillis() + expirationTime);
        claims.put(Claims.EXPIRATION, expirationDate);
        //使用JwtBuilder生成Token,其中需要设置Claims、过期时间,最后再
        String token = Jwts
                .builder()//获取DefaultJwtBuilder
                .setClaims(claims)//设置声明
                .signWith(signatureAlgorithm, BASE64_SECRET_KEY)//使用指定加密方式和密钥进行签名
                .compact();//生成字符串类型的Token
        //将生成的Token字符串存入Redis,同时设置缓存有效期
        if(StringUtils.hasText(token)){
            tokenCache.opsForValue().set(username, token, expirationTime, TimeUnit.MILLISECONDS);
        }
        return token;
    }

    /**
     * 验证Token
     * 验证方法:将客户端携带的Token进行解析,如果没有抛出解析异常(JwtException),如果相同就返回true,反之返回false
     * @param token 客户端携带过来的要验证的Token
     * @return Token是否有效
     */
    public static boolean validateToken(String token) {
        try{
            if(isTokenExpired(token)){
                return false;
            }
        }catch(JwtException e){
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断Token是否过期
     * 使用Token有效载荷中的过期时间与当前时间进行比较
     * @param token 要判断的Token字符串
     * @return 是否过期
     */
    private static boolean isTokenExpired(String token) throws JwtException{
       try{
           //从Token中获取有效载荷
           Claims claims = parseToken(token);
           //从有效载荷中获取用户名
           String username = claims.get("Username", String.class);
           if(StringUtils.isEmpty(username)){
               return true;
           }
           //通过用户名从缓存中获取指定的Token
           Object cacheToken = tokenCache.opsForValue().get("Username");
           if(StringUtils.isEmpty(cacheToken)){
               return true;
           }
       }catch(SignatureException e){
           throw new SignatureException("令牌签名校验不通过!");
       }
        return false;
    }

    /**
     * 解析Token字符串并且从其中的有效载荷中获取指定Key的元素,获取的是Object类型的元素
     * @param token 解析哪个Token字符串并获取其中的有效载荷
     * @param key 有效载荷中元素的Key
     * @return 要获取的元素
     */
    public static Object getElement(String token, String key) {
        Object element;
        try{
            Claims claims = parseToken(token);
            element = claims.get(key);
        }catch(JwtException e){
            e.printStackTrace();
            return null;
        }
        return element;
    }

    /**
     * 解析Token字符串并且从其中的有效载荷中获取指定Key的元素,获取的是指定泛型类型的元素
     * @param token 解析哪个Token字符串并获取其中的有效载荷
     * @param key 有效载荷中元素的Key
     * @param clazz 指定获取元素的类型
     * @param <T> 元素的类型
     * @return 要获取的元素
     */
    public static <T> T getElement(String token, String key, Class<T> clazz) {
        T element;
        try{
            Claims claims = parseToken(token);
            element = claims.get(key, clazz);
        }catch(JwtException e){
            e.printStackTrace();
            return null;
        }
        return element;
    }

    /**
     * 根据Token字符串获取其有效载荷,同时也是在校验Token的有效性
     * 需要使用特定的密钥来解析该Token字符串,该解析密钥必须与加密密钥一致
     * 如果解析失败则会抛出JwtException异常,解析失败的原因有很多种:
     * - 令牌过期
     * - 令牌签名验证不通过
     * - 令牌结构不正确
     * - 令牌有效载荷中数据的类型不匹配
     * ...
     * 抛出JwtException表示该Token无效!
     *
     * @param token 要解析的Token字符串
     * @return 要获取的有效载荷
     * @throws JwtException Token解析错误的异常信息
     */
    public static Claims parseToken(String token) throws JwtException{
        //在JwtParser解析器中配置用于解析的密钥,然后将Token字符串解析为Jws对象,最后从Jws对象中获取Claims
        Claims claims = Jwts
                .parser()//获取DefaultJwtParser
                .setSigningKey(BASE64_SECRET_KEY)//为获取DefaultJwtParser设置签名时使用的密钥
                .parseClaimsJws(token)//解析Token
                .getBody();//获取Claims
        return claims;
    }

    /*================================= 缓存方法 =================================*/

    /**
     * 从Token缓存中获取指定Token
     * @param object 要获取指定Token的对应的Key
     * @return Token字符串
     */
    public static String getTokenFromCache(Object object){
        Object rowToken = tokenCache.opsForValue().get(object);
        if(StringUtils.isEmpty(rowToken)){
            return null;
        }
        String token = rowToken.toString();
        return token;
    }

    /**
     * 从Token缓存中移除指定Token
     * @param key 要移除指定Token的对应的Key
     * @return 是否成功移除
     */
    public static boolean removeTokenFromCache(String key){
        Boolean isDelete = tokenCache.delete(key);
        return isDelete;
    }

    /*================================= 属性设置器 =================================*/

    /**
     * 设置Token有效期,可以使用链式编程
     * @param expirationTime Token有效期(单位为毫秒)
     * @return 返回当前JwtUtils对象
     */
    public JwtUtils setExpirationTime(long expirationTime){
        this.expirationTime = expirationTime;
        return this;
    }

    /**
     * 设置Jwt的加密算法,可以使用链式编程
     * @param signatureAlgorithm 加密算法
     * @return 返回当前JwtUtils对象
     */
    public  JwtUtils setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm){
        this.signatureAlgorithm = signatureAlgorithm;
        return this;
    }

    /**
     * 设置Jwt的加密密钥,可以使用链式编程
     * @param secretKey 加密密钥
     * @return 返回当前JwtUtils对象
     */
    public JwtUtils setSecretKey(String secretKey){
        this.secretKey = secretKey;
        return this;
    }
}

 

posted on 2022-06-04 22:27  每天积极向上  阅读(291)  评论(0编辑  收藏  举报

导航