feat: 新增 sa-token-serializer-features 插件,用于实现各种形式的自定义字符集序列化方案

This commit is contained in:
click33 2025-02-25 03:59:51 +08:00
parent d1be8365c4
commit a94fe35e65
10 changed files with 455 additions and 0 deletions

View File

@ -60,6 +60,11 @@
<artifactId>sa-token-redis-template</artifactId>
<version>${sa-token.version}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-serializer-features</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>

View File

@ -32,6 +32,7 @@
<module>sa-token-sso</module>
<module>sa-token-oauth2</module>
<module>sa-token-redisson</module>
<module>sa-token-serializer-features</module>
<!-- SpringBoot 环境插件 -->
<module>sa-token-redis-template</module>

View File

@ -0,0 +1,29 @@
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-plugin</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<name>sa-token-serializer-features</name>
<artifactId>sa-token-serializer-features</artifactId>
<description>sa-token-serializer-features</description>
<dependencies>
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,34 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.plugin;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.serializer.SaSerializerForBase64UseTianGan;
/**
* SaToken 插件安装自定义序列化器
*
* @author click33
* @since 1.41.0
*/
public class SaTokenPluginForSerializerFeatures implements SaTokenPlugin {
@Override
public void install() {
SaManager.setSaSerializerTemplate(new SaSerializerForBase64UseTianGan());
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.serializer;
import cn.dev33.satoken.serializer.impl.SaSerializerTemplateForJdk;
import java.util.HashMap;
import java.util.Map;
/**
* 序列化器base64 算法采用自定义字符集
*
* @author click33
* @since 1.41.0
*/
public class SaSerializerForBase64UseCustomCharacters implements SaSerializerTemplateForJdk {
// 自定义字符集需确保包含64个中文字符
public String CUSTOM_CHARS;
// 填充符确保不在字符集中
public char PAD_CHAR;
public SaSerializerForBase64UseCustomCharacters(String customChars, char padChar) {
if (customChars.length() != 64) {
throw new IllegalArgumentException("自定义字符集长度必须为64");
}
if (customChars.indexOf(padChar) != -1) {
throw new IllegalArgumentException("填充符不能在自定义字符集中");
}
this.CUSTOM_CHARS = customChars;
this.PAD_CHAR = padChar;
}
@Override
public String bytesToString(byte[] data) {
StringBuilder encoded = new StringBuilder();
int length = data.length;
int i = 0;
// 处理完整的3字节组
while (i < length - 2) {
int byte1 = data[i++] & 0xFF;
int byte2 = data[i++] & 0xFF;
int byte3 = data[i++] & 0xFF;
int combined = (byte1 << 16) | (byte2 << 8) | byte3;
encoded.append(CUSTOM_CHARS.charAt((combined >> 18) & 0x3F));
encoded.append(CUSTOM_CHARS.charAt((combined >> 12) & 0x3F));
encoded.append(CUSTOM_CHARS.charAt((combined >> 6) & 0x3F));
encoded.append(CUSTOM_CHARS.charAt(combined & 0x3F));
}
// 处理剩余字节01或2个
int remaining = length - i;
if (remaining > 0) {
int byte1 = data[i++] & 0xFF;
int byte2 = remaining > 1 ? data[i++] & 0xFF : 0;
int combined = (byte1 << 16) | (byte2 << 8);
encoded.append(CUSTOM_CHARS.charAt((combined >> 18) & 0x3F));
encoded.append(CUSTOM_CHARS.charAt((combined >> 12) & 0x3F));
if (remaining == 1) {
encoded.append(PAD_CHAR).append(PAD_CHAR);
} else {
encoded.append(CUSTOM_CHARS.charAt((combined >> 6) & 0x3F));
encoded.append(PAD_CHAR);
}
}
return encoded.toString();
}
@Override
public byte[] stringToBytes(String encodedStr) {
if (CUSTOM_CHARS.length() != 64) {
throw new IllegalStateException("自定义字符集长度必须为64");
}
Map<Character, Integer> charMap = new HashMap<>();
for (int i = 0; i < CUSTOM_CHARS.length(); i++) {
charMap.put(CUSTOM_CHARS.charAt(i), i);
}
int length = encodedStr.length();
if (length % 4 != 0) {
throw new IllegalArgumentException("编码字符串长度无效");
}
// 计算填充符数量
int paddingCount = 0;
for (int i = length - 1; i >= 0 && encodedStr.charAt(i) == PAD_CHAR; i--) {
paddingCount++;
}
int numGroups = length / 4;
byte[] decoded = new byte[numGroups * 3 - paddingCount];
int decodedIndex = 0;
for (int group = 0; group < numGroups; group++) {
int[] indices = new int[4];
for (int j = 0; j < 4; j++) {
char c = encodedStr.charAt(group * 4 + j);
if (c == PAD_CHAR) {
indices[j] = 0; // 填充符处理为0后续根据paddingCount调整
} else {
Integer index = charMap.get(c);
if (index == null) {
throw new IllegalArgumentException("无效字符: " + c);
}
indices[j] = index;
}
}
int combined = (indices[0] << 18) | (indices[1] << 12) | (indices[2] << 6) | indices[3];
for (int k = 0; k < 3; k++) {
if (decodedIndex < decoded.length) {
decoded[decodedIndex++] = (byte) ((combined >> (16 - 8 * k)) & 0xFF);
}
}
}
return decoded;
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.serializer;
import cn.dev33.satoken.serializer.impl.SaSerializerTemplateForJdk;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 序列化器base64 算法采用 64 Emoji 小黄脸作为元字符集无填充字符
*
* @author click33
* @since 1.41.0
*/
public class SaSerializerForBase64UseEmoji implements SaSerializerTemplateForJdk {
private final List<String> EMOJI_TABLE = new ArrayList<>(); // 编码表
private final Map<String, Integer> EMOJI_MAP = new HashMap<>(); // 解码表
public SaSerializerForBase64UseEmoji() {
// 初始化编码表64个EmojiU+1F600 U+1F63F
for (int i = 0; i < 64; i++) {
int codePoint = 0x1F600 + i;
String emoji = new String(Character.toChars(codePoint));
EMOJI_TABLE.add(emoji);
EMOJI_MAP.put(emoji, i);
}
}
@Override
public String bytesToString(byte[] data) {
StringBuilder binaryStr = new StringBuilder();
for (byte b : data) {
binaryStr.append(String.format("%8s", Integer.toBinaryString(b & 0xFF))
.replace(' ', '0'));
}
// 补零到6的倍数
int bitLength = binaryStr.length();
int paddingBits = (6 - (bitLength % 6)) % 6;
for (int i = 0; i < paddingBits; i++) {
binaryStr.append('0');
}
// 转换为索引
List<Integer> indices = new ArrayList<>();
for (int i = 0; i < binaryStr.length(); i += 6) {
String chunk = binaryStr.substring(i, Math.min(i + 6, binaryStr.length()));
indices.add(Integer.parseInt(chunk, 2));
}
// 拼接Emoji
StringBuilder result = new StringBuilder();
for (int index : indices) {
result.append(EMOJI_TABLE.get(index));
}
return result.toString();
}
@Override
public byte[] stringToBytes(String encoded) {
List<Integer> indices = new ArrayList<>();
// 提取索引每个Emoji占2个char
for (int i = 0; i < encoded.length(); ) {
if (i + 1 >= encoded.length()) break;
String emoji = encoded.substring(i, i + 2);
i += 2;
Integer index = EMOJI_MAP.get(emoji);
if (index == null) {
throw new IllegalArgumentException("非法Emoji: " + emoji);
}
indices.add(index);
}
// 转换为二进制字符串
StringBuilder binaryStr = new StringBuilder();
for (int index : indices) {
binaryStr.append(String.format("%6s", Integer.toBinaryString(index))
.replace(' ', '0'));
}
// 转换为字节数组自动处理末尾补零
List<Byte> bytes = new ArrayList<>();
for (int i = 0; i < binaryStr.length(); i += 8) {
int endIndex = Math.min(i + 8, binaryStr.length());
String byteStr = binaryStr.substring(i, endIndex);
if (byteStr.length() < 8) break; // 忽略末尾不足8位的部分
bytes.add((byte) Integer.parseInt(byteStr, 2));
}
byte[] result = new byte[bytes.size()];
for (int i = 0; i < bytes.size(); i++) {
result[i] = bytes.get(i);
}
return result;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.serializer;
/**
* 序列化器base64 算法采用 元素周期表 前六十四位作为元字符集
*
* @author click33
* @since 1.41.0
*/
public class SaSerializerForBase64UsePeriodicTable extends SaSerializerForBase64UseCustomCharacters {
public SaSerializerForBase64UsePeriodicTable() {
super(
// 自定义字符集需确保包含64个不重复的字符
"氢氦锂铍硼碳氮氧" +
"氟氖钠镁铝硅磷硫" +
"氯氩钾钙钪钛钒铬" +
"锰铁钴镍铜锌镓锗" +
"砷硒溴氪铷锶钇锆" +
"铌钼锝钌铑钯银镉" +
"铟锡锑碲碘氙铯钡" +
"镧铈镨钕钷钐铕钆"
,
// 填充符确保不在字符集中
'鿫'
);
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.serializer;
/**
* 序列化器base64 算法采用64个特殊符号作为元字符集
*
* @author click33
* @since 1.41.0
*/
public class SaSerializerForBase64UseSpecialSymbols extends SaSerializerForBase64UseCustomCharacters {
public SaSerializerForBase64UseSpecialSymbols() {
super(
// 自定义字符集需确保包含64个不重复的字符
"▲▼●◆■★▶◀" +
"♠♥♦♣▁▂▃▄" +
"▅▆▇█▏▎▍▌" +
"▋▊▉▬〓◤◥◣" +
"◢♩♪♫♬§〼↖" +
"↑↗←→↙↓↘☴" +
"☲☷☳☱☶☵☰◐" +
"◑☀☼▪•‥…∷"
,
// 填充符确保不在字符集中
'※'
);
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.serializer;
/**
* 序列化器base64 算法采用 十大天干十二地支 等64个中文字符作为元字符集
*
* @author click33
* @since 1.41.0
*/
public class SaSerializerForBase64UseTianGan extends SaSerializerForBase64UseCustomCharacters {
public SaSerializerForBase64UseTianGan() {
super(
// 自定义字符集需确保包含64个不重复的字符
"甲乙丙丁戊己庚辛" +
"壬癸子丑寅卯辰巳" +
"午未申酉戌亥乾坤" +
"震巽坎离艮兑金木" +
"水火土天地日月山" +
"石田风雷电霜雾露" +
"东南西北中信谷岚" +
"宇宙羽泰铭安鹤纤"
,
// 填充符确保不在字符集中
'口'
);
}
}

View File

@ -0,0 +1 @@
cn.dev33.satoken.plugin.SaTokenPluginForSerializerFeatures