博客
关于我
java手动实现JWT(我和别人的不一样)
阅读量:292 次
发布时间:2019-03-03

本文共 20521 字,大约阅读时间需要 68 分钟。

JWT是啥

我不会官方语言,我只会最简单的解释 json web token

为什么要用这个东西

简单点说 现在都流行前后端分离开发,如果需要一个前后端的通讯验证,最简单的cookie+session可以完成这个任务。但是会有问题昂,万一给你浏览cookie器禁了咋整,现在的用户才是老大哥不是,所以呢 就出现这个东西了

它能干点啥

网上一搜一大片最多的就是。单点登录 + jwt。但是这两个东西不是一定要混为一谈的。jwt是jwt 简单点 他就是提供了一点token验证的方式,而不是一定要去作单点登录。(一开始我也混为一谈了,俺们领导天天跟我强调他俩不是一回事)

正题开始—怎么玩

序篇:最烦唠叨一大片没有实际应用的烦不烦,最开始还要强调一个问题,本文不是用的第三方 java-jwt 或者 jjwt 我们是完全自己实现的。和大众定义的jwt格式没啥太大关系。我们只是借鉴一下思想(因为领导说自己写加密方式比较安全)正文开始:我们查一下文档他说 jwt分为三段用 “.” 连接
第一段:
第一段是个json。很简单很简单,就是告诉大家,我们在用jwt,也可以告诉他你用什么方式加密的 但是我并不想告诉他 哈哈哈
第二段 payload
第二段是我们传输的实际业务内容,比如用户名呐,id呐。 所以可以定一个javaBean
第三段
这一段是作为安全性验证最关键的一步,将前两段内容拼接后拿过来,进行加密。
代码开始
我们先准备一点工具类,准备啥呢,我们准备用Aes进行第三种的加密, 当然还要准备base64工具类,不可能用字节数组来回的传输。这里用到的东西以jdk1.8为准。都是自带的 不需要导入任何第三方依赖 首先是AesUtil  这里有个坑要注意哦 网上搜的大多数aes加密工具类 会有问题, 第一个问题:详情请搜索Aes自动补全机制,如果你不改 在windows环境没问题, linux环境加解密的补全机制不一样,解密时就会报错。报错是啥我没记 记住有这个问题就好 具体改动下面注释有 第二个问题:aes 处理前后是有byte数组存在的,正常情况下我们是直接 new  String(byte[] b).  String s.getBytes()这样的。但是这样会有问题,由于这个串长度 巴拉巴拉 的问题,前后会不一样,所以用base64进行处理。或者用 16--2 进制处理,我下面都提供了 自己看着来
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.crypto.*;import javax.crypto.spec.SecretKeySpec;import java.io.UnsupportedEncodingException;import java.security.InvalidKeyException;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;import java.security.Security;/** * @author chunying * @Date 2020.12.02 * @Description 用于Aes加解密的工具类 */public class AesUtil {       private static final Logger LOG = LoggerFactory.getLogger(AesUtil.class);    /**     * AES加密     * @param value     * @param key     * @return     */    public static byte[] encrypt(String value, String key) {           try {            //等等等 就是这里了 这里要告诉key生成器 我们用的是sun提供的加密。解密同理 如果不服气 你不用一个试试             Security.addProvider(new sun.security.provider.Sun());            KeyGenerator kgen = KeyGenerator.getInstance("AES");// 创建AES的Key生产者            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG",new sun.security.provider.Sun());            secureRandom.setSeed(key.getBytes());            kgen.init(128, secureRandom);            //加密没关系,SecureRandom是生成安全随机数序列,key.getBytes()是种子,只要种子相同,序列就一样,所以解密只要有password就行            SecretKey secretKey = kgen.generateKey();// 根据用户密码,生成一个密钥            byte[] enCodeFormat = secretKey.getEncoded();// 返回基本编码格式的密钥,如果此密钥不支持编码,则返回            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");// 转换为AES专用密钥            Cipher cipher = Cipher.getInstance("AES");// 创建密码器            byte[] byteContent = value.getBytes("utf-8");            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);// 初始化为加密模式的密码器            byte[] result = cipher.doFinal(byteContent);// 加密            return result;        } catch (NoSuchPaddingException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        } catch (NoSuchAlgorithmException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        } catch (UnsupportedEncodingException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        } catch (InvalidKeyException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        } catch (IllegalBlockSizeException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        } catch (BadPaddingException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        }        return null;    }    /**     * AES解密 参数是二进制数组 + key     * @param value     * @param key     * @return     */    public static byte[] desCrypt(byte[] value, String key) {           try {               Security.addProvider(new sun.security.provider.Sun());            KeyGenerator kgen = KeyGenerator.getInstance("AES");// 创建AES的Key生产者//            kgen.init(128, new SecureRandom(key.getBytes()));            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG", new sun.security.provider.Sun());            secureRandom.setSeed(key.getBytes());            kgen.init(128, secureRandom);            SecretKey secretKey = kgen.generateKey();// 根据用户密码,生成一个密钥            byte[] enCodeFormat = secretKey.getEncoded();// 返回基本编码格式的密钥            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");// 转换为AES专用密钥            Cipher cipher = Cipher.getInstance("AES");// 创建密码器            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);// 初始化为解密模式的密码器            byte[] result = cipher.doFinal(value);            return result; // 明文        } catch (NoSuchAlgorithmException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        } catch (NoSuchPaddingException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        } catch (InvalidKeyException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        } catch (IllegalBlockSizeException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        } catch (BadPaddingException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        }        return null;    }    /**     * 将二进制密文转再次换成可识别字符串 返回的是16进制     * @param value     * @return     */    public static String parseEncrypt(String value, String key) {           byte[] encrypt = encrypt(value, key);//        String s = ParseSystemUtil.parseByte2HexStr(encrypt);        String s = Base64Util.byte2Base64StringFun(encrypt);        return s;    }    /**     * 返回解密后的内容     * @param value     * @param key     * @return     */    public static String parseDesEncrypt(String value, String key) {   //        byte[] bytes = ParseSystemUtil.parseHexStr2Byte(value);        byte[] bytes = Base64Util.base64String2ByteFun(value);        try {               return new String(desCrypt(bytes, key), "utf-8");        } catch (UnsupportedEncodingException e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        }        return null;    }}
除了上面那个大哥 下面基本就没啥问题了、
import java.io.UnsupportedEncodingException;import java.util.Base64;/** * @author wangchunying * @Date 2020.12.06 * @Description base工具类 */public class Base64Util {       //转码 参数:值,编码格式    public static String getEncodeStr(String value, String format) throws UnsupportedEncodingException{           return Base64.getEncoder().encodeToString(value.getBytes(format));    }    //解码 参数:值,编码格式    public static String getDecodeStr(String value, String format) throws UnsupportedEncodingException {           return new String(Base64.getDecoder().decode(value), format);    }    //base64字符串转byte[]    public static byte[] base64String2ByteFun(String base64Str){           return Base64.getDecoder().decode(base64Str);    }    //byte[]转base64    public static String byte2Base64StringFun(byte[] b){           return Base64.getEncoder().encodeToString(b);    }}/** * @author wangchunying * @Date 2020.12.02 * @Description 目前有进制转换 */public class ParseSystemUtil {       /**将二进制转换成16进制     * @param buf     * @return     */    public static String parseByte2HexStr(byte buf[]) {           StringBuffer sb = new StringBuffer();        for (int i = 0; i < buf.length; i++) {               String hex = Integer.toHexString(buf[i] & 0xFF);            if (hex.length() == 1) {                   hex = '0' + hex;            }            sb.append(hex.toUpperCase());        }        return sb.toString();    }    /**将16进制转换为二进制     * @param hexStr     * @return     */    public static byte[] parseHexStr2Byte(String hexStr) {           if (hexStr.length() < 1)            return null;        byte[] result = new byte[hexStr.length() / 2];        for (int i = 0; i < hexStr.length() / 2; i++) {               int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);            result[i] = (byte) (high * 16 + low);        }        return result;    }}
然后是最重要的部分 生成解密token部分
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import org.apache.commons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.servlet.http.HttpSession;/** * @author wangchunying * @Date 2020.12.06 * @Description 关于jwt的操作 */public class JwtTokenUtil {       private static final Logger LOG = LoggerFactory.getLogger(JwtTokenUtil.class);    //token header    public static final String AUTH_HEADER_KEY = "Authorization";    //一周过期时间    private static final Long expireTime = 1000L*60*60*24*7;    private static final String TYPE_KEY = "typ";    private static final String TYPE_VALUE = "jwt";    private static final String SPOT = ".";    private static final String FORMAT = "utf-8";    private static final String SESSION_KEY = "token";    //加密key    private static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";    //token前缀    private static final String TOKEN_PREFIX = "ssoToken";    /**     * 生成token 以用户基本信息为payload基础生成token 这样解析出来知道是哪个用户     */    public static String createToken(UserToken userToken)throws Exception {           StringBuffer sbResult = new StringBuffer();        JSONObject jwtFirstJson = new JSONObject();        String jwtSecondStr = Base64Util.getEncodeStr(JSON.toJSONString(userToken), FORMAT);        //拼接jwt格式        jwtFirstJson.put(TYPE_KEY, TYPE_VALUE);        String jwtFirstStr = Base64Util.getEncodeStr(JSON.toJSONString(jwtFirstJson), FORMAT);        sbResult.append(jwtFirstStr).append(SPOT).append(jwtSecondStr);        String hexString = AesUtil.parseEncrypt(sbResult.toString(), KEY);        //设置过期时间        refreshExpireTime(userToken);//        String jwtString = TOKEN_PREFIX + JWT.create()//                .withSubject(jsonString)//                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))//                .sign(AesUtil.parseEncrypt(jsonString, KEY));        return sbResult.append(SPOT).append(hexString).toString();    }    //解析返回的token    public static UserToken verifyToken(String value) {           if (StringUtils.isBlank(value)) {               throw new JwtException(JwtException.JwtErrorCodeEnum.FORMAT, "token is null");        }        String[] valueArray = value.split("\\.");        if (valueArray.length < 3) {               throw new JwtException(JwtException.JwtErrorCodeEnum.FORMAT, "token des format is error", valueArray);        }        String thirdStr = AesUtil.parseDesEncrypt(valueArray[2], KEY);        //校验是否是真实用户?暂定先返回传过来的信息        if (StringUtils.isBlank(thirdStr)) {               throw new JwtException(JwtException.JwtErrorCodeEnum.FORMAT, "token des is error");        }        String[] thirdDesStr = thirdStr.split("\\.");        if (thirdDesStr == null || thirdDesStr.length < 2) {               throw new JwtException(JwtException.JwtErrorCodeEnum.CONTENT, "token des format is error", thirdDesStr);        }        //校验是否符合格式        try {               //检验payload 与 header 是否被动过            if (!valueArray[0].equals(thirdDesStr[0]) || !valueArray[1].equals(thirdDesStr[1])) {                   throw new JwtException(JwtException.JwtErrorCodeEnum.MODIFIED, "token data is modified by smo");            }            //first str            String firstStr = Base64Util.getDecodeStr(thirdDesStr[0], FORMAT);            JSONObject jsonObject = JSON.parseObject(firstStr);            String typeValue = jsonObject.getString(TYPE_KEY);            if (!StringUtils.isNotBlank(typeValue) || !typeValue.equals(TYPE_VALUE)) {                   throw new JwtException(JwtException.JwtErrorCodeEnum.CONTENT, "token first format is error", typeValue);            }            //second str            String secondStr = Base64Util.getDecodeStr(thirdDesStr[1], FORMAT);            UserToken userToken = JSON.parseObject(secondStr, UserToken.class);            if (isExpire(userToken)) {                   throw new JwtException(JwtException.JwtErrorCodeEnum.EXPIRE, "token is expire");            }            return userToken;        }catch(Exception e) {               LOG.error(e.getMessage(), e);            e.printStackTrace();        }        return null;    }    /**     * 判断用户token是否过期     * @param userToken     * @return     */    public static Boolean isExpire(UserToken userToken) {           Long currentTime = System.currentTimeMillis();        return userToken.getExpireTime() <= currentTime;    }    /**     * 刷新token生效时间     * @param usertoken     */    public static void refreshExpireTime(UserToken usertoken) {           Long time = System.currentTimeMillis() + expireTime;        usertoken.setExpireTime(time);    }    /**     * 将token放入session 可以知道当前会话用户信息     * @param userToken     */    public static void setLocalUser(HttpSession session, UserToken userToken) {           session.setAttribute(SESSION_KEY, userToken);    }    /**     * 获取当前会话的用户token信息     * @return     */    public static UserToken getUserToken(HttpSession session) {           return (UserToken)session.getAttribute(SESSION_KEY);    }}
下面是一些会用到的其他类 一起提供了 使用时请注意。。。
/** * @author wangchunying * @Date 2020.12.06 */public class JwtException extends RuntimeException{       private JwtErrorCodeEnum code;    private String message;    private Object data;    public JwtException(){           super();    }    public JwtException(JwtErrorCodeEnum code, String messgae) {           this.code = code;        this.message = messgae;    }    public JwtException(JwtErrorCodeEnum code, String message, Object data) {           this.code = code;        this.message = message;        this.data = data;    }    public Object getData() {           return data;    }    public void setData(Object data) {           this.data = data;    }    public JwtErrorCodeEnum getCode() {           return code;    }    public void setCode(JwtErrorCodeEnum code) {           this.code = code;    }    @Override    public String getMessage() {           return message;    }    public void setMessage(String message) {           this.message = message;    }    @Override    public String toString() {           return "JwtException{" +                "code=" + code +                ", message='" + message + '\'' +                ", data=" + data +                '}';    }    /**     * sso code enum     */    public enum JwtErrorCodeEnum {           //解析格式错误        FORMAT(30001),        //内容错误        CONTENT(30002),        //内容前后不一致 确认被修改过        MODIFIED(30003),        //过期        EXPIRE(30004),        //其他错误        OTHER(-10);        Integer code;        JwtErrorCodeEnum(Integer code){               this.code = code;        }        public Integer getCode() {               return code;        }        @Override        public String toString() {               return "JwtErrorCodeEnum{" +                    "code=" + code +                    '}';        }    }}public class SsoException extends RuntimeException{       private Integer code;    private String message;    public SsoException(Integer code, String message) {           this.code = code;        this.message = message;    }    public Integer getCode() {           return code;    }    public void setCode(Integer code) {           this.code = code;    }    @Override    public String getMessage() {           return message;    }    public void setMessage(String message) {           this.message = message;    }    @Override    public String toString() {           return "SsoException{" +                "code=" + code +                ", message='" + message + '\'' +                '}';    }}import java.io.Serializable;/** * @author wangchunying * @Date 2020.12.02 * @Description */public class UserToken implements Serializable {       private static final long serialVersionUID = 1619189980427628544L;    private Integer userID;    private String userName;    //过期时间    private Long expireTime;    public Long getExpireTime() {           return expireTime;    }    public void setExpireTime(Long expireTime) {           this.expireTime = expireTime;    }    public Integer getUserID() {           return userID;    }    public void setUserID(Integer userID) {           this.userID = userID;    }    public String getUserName() {           return userName;    }    public void setUserName(String userName) {           this.userName = userName;    }    @Override    public String toString() {           return "UserToken{" +                "userID=" + userID +                ", userName='" + userName + '\'' +                ", expireTime='" + expireTime + '\'' +                '}';    }}
到此为止 所谓的jwt部分已经结束了,下面是如果你想用jwt作为共有接口拦截应该怎么做。是基于springboot来说的
import org.apache.commons.lang3.StringUtils;import org.springframework.http.HttpMethod;import org.springframework.stereotype.Component;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.annotation.Annotation;import java.lang.reflect.Method;/** * @author wangchunying * @Date 2020.12.07 * @Descrip 全局方法拦截 若方法不想使用此拦截器 请看JwtIgnore */public class AuthenticationInterceptor implements HandlerInterceptor {       @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {           //从http请求头中取出token        //如果本地测试嫌麻烦可以把下面三行注释打开 切记别提交//        if (1==1) {   //            return true;//        }        final String token = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);        //如果不是映射到方法,直接通过        if (!(handler instanceof HandlerMethod)) {               return true;        }        //如果是方法探测,直接通过        if (HttpMethod.OPTIONS.equals(request.getMethod())) {               response.setStatus(HttpServletResponse.SC_OK);            return true;        }        //如果方法有JwtIgnore注解,直接通过        HandlerMethod handlerMethod = (HandlerMethod) handler;        Method method = handlerMethod.getMethod();        if (method.isAnnotationPresent(JwtIgnore.class)) {               JwtIgnore jwtIgnore = method.getAnnotation(JwtIgnore.class);            if (jwtIgnore.value()) {                   return true;            }        }        //若测试接口 可以注释 打开token//        if (StringUtils.isBlank(token)) {   //            throw new SsoException(901, "token is null, please check again");//        }        //验证,并获取token内部信息        UserToken userToken = JwtTokenUtil.verifyToken(token);        //将token放入本地缓存        JwtTokenUtil.setLocalUser(request.getSession(), userToken);        return true;    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {           //方法结束后,移除缓存的token//        JwtTokenUtil.removeUserToken();    }}import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/** * @author wangchunying * @Date 2020.12.07 * @Descrip 跨域配置 */@Configurationpublic class WebMvcConfig implements WebMvcConfigurer {   //    /**//     * 重写父类提供的跨域请求处理的接口//     *///    @Override//    public void addCorsMappings(CorsRegistry registry) {   //        // 添加映射路径//        registry.addMapping("/**")//                // 放行哪些原始域//                .allowedOrigins("*")//                // 是否发送Cookie信息//                .allowCredentials(true)//                // 放行哪些原始域(请求方式)//                .allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")//                // 放行哪些原始域(头部信息)//                .allowedHeaders("*")//                // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)//                .exposedHeaders("Server","Content-Length", "Authorization", "Access-Token", "Access-Control-Allow-Origin","Access-Control-Allow-Credentials");//    }    /**     * 添加拦截器。如果你想在拦截器里面注入其他类。请交给spring管理 如不会 可问我 或自行查询     */    @Override    public void addInterceptors(InterceptorRegistry registry) {           //添加权限拦截器        registry.addInterceptor(new AuthenticationInterceptor()).addPathPatterns("/**").excludePathPatterns("/static/**");    }}这里我不想启动跨域 所以注销掉了import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author wangchunying * @Date 2020.12.07 * @Description: 如果你不想token 介入方法 请添加此注解 */@Target({   ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface JwtIgnore {       boolean value() default true;}

到此为止 jwt已经写完了,后面我会陆续更新息息相关的登录~

转载地址:http://erpm.baihongyu.com/

你可能感兴趣的文章
Java语言特点与学习
查看>>
夜光精讲 Opentcs 三大算法(十三)调度算法
查看>>
error TS1192: Module ‘“fs“‘ has no default export.
查看>>
BCGControlBar教程:应用向导
查看>>
MyEclipse教程:Web开发——部署并测试项目
查看>>
【更新】CLion v2018.3发布(六):VCS和插件
查看>>
文件服务器——src文件夹
查看>>
从零构建通讯器--4.4-4.5信号在创建线程的实战作用、write函数写入日志设置成不混乱、文件IO详解
查看>>
从零构建通讯器--5.2三次握手,telnet,wireshark
查看>>
如何判断两个浮点数是否相等?
查看>>
2017ccpc杭州 E. Master of Subgraph(点分治 + 树dp + bitset)
查看>>
2021牛客寒假算法基础集训营3
查看>>
苹果进军搜索,背后藏着什么“阳谋”?
查看>>
egg:如何在控制器中拿到前端传的参数
查看>>
MVC之修改
查看>>
struct 模块
查看>>
python之集合类型内置方法
查看>>
编程与编程语言分类
查看>>
【 UVA - 572 】 Oil Deposits (DFS水题)
查看>>
约瑟夫环问题
查看>>