正文
大家可上我的git上下载源码,可以的话请给个星星,谢谢了。
代码结构图
代码,从上往下
AESSecretUtil:
package com.jwt.demo;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
/**
* @author : HJH
* @Description : AES加密工具类
* @date 2019/8/2 15:27
*/
public class AESSecretUtil {
/**
* 秘钥的大小
*/
private static final int KEYSIZE = 128;
/**
* @param data 待加密内容
* @param key 加密秘钥
* @deprecated AES加密
* @return 加密后的数组
*/
public static byte[] encrypt(String data, String key) {
if (StringUtils.isNotBlank(data)) {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
//选择一种固定算法,为了避免不同java实现的不同算法,生成不同的密钥,而导致解密失败
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(key.getBytes());
keyGenerator.init(KEYSIZE, random);
SecretKey secretKey = keyGenerator.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
byte[] byteContent = data.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);// 初始化
byte[] result = cipher.doFinal(byteContent);
return result; // 加密
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* @param data 待加密内容
* @param key 加密秘钥
* @deprecated AES加密
* @return 返回String
*/
public static String encryptToStr(String data, String key) {
return StringUtils.isNotBlank(data) ? parseByte2HexStr(encrypt(data, key)) : null;
}
/**
* @param data - 待解密字节数组
* @param key - 秘钥
* @deprecated : AES解密
*/
public static byte[] decrypt(byte[] data, String key) {
if (ArrayUtils.isNotEmpty(data)) {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
//选择一种固定算法,为了避免不同java实现的不同算法,生成不同的密钥,而导致解密失败
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(key.getBytes());
keyGenerator.init(KEYSIZE, random);
SecretKey secretKey = keyGenerator.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);// 初始化
byte[] result = cipher.doFinal(data);
return result;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* @param enCryptdata 待解密字节数组
* @param key 秘钥
* @deprecated : AES解密
* @return 返回String
*/
public static String decryptToStr(String enCryptdata, String key) {
return StringUtils.isNotBlank(enCryptdata) ? new String(decrypt(parseHexStr2Byte(enCryptdata), key)) : null;
}
/**
* @param buf - 二进制数组
* @deprecated : 将二进制转换成16进制
*/
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();
}
/**
* @param hexStr - 16进制字符串
* @deprecated : 将16进制转换为二进制
*/
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;
}
public static void main(String[] args) {
String ss = encryptToStr("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiIxMjMiLCJ1c2VyTmFtZSI6Ikp1ZHkiLCJleHAiOjE1MzI3Nzk2MjIsIm5iZiI6MTUzMjc3NzgyMn0.sIw_leDZwG0pJ8ty85Iecd_VXjObYutILNEwPUyeVSo", SecretConstant.DATAKEY);
System.out.println(ss);
System.out.println(decryptToStr(ss, SecretConstant.DATAKEY));
}
}
Base64Util
package com.jwt.demo;
/**
* @author : HJH
* @Description : Base64转码解码工具类
* @date : 2019/8/2 15:27
*/
public class Base64Util {
//字母表
private static char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray();
private static byte[] codes = new byte[256];
static {
for (int i = 0; i < 256; i++) {
codes[i] = -1;
// LoggerUtil.debug(i + "&" + codes[i] + " ");
}
for (int i = 'A'; i <= 'Z'; i++) {
codes[i] = (byte) (i - 'A');
// LoggerUtil.debug(i + "&" + codes[i] + " ");
}
for (int i = 'a'; i <= 'z'; i++) {
codes[i] = (byte) (26 + i - 'a');
// LoggerUtil.debug(i + "&" + codes[i] + " ");
}
for (int i = '0'; i <= '9'; i++) {
codes[i] = (byte) (52 + i - '0');
// LoggerUtil.debug(i + "&" + codes[i] + " ");
}
codes['+'] = 62;
codes['/'] = 63;
}
/**
* @param data
* @deprecated : 功能:编码字符串
*/
public static String encode(String data) {
return new String(encode(data.getBytes()));
}
/**
* @param data
* @deprecated : 功能:解码字符串
*/
public static String decode(String data) {
return new String(decode(data.toCharArray()));
}
/**
* @param data - 字节数组
* @deprecated : 功能:编码byte[]
*/
public static char[] encode(byte[] data) {
char[] out = new char[((data.length + 2) / 3) * 4];
for (int i = 0, index = 0; i < data.length; i += 3, index += 4) {
boolean quad = false;
boolean trip = false;
int val = (0xFF & (int) data[i]);
val <<= 8;
if ((i + 1) < data.length) {
val |= (0xFF & (int) data[i + 1]);
trip = true;
}
val <<= 8;
if ((i + 2) < data.length) {
val |= (0xFF & (int) data[i + 2]);
quad = true;
}
out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];
val >>= 6;
out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)];
val >>= 6;
out[index + 1] = alphabet[val & 0x3F];
val >>= 6;
out[index + 0] = alphabet[val & 0x3F];
}
return out;
}
/**
* @param data - 字节数组
* @deprecated : 功能:解码字节数组
*/
public static byte[] decode(char[] data) {
int tempLen = data.length;
for (int ix = 0; ix < data.length; ix++) {
if ((data[ix] > 255) || codes[data[ix]] < 0) {
--tempLen; // ignore non-valid chars and padding
}
}
// calculate required length:
// -- 3 bytes for every 4 valid base64 chars
// -- plus 2 bytes if there are 3 extra base64 chars,
// or plus 1 byte if there are 2 extra.
int len = (tempLen / 4) * 3;
if ((tempLen % 4) == 3) {
len += 2;
}
if ((tempLen % 4) == 2) {
len += 1;
}
byte[] out = new byte[len];
int shift = 0; // # of excess bits stored in accum
int accum = 0; // excess bits
int index = 0;
// we now go through the entire array (NOT using the 'tempLen' value)
for (int ix = 0; ix < data.length; ix++) {
int value = (data[ix] > 255) ? -1 : codes[data[ix]];
if (value >= 0) { // skip over non-code
accum <<= 6; // bits shift up by 6 each time thru
shift += 6; // loop, with new bits being put in
accum |= value; // at the bottom.
if (shift >= 8) { // whenever there are 8 or more shifted in,
shift -= 8; // write them out (from the top, leaving any
out[index++] = // excess at the bottom for next iteration.
(byte) ((accum >> shift) & 0xff);
}
}
}
// if there is STILL something wrong we just have to throw up now!
if (index != out.length) {
throw new Error("Miscalculated data length (wrote " + index
+ " instead of " + out.length + ")");
}
return out;
}
}
JwtHelper
package com.jwt.demo;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author HJH
* @Description : JWT工具类
* <br/>参考官网:https://jwt.io/
* <br/>JWT的数据结构为:A.B.C三部分数据,由字符点"."分割成三部分数据
* <br/>A-header头信息
* <br/>B-payload 有效负荷 一般包括:已注册信息(registered claims),公开数据(public claims),私有数据(private claims)
* <br/>C-signature 签名信息 是将header和payload进行加密生成的
* @date 2019/8/2 15:27
*/
public class JwtHelper {
private static Logger logger = LoggerFactory.getLogger(JwtHelper.class);
/**
* @param userId - 用户编号
* @param userName - 用户名
* @param identities - 客户端信息(变长参数),目前包含浏览器信息,用于客户端拦截器校验,防止跨域非法访问
* @Author: Helon
* @deprecated 生成JWT字符串
* <br/>格式:A.B.C
* <br/>A-header头信息
* <br/>B-payload 有效负荷
* <br/>C-signature 签名信息 是将header和payload进行加密生成的
* @Data: 2018/7/28 19:26
* @Modified By:
*/
public static String generateJWT(String userId, String userName, String... identities) {
//签名算法,选择SHA-256
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//获取当前系统时间
long nowTimeMillis = System.currentTimeMillis();
Date now = new Date(nowTimeMillis);
//将BASE64SECRET常量字符串使用base64解码成字节数组
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SecretConstant.BASE64SECRET);
//使用HmacSHA256签名算法生成一个HS256的签名秘钥Key
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//添加构成JWT的参数
Map<String, Object> headMap = new HashMap<>();
/*
Header
{
"alg": "HS256",
"typ": "JWT"
}
*/
headMap.put("alg", SignatureAlgorithm.HS256.getValue());
headMap.put("typ", "JWT");
JwtBuilder builder = Jwts.builder().setHeader(headMap)
/*
Payload
{
"userId": "1234567890",
"userName": "John Doe",
}
*/
//加密后的客户编号
.claim("userId", AESSecretUtil.encryptToStr(userId, SecretConstant.DATAKEY))
//客户名称
.claim("userName", userName)
//客户端浏览器信息
.claim("userAgent", identities[0])
//Signature
.signWith(signatureAlgorithm, signingKey);
//添加Token过期时间
if (SecretConstant.EXPIRESSECOND >= 0) {
long expMillis = nowTimeMillis + SecretConstant.EXPIRESSECOND;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate).setNotBefore(now);
}
return builder.compact();
}
/**
* @param jsonWebToken - 页面传过来的token
* @deprecated: 解析JWT
* @return Claims对象
*/
public static Claims parseJWT(String jsonWebToken) {
Claims claims = null;
try {
if (StringUtils.isNotBlank(jsonWebToken)) {
//解析jwt
claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(SecretConstant.BASE64SECRET))
.parseClaimsJws(jsonWebToken).getBody();
} else {
logger.warn("[JWTHelper]-json web token 为空");
}
} catch (Exception e) {
logger.error("[JWTHelper]-JWT解析异常:可能因为token已经超时或非法token");
}
return claims;
}
/**
* @param jsonWebToken - 页面传过来的token
* @deprecated: 校验JWT是否有效
* @return json字符串的demo:<br/>
* {"freshToken":"A.B.C","userName":"Judy","userId":"123", "userAgent":"xxxx"}
* <br/>freshToken-刷新后的jwt
* <br/>userName-客户名称
* <br/>userId-客户编号
* <br/>userAgent-客户端浏览器信息
*/
public static String validateLogin(String jsonWebToken) {
Map<String, Object> retMap = null;
Claims claims = parseJWT(jsonWebToken);
if (claims != null) {
//解密客户编号
String decryptUserId = AESSecretUtil.decryptToStr((String) claims.get("userId"), SecretConstant.DATAKEY);
retMap = new HashMap<>();
//加密后的客户编号
retMap.put("userId", decryptUserId);
//客户名称
retMap.put("userName", claims.get("userName"));
//客户端浏览器信息
retMap.put("userAgent", claims.get("userAgent"));
//刷新JWT
retMap.put("freshToken", generateJWT(decryptUserId, (String) claims.get("userName"), (String) claims.get("userAgent"), (String) claims.get("domainName")));
} else {
logger.warn("[JWTHelper]-JWT解析出claims为空");
}
return retMap != null ? JSONObject.toJSONString(retMap) : null;
}
public static void main(String[] args) {
String jsonWebKey = generateJWT("123", "Judy",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36");
System.out.println(jsonWebKey);
Claims claims = parseJWT(jsonWebKey);
System.out.println(claims);
System.out.println(validateLogin(jsonWebKey));
}
}
SecretConstant
package com.jwt.demo;
/**
* @author HJH
* @Description : JWT使用常量值
* @date 2019/8/2 15:27
*/
public class SecretConstant {
//签名秘钥
public static final String BASE64SECRET = "ZW]4l5JH[m6Lm)LaQEjpb!4E0lRaG(";
//超时毫秒数(默认30分钟)
public static final int EXPIRESSECOND = 1800000;
//用于JWT加密的密匙
public static final String DATAKEY = "u^3y6SPER41jm*fn";
}
ValidateLoginInterceptor
package com.jwt.demo;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author HJH
* @Description : 校验是否登录拦截器
* @date 2019/8/2 14:45
*/
@Slf4j
public class ValidateLoginInterceptor implements HandlerInterceptor {
private String SESSION_CUSTOMER_NO_KEY = "session_customer_no_key";
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
//首先从请求头中获取jwt串,与页面约定好存放jwt值的请求头属性名为User-Token
String jwt = httpServletRequest.getHeader("User-Token");
log.info("[登录校验拦截器]-从header中获取的jwt为:{}", jwt);
//判断jwt是否有效
if(StringUtils.isNotBlank(jwt)){
//校验jwt是否有效,有效则返回json信息,无效则返回空
String retJson = JwtHelper.validateLogin(jwt);
log.info("[登录校验拦截器]-校验JWT有效性返回结果:{}", retJson);
//retJSON为空则说明jwt超时或非法
if(StringUtils.isNotBlank(retJson)){
JSONObject jsonObject = JSONObject.parseObject(retJson);
//校验客户端信息
String userAgent = httpServletRequest.getHeader("User-Agent");
if (userAgent.equals(jsonObject.getString("userAgent"))) {
//获取刷新后的jwt值,设置到响应头中
httpServletResponse.setHeader("User-Token", jsonObject.getString("freshToken"));
//将客户编号设置到session中
httpServletRequest.getSession().setAttribute(SESSION_CUSTOMER_NO_KEY, jsonObject.getString("userId"));
return true;
}else{
log.warn("[登录校验拦截器]-客户端浏览器信息与JWT中存的浏览器信息不一致,重新登录。当前浏览器信息:{}", userAgent);
}
}else {
log.warn("[登录校验拦截器]-JWT非法或已超时,重新登录");
}
}
//输出响应流
JSONObject jsonObject = new JSONObject();
jsonObject.put("hmac", "");
jsonObject.put("status", "");
jsonObject.put("code", "4007");
jsonObject.put("msg", "未登录");
jsonObject.put("data", "");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
httpServletResponse.getOutputStream().write(jsonObject.toJSONString().getBytes("UTF-8"));
return false;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jwt</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<java-jwt>3.3.0</java-jwt>
<jsonwebtoken.jjwt>0.9.0</jsonwebtoken.jjwt>
<alibaba.fastjson>1.2.47</alibaba.fastjson>
<apache.commons-lang3>3.7</apache.commons-lang3>
<slf4j.version>1.6.4</slf4j.version>
<junit.version>4.12</junit.version>
<apache.commons-codec>1.11</apache.commons-codec>
<lombok.version>1.16.16</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--jwt依赖包-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${java-jwt}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jsonwebtoken.jjwt}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${alibaba.fastjson}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${apache.commons-lang3}</version>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--Apache Commons Codec-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${apache.commons-codec}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意
因为使用了@Slf4j
注解,需要安装Lombok
结语
代码中并没有存token,请根据实际情况处理token。