fix bugs
This commit is contained in:
40
blade-starter-jwt/pom.xml
Normal file
40
blade-starter-jwt/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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>
|
||||
<artifactId>BladeX-Tool</artifactId>
|
||||
<groupId>org.springblade</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>blade-starter-jwt</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<version>${project.parent.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<!-- Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
</dependency>
|
||||
<!-- Auto -->
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-auto</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.jwt;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.util.annotation.Nullable;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* JwtCrypto
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class JwtCrypto {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
public static final String BLADE_TOKEN_CRYPTO_KEY = "blade.token.crypto-key";
|
||||
|
||||
|
||||
/**
|
||||
* Base64加密
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param aesTextKey 文本密钥
|
||||
* @return {String}
|
||||
*/
|
||||
public static String encryptToString(String content, String aesTextKey) {
|
||||
return encodeToString(encrypt(content, aesTextKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64加密
|
||||
*
|
||||
* @param content 内容
|
||||
* @param aesTextKey 文本密钥
|
||||
* @return {String}
|
||||
*/
|
||||
public static String encryptToString(byte[] content, String aesTextKey) {
|
||||
return encodeToString(encrypt(content, aesTextKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param aesTextKey 文本密钥
|
||||
* @return byte[]
|
||||
*/
|
||||
public static byte[] encrypt(String content, String aesTextKey) {
|
||||
return encrypt(content.getBytes(DEFAULT_CHARSET), aesTextKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param charset 编码
|
||||
* @param aesTextKey 文本密钥
|
||||
* @return byte[]
|
||||
*/
|
||||
public static byte[] encrypt(String content, Charset charset, String aesTextKey) {
|
||||
return encrypt(content.getBytes(charset), aesTextKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param content 内容
|
||||
* @param aesTextKey 文本密钥
|
||||
* @return byte[]
|
||||
*/
|
||||
public static byte[] encrypt(byte[] content, String aesTextKey) {
|
||||
return encrypt(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64解密
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param aesTextKey 文本密钥
|
||||
* @return {String}
|
||||
*/
|
||||
@Nullable
|
||||
public static String decryptToString(@Nullable String content, @Nullable String aesTextKey) {
|
||||
if (!StringUtils.hasText(content) || !StringUtils.hasText(aesTextKey)) {
|
||||
return null;
|
||||
}
|
||||
byte[] hexBytes = decrypt(decode(content.getBytes(DEFAULT_CHARSET)), aesTextKey);
|
||||
return new String(hexBytes, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param content 内容
|
||||
* @param aesTextKey 文本密钥
|
||||
* @return byte[]
|
||||
*/
|
||||
public static byte[] decrypt(byte[] content, String aesTextKey) {
|
||||
return decrypt(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param content 内容
|
||||
* @param aesKey 密钥
|
||||
* @return byte[]
|
||||
*/
|
||||
public static byte[] encrypt(byte[] content, byte[] aesKey) {
|
||||
return aes(Pkcs7Encoder.encode(content), aesKey, Cipher.ENCRYPT_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param encrypted 内容
|
||||
* @param aesKey 密钥
|
||||
* @return byte[]
|
||||
*/
|
||||
public static byte[] decrypt(byte[] encrypted, byte[] aesKey) {
|
||||
return Pkcs7Encoder.decode(aes(encrypted, aesKey, Cipher.DECRYPT_MODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* ase加密
|
||||
*
|
||||
* @param encrypted 内容
|
||||
* @param aesKey 密钥
|
||||
* @param mode 模式
|
||||
* @return byte[]
|
||||
*/
|
||||
@SneakyThrows
|
||||
private static byte[] aes(byte[] encrypted, byte[] aesKey, int mode) {
|
||||
Assert.isTrue(aesKey.length == 32, "IllegalAesKey, aesKey's length must be 32");
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
|
||||
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
|
||||
cipher.init(mode, keySpec, iv);
|
||||
return cipher.doFinal(encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-encode the given byte array to a String.
|
||||
*
|
||||
* @param src the original byte array
|
||||
* @return the encoded byte array as a UTF-8 String
|
||||
*/
|
||||
public static String encodeToString(byte[] src) {
|
||||
if (src.length == 0) {
|
||||
return "";
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(src);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-decode the given byte array.
|
||||
*
|
||||
* @param src the encoded byte array
|
||||
* @return the original byte array
|
||||
*/
|
||||
public static byte[] decode(byte[] src) {
|
||||
if (src.length == 0) {
|
||||
return src;
|
||||
}
|
||||
return Base64.getDecoder().decode(src);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供基于PKCS7算法的加解密接口.
|
||||
*/
|
||||
private static class Pkcs7Encoder {
|
||||
private static final int BLOCK_SIZE = 32;
|
||||
|
||||
private static byte[] encode(byte[] src) {
|
||||
int count = src.length;
|
||||
// 计算需要填充的位数
|
||||
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
|
||||
// 获得补位所用的字符
|
||||
byte pad = (byte) (amountToPad & 0xFF);
|
||||
byte[] pads = new byte[amountToPad];
|
||||
Arrays.fill(pads, pad);
|
||||
int length = count + amountToPad;
|
||||
byte[] dest = new byte[length];
|
||||
System.arraycopy(src, 0, dest, 0, count);
|
||||
System.arraycopy(pads, 0, dest, count, amountToPad);
|
||||
return dest;
|
||||
}
|
||||
|
||||
private static byte[] decode(byte[] decrypted) {
|
||||
int pad = decrypted[decrypted.length - 1];
|
||||
if (pad < 1 || pad > BLOCK_SIZE) {
|
||||
pad = 0;
|
||||
}
|
||||
if (pad > 0) {
|
||||
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
|
||||
}
|
||||
return decrypted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.jwt;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.Getter;
|
||||
import org.springblade.core.jwt.enums.SingleLevel;
|
||||
import org.springblade.core.jwt.props.JwtProperties;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Jwt工具类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class JwtUtil {
|
||||
|
||||
/**
|
||||
* token基础配置
|
||||
*/
|
||||
public static String BEARER = "bearer";
|
||||
public static String CRYPTO = "crypto";
|
||||
public static Integer AUTH_LENGTH = 7;
|
||||
|
||||
/**
|
||||
* token保存至redis的key
|
||||
*/
|
||||
private static final String ACCESS_TOKEN_CACHE = "blade:token";
|
||||
private static final String REFRESH_TOKEN_CACHE = "blade:refreshToken";
|
||||
private static final String TOKEN_KEY = "token:state:";
|
||||
|
||||
/**
|
||||
* jwt配置
|
||||
*/
|
||||
@Getter
|
||||
private static JwtProperties jwtProperties;
|
||||
|
||||
/**
|
||||
* redis工具
|
||||
*/
|
||||
@Getter
|
||||
private static RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
public static void setJwtProperties(JwtProperties properties) {
|
||||
if (JwtUtil.jwtProperties == null) {
|
||||
JwtUtil.jwtProperties = properties;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
|
||||
if (JwtUtil.redisTemplate == null) {
|
||||
JwtUtil.redisTemplate = redisTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名加密
|
||||
*/
|
||||
public static String getBase64Security() {
|
||||
return Base64.getEncoder().encodeToString(getJwtProperties().getSignKey().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求传递的token串
|
||||
*
|
||||
* @param auth token
|
||||
* @return String
|
||||
*/
|
||||
public static String getToken(String auth) {
|
||||
if (isBearer(auth) || isCrypto(auth)) {
|
||||
return auth.substring(AUTH_LENGTH);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断token类型为bearer
|
||||
*
|
||||
* @param auth token
|
||||
* @return String
|
||||
*/
|
||||
public static Boolean isBearer(String auth) {
|
||||
if ((auth != null) && (auth.length() > AUTH_LENGTH)) {
|
||||
String headStr = auth.substring(0, 6).toLowerCase();
|
||||
return headStr.compareTo(BEARER) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断token类型为crypto
|
||||
*
|
||||
* @param auth token
|
||||
* @return String
|
||||
*/
|
||||
public static Boolean isCrypto(String auth) {
|
||||
if ((auth != null) && (auth.length() > AUTH_LENGTH)) {
|
||||
String headStr = auth.substring(0, 6).toLowerCase();
|
||||
return headStr.compareTo(CRYPTO) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析jsonWebToken
|
||||
*
|
||||
* @param jsonWebToken token串
|
||||
* @return Claims
|
||||
*/
|
||||
public static Claims parseJWT(String jsonWebToken) {
|
||||
try {
|
||||
SecretKey secretKey = Keys.hmacShaKeyFor(Base64.getDecoder().decode(getBase64Security()));
|
||||
return Jwts.parser()
|
||||
.verifyWith(secretKey)
|
||||
.build()
|
||||
.parseSignedClaims(jsonWebToken)
|
||||
.getPayload();
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保存在redis的accessToken
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param userId 用户id
|
||||
* @param accessToken accessToken
|
||||
* @return accessToken
|
||||
*/
|
||||
public static String getAccessToken(String tenantId, String userId, String accessToken) {
|
||||
return getAccessToken(tenantId, null, userId, accessToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保存在redis的accessToken
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
* @param accessToken accessToken
|
||||
* @return accessToken
|
||||
*/
|
||||
public static String getAccessToken(String tenantId, String clientId, String userId, String accessToken) {
|
||||
return String.valueOf(getRedisTemplate().opsForValue().get(getAccessTokenKey(tenantId, clientId, userId, accessToken)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加accessToken至redis
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param userId 用户id
|
||||
* @param accessToken accessToken
|
||||
* @param expire 过期时间
|
||||
*/
|
||||
public static void addAccessToken(String tenantId, String userId, String accessToken, int expire) {
|
||||
addAccessToken(tenantId, null, userId, accessToken, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加accessToken至redis
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
* @param accessToken accessToken
|
||||
* @param expire 过期时间
|
||||
*/
|
||||
public static void addAccessToken(String tenantId, String clientId, String userId, String accessToken, int expire) {
|
||||
getRedisTemplate().delete(getAccessTokenKey(tenantId, clientId, userId, accessToken));
|
||||
getRedisTemplate().opsForValue().set(getAccessTokenKey(tenantId, clientId, userId, accessToken), accessToken, expire, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除保存在redis的accessToken
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param userId 用户id
|
||||
*/
|
||||
public static void removeAccessToken(String tenantId, String userId) {
|
||||
removeAccessToken(tenantId, userId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除保存在redis的accessToken
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param userId 用户id
|
||||
* @param accessToken accessToken
|
||||
*/
|
||||
public static void removeAccessToken(String tenantId, String userId, String accessToken) {
|
||||
removeAccessToken(tenantId, null, userId, accessToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除保存在redis的accessToken
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
* @param accessToken accessToken
|
||||
*/
|
||||
public static void removeAccessToken(String tenantId, String clientId, String userId, String accessToken) {
|
||||
getRedisTemplate().delete(getAccessTokenKey(tenantId, clientId, userId, accessToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保存在redis的refreshToken
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param userId 用户id
|
||||
* @return refreshToken
|
||||
*/
|
||||
public static String getRefreshToken(String tenantId, String userId) {
|
||||
return getRefreshToken(tenantId, null, userId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保存在redis的refreshToken
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param userId 用户id
|
||||
* @param refreshToken refreshToken
|
||||
* @return refreshToken
|
||||
*/
|
||||
public static String getRefreshToken(String tenantId, String userId, String refreshToken) {
|
||||
return getRefreshToken(tenantId, null, userId, refreshToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保存在redis的refreshToken
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
* @param refreshToken refreshToken
|
||||
* @return accessToken
|
||||
*/
|
||||
public static String getRefreshToken(String tenantId, String clientId, String userId, String refreshToken) {
|
||||
return String.valueOf(getRedisTemplate().opsForValue().get(getRefreshTokenKey(tenantId, clientId, userId, refreshToken)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加refreshToken至redis
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param userId 用户id
|
||||
* @param refreshToken refreshToken
|
||||
* @param expire 过期时间
|
||||
*/
|
||||
public static void addRefreshToken(String tenantId, String userId, String refreshToken, int expire) {
|
||||
addRefreshToken(tenantId, null, userId, refreshToken, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加refreshToken至redis
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
* @param refreshToken refreshToken
|
||||
* @param expire 过期时间
|
||||
*/
|
||||
public static void addRefreshToken(String tenantId, String clientId, String userId, String refreshToken, int expire) {
|
||||
getRedisTemplate().delete(getRefreshTokenKey(tenantId, clientId, userId, refreshToken));
|
||||
getRedisTemplate().opsForValue().set(getRefreshTokenKey(tenantId, clientId, userId, refreshToken), refreshToken, expire, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除保存在refreshToken的token
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param userId 用户id
|
||||
*/
|
||||
public static void removeRefreshToken(String tenantId, String userId) {
|
||||
removeRefreshToken(tenantId, null, userId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除保存在refreshToken的token
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
*/
|
||||
public static void removeRefreshToken(String tenantId, String clientId, String userId) {
|
||||
removeRefreshToken(tenantId, clientId, userId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除保存在refreshToken的token
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
* @param refreshToken refreshToken
|
||||
*/
|
||||
public static void removeRefreshToken(String tenantId, String clientId, String userId, String refreshToken) {
|
||||
getRedisTemplate().delete(getRefreshTokenKey(tenantId, clientId, userId, refreshToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取accessToken索引
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param userId 用户id
|
||||
* @param accessToken accessToken
|
||||
* @return token索引
|
||||
*/
|
||||
public static String getAccessTokenKey(String tenantId, String userId, String accessToken) {
|
||||
return getAccessTokenKey(tenantId, null, userId, accessToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取accessToken索引
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
* @param accessToken accessToken
|
||||
* @return token索引
|
||||
*/
|
||||
public static String getAccessTokenKey(String tenantId, String clientId, String userId, String accessToken) {
|
||||
return getTokenKey(ACCESS_TOKEN_CACHE, tenantId, clientId, userId, accessToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取refreshToken索引
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param userId 用户id
|
||||
* @return token索引
|
||||
*/
|
||||
public static String getRefreshTokenKey(String tenantId, String userId) {
|
||||
return getRefreshTokenKey(tenantId, null, userId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取refreshToken索引
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
* @return token索引
|
||||
*/
|
||||
public static String getRefreshTokenKey(String tenantId, String clientId, String userId) {
|
||||
return getRefreshTokenKey(tenantId, clientId, userId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取refreshToken索引
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
* @param refreshToken refreshToken
|
||||
* @return token索引
|
||||
*/
|
||||
public static String getRefreshTokenKey(String tenantId, String clientId, String userId, String refreshToken) {
|
||||
return getTokenKey(REFRESH_TOKEN_CACHE, tenantId, clientId, userId, refreshToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取通用Token索引
|
||||
*
|
||||
* @param tokenCache 缓存名
|
||||
* @param tenantId 租户id
|
||||
* @param clientId 应用id
|
||||
* @param userId 用户id
|
||||
* @param tokenValue tokenValue
|
||||
* @return token索引
|
||||
*/
|
||||
public static String getTokenKey(String tokenCache, String tenantId, String clientId, String userId, String tokenValue) {
|
||||
String key = tenantId.concat(":").concat(tokenCache).concat("::").concat(TOKEN_KEY);
|
||||
if (getJwtProperties().getSingle() || !StringUtils.hasText(tokenValue)) {
|
||||
if (getJwtProperties().getSingleLevel() == SingleLevel.CLIENT && StringUtils.hasText(clientId)) {
|
||||
key = key.concat(clientId).concat(":");
|
||||
}
|
||||
return key.concat(userId);
|
||||
} else {
|
||||
return key.concat(tokenValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.jwt.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.jwt.JwtUtil;
|
||||
import org.springblade.core.jwt.props.JwtProperties;
|
||||
import org.springblade.core.jwt.serializer.JwtRedisKeySerializer;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||
|
||||
/**
|
||||
* Jwt配置类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@AutoConfiguration(after = JwtRedisConfiguration.class)
|
||||
@EnableConfigurationProperties({JwtProperties.class})
|
||||
public class JwtConfiguration implements SmartInitializingSingleton {
|
||||
|
||||
private final JwtProperties jwtProperties;
|
||||
private final RedisConnectionFactory redisConnectionFactory;
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
// redisTemplate 实例化
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
JwtRedisKeySerializer redisKeySerializer = new JwtRedisKeySerializer();
|
||||
JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
|
||||
// key 序列化
|
||||
redisTemplate.setKeySerializer(redisKeySerializer);
|
||||
redisTemplate.setHashKeySerializer(redisKeySerializer);
|
||||
// value 序列化
|
||||
redisTemplate.setValueSerializer(jdkSerializationRedisSerializer);
|
||||
redisTemplate.setHashValueSerializer(jdkSerializationRedisSerializer);
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
JwtUtil.setJwtProperties(jwtProperties);
|
||||
JwtUtil.setRedisTemplate(redisTemplate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.jwt.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* RedisTemplate 配置
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Order
|
||||
@EnableCaching
|
||||
@AutoConfiguration(after = RedisAutoConfiguration.class)
|
||||
public class JwtRedisConfiguration {
|
||||
|
||||
@Bean("redisCacheManager")
|
||||
@ConditionalOnMissingBean(name = "redisCacheManager")
|
||||
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.entryTtl(Duration.ofHours(1));
|
||||
return RedisCacheManager
|
||||
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
|
||||
.cacheDefaults(redisCacheConfiguration).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.jwt.constant;
|
||||
|
||||
/**
|
||||
* Jwt常量
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface JwtConstant {
|
||||
|
||||
/**
|
||||
* 默认key
|
||||
*/
|
||||
String DEFAULT_SECRET_KEY = "bladexisapowerfulmicroservicearchitectureupgradedandoptimizedfromacommercialproject";
|
||||
|
||||
/**
|
||||
* key安全长度,具体见:https://tools.ietf.org/html/rfc7518#section-3.2
|
||||
*/
|
||||
int SECRET_KEY_LENGTH = 32;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.jwt.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 单人模式平台枚举
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum SingleLevel {
|
||||
|
||||
/**
|
||||
* 全平台仅可登录一人
|
||||
*/
|
||||
ALL("all", 1),
|
||||
|
||||
/**
|
||||
* 各应用仅可登录一人
|
||||
*/
|
||||
CLIENT("client", 2),
|
||||
;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
final String name;
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
final int level;
|
||||
|
||||
/**
|
||||
* 匹配枚举值
|
||||
*
|
||||
* @param name 名称
|
||||
* @return SingleLevel
|
||||
*/
|
||||
public static SingleLevel of(String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
SingleLevel[] values = SingleLevel.values();
|
||||
for (SingleLevel singleLevel : values) {
|
||||
if (singleLevel.name.equals(name)) {
|
||||
return singleLevel;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.jwt.props;
|
||||
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import lombok.Data;
|
||||
import org.springblade.core.jwt.constant.JwtConstant;
|
||||
import org.springblade.core.jwt.enums.SingleLevel;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* JWT配置
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("blade.token")
|
||||
public class JwtProperties {
|
||||
|
||||
/**
|
||||
* token是否有状态
|
||||
*/
|
||||
private Boolean state = Boolean.FALSE;
|
||||
|
||||
/**
|
||||
* 是否只可同时在线一人
|
||||
*/
|
||||
private Boolean single = Boolean.FALSE;
|
||||
|
||||
/**
|
||||
* 单人模式级别(ALL 全部平台只能有一个,CLIENT 不同客户端只能有一个)
|
||||
*/
|
||||
private SingleLevel singleLevel = SingleLevel.ALL;
|
||||
|
||||
/**
|
||||
* token签名
|
||||
*/
|
||||
private String signKey = "";
|
||||
|
||||
/**
|
||||
* token密钥
|
||||
*/
|
||||
private String cryptoKey = "";
|
||||
|
||||
/**
|
||||
* 获取签名规则
|
||||
*/
|
||||
public String getSignKey() {
|
||||
if (this.signKey.length() < JwtConstant.SECRET_KEY_LENGTH) {
|
||||
throw new JwtException("请配置 blade.token.sign-key 的值, 长度32位以上");
|
||||
}
|
||||
return this.signKey;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: DreamLu (596392912@qq.com)
|
||||
*/
|
||||
|
||||
package org.springblade.core.jwt.serializer;
|
||||
|
||||
import org.springframework.cache.interceptor.SimpleKey;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 将redis key序列化为字符串
|
||||
*
|
||||
* <p>
|
||||
* spring cache中的简单基本类型直接使用 StringRedisSerializer 会有问题
|
||||
* </p>
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
public class JwtRedisKeySerializer implements RedisSerializer<Object> {
|
||||
private final Charset charset;
|
||||
private final ConversionService converter;
|
||||
|
||||
public JwtRedisKeySerializer() {
|
||||
this(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public JwtRedisKeySerializer(Charset charset) {
|
||||
Objects.requireNonNull(charset, "Charset must not be null");
|
||||
this.charset = charset;
|
||||
this.converter = DefaultConversionService.getSharedInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(byte[] bytes) {
|
||||
// redis keys 会用到反序列化
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
return new String(bytes, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(Object object) {
|
||||
String key;
|
||||
if (object instanceof SimpleKey) {
|
||||
key = "";
|
||||
} else if (object instanceof String) {
|
||||
key = (String) object;
|
||||
} else {
|
||||
key = converter.convert(object, String.class);
|
||||
}
|
||||
return Objects.requireNonNull(key).getBytes(this.charset);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user