赞
踩
我不会官方语言,我只会最简单的解释 json web token
简单点说 现在都流行前后端分离开发,如果需要一个前后端的通讯验证,最简单的cookie+session可以完成这个任务。但是会有问题昂,万一给你浏览cookie器禁了咋整,现在的用户才是老大哥不是,所以呢 就出现这个东西了
网上一搜一大片最多的就是。单点登录 + jwt。但是这两个东西不是一定要混为一谈的。jwt是jwt 简单点 他就是提供了一点token验证的方式,而不是一定要去作单点登录。(一开始我也混为一谈了,俺们领导天天跟我强调他俩不是一回事)
序篇:
最烦唠叨一大片没有实际应用的烦不烦,最开始还要强调一个问题,本文不是用的第三方 java-jwt 或者 jjwt 我们是完全自己实现的。和大众定义的jwt格式没啥太大关系。我们只是借鉴一下思想(因为领导说自己写加密方式比较安全)
正文开始:
我们查一下文档他说 jwt分为三段用 “.” 连接
第一段是个json。很简单很简单,就是告诉大家,我们在用jwt,也可以告诉他你用什么方式加密的 但是我并不想告诉他 哈哈哈
第二段是我们传输的实际业务内容,比如用户名呐,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; } }
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 + '\'' + '}'; } }
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 跨域配置 */ @Configuration public 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已经写完了,后面我会陆续更新息息相关的登录~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。