
java手动实现JWT(我和别人的不一样)
发布日期:2021-05-06 19:33:06
浏览次数:11
分类:技术文章
本文共 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已经写完了,后面我会陆续更新息息相关的登录~
发表评论
最新留言
关注你微信了!
[***.104.42.241]2025年03月26日 22时07分40秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
6、ShardingSphere 之 读写分离
2019-03-03
C++ STL
2019-03-03
拓扑排序
2019-03-03
解方程
2019-03-03
练习赛 位运算 思维 思维
2019-03-03
Netty 粘包 拆包 | 史上最全解读
2019-03-03
protobuf + maven 爬坑记
2019-03-03
考了400分?不好意思,可能连这些“变态”学校的复试线都没够着!
2019-03-03
【调剂】211北京邮电大学2020年计算机学院硕士研究生招生缺额信息
2019-03-03
【招生目录和招生简章】浙江大学 华北电力大学 河南工业大学 福建师范大学...
2019-03-03
这些考研阅卷潜规则你知道几个?
2019-03-03
【考研英语】考研英语小作文万能模板(致歉信)
2019-03-03
【数据结构与算法】队列
2019-03-03
【研究生】PyTorch 1.0稳定版正式发布,并向开发者提供免费AI课程
2019-03-03
平均分392分!某985计算机专硕复试线暴涨!
2019-03-03
为何二战考生成功率远远大于应届?
2019-03-03
计算机专业【本科生】毕业还不如【专科生】?
2019-03-03
考研408联盟新添一所985!某知名大学专业课改用408!
2019-03-03
最有钱的大学是哪个?教育部直属高校公布2018年决算
2019-03-03
408的逆袭!武汉大学所有计算机/软件专业都改为408!
2019-03-03