Json序列化

This commit is contained in:
tqzhang 2025-02-08 15:40:24 +08:00
parent 74c813be8d
commit df6e52ce15
6 changed files with 805 additions and 0 deletions

View File

@ -0,0 +1,242 @@
package com.bonus.config;
import cn.hutool.core.util.ArrayUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
@JsonIgnoreProperties(
ignoreUnknown = true
)
public class AccessToken {
private static final Logger log = LoggerFactory.getLogger(AccessToken.class);
@JsonIgnore
protected static ObjectMapper objectMapper;
@JsonIgnore
protected static SecureProperties secureProperties;
@JsonIgnore
protected static StringRedisTemplate redisTemplate;
@JsonIgnore
protected static AuthenticationPredicate authenticationPredicate;
private String id;
private Long subjectId;
private String subjectName;
private Map<String, String> subjectData = Maps.newHashMap();
private String scope;
private boolean identified;
private long createTime;
private long lastTime;
public static Optional<AccessToken> recovery(String clientToken) {
StringRedisTemplate var10000 = redisTemplate;
String var10001 = secureProperties.getServer().getStoreKey();
clientToken = (String)var10000.boundValueOps(var10001 + ":" + clientToken).get();
if (StringUtils.isBlank(clientToken)) {
return Optional.empty();
} else {
try {
AccessToken existToken = (AccessToken)objectMapper.readValue(clientToken, AccessToken.class);
return authenticationPredicate.authenticated(existToken) ? Optional.of(existToken) : Optional.empty();
} catch (Exception var2) {
log.error("Deserialize exist token error", var2);
return Optional.empty();
}
}
}
public static AccessToken create(long subjectId) {
AccessToken accessToken = create();
accessToken.setSubjectId(subjectId);
return accessToken;
}
public static AccessToken create(long subjectId, String subjectName) {
AccessToken accessToken = create(subjectId);
accessToken.setSubjectName(subjectName);
return accessToken;
}
public static AccessToken create() {
AccessToken accessToken = new AccessToken();
accessToken.setId(UUID.randomUUID().toString());
accessToken.setCreateTime(Instant.now().getEpochSecond());
accessToken.setLastTime(Instant.now().getEpochSecond());
return accessToken;
}
public AccessToken touch() {
this.identified = this.isAuthenticated();
this.lastTime = Instant.now().getEpochSecond();
return this.store();
}
@JsonIgnore
public boolean isAuthenticated() {
return this.identified && !this.isExpired();
}
@JsonIgnore
private boolean isExpired() {
return this.lastTime + secureProperties.getExpireAfter() < Instant.now().getEpochSecond();
}
public AccessToken withData(Map<String, String> data) {
this.subjectData = data;
return this;
}
public AccessToken setData(String name, String value) {
this.subjectData.put(name, value);
return this;
}
public AccessToken removeData(String... keys) {
if (ArrayUtil.isEmpty(keys)) {
return this;
} else {
String[] var2 = keys;
int var3 = keys.length;
for(int var4 = 0; var4 < var3; ++var4) {
String key = var2[var4];
this.subjectData.remove(key);
}
return this;
}
}
public AccessToken revokeAuthenticate() {
this.identified = false;
return this.store();
}
public AccessToken authenticate() {
if (this.subjectId == null) {
throw new RuntimeException("required subjectId is not provide");
} else {
this.identified = true;
return this;
}
}
public AccessToken store() {
try {
StringRedisTemplate var10000 = redisTemplate;
DefaultRedisScript var10001 = new DefaultRedisScript("redis.call('SET',KEYS[1],ARGV[1],'EX',ARGV[3]);redis.call('SET',KEYS[2],ARGV[2],'EX',ARGV[3]);");
String[] var10002 = new String[2];
String var10005 = secureProperties.getServer().getStoreKey();
var10002[0] = var10005 + ":" + this.getId();
var10005 = secureProperties.getServer().getSubjectRefTokenKey();
var10002[1] = var10005 + ":" + this.getSubjectId() + ":" + this.getId() + ":" + this.getCreateTime();
var10000.execute(var10001, Lists.newArrayList(var10002), new Object[]{objectMapper.writeValueAsString(this), this.getId(), String.valueOf(secureProperties.getServer().getTtl())});
} catch (Exception var2) {
log.error("Token store error", var2);
}
return this;
}
public AccessToken bind() {
WebContext.get().setAccessToken(this);
return this;
}
public void write() {
WebContext.get().getResponse().ifPresent((response) -> {
response.setHeader(secureProperties.getTokenSymbol(), this.getId());
});
}
public void clear() {
try {
StringRedisTemplate var10000 = redisTemplate;
DefaultRedisScript var10001 = new DefaultRedisScript("redis.call('DEL',KEYS[1],KEYS[2]);");
String[] var10002 = new String[2];
String var10005 = secureProperties.getServer().getStoreKey();
var10002[0] = var10005 + ":" + this.getId();
var10005 = secureProperties.getServer().getSubjectRefTokenKey();
var10002[1] = var10005 + ":" + this.getSubjectId() + ":" + this.getId() + ":" + this.getCreateTime();
var10000.execute(var10001, Lists.newArrayList(var10002), new Object[0]);
} catch (Exception var2) {
log.error("Token clear error", var2);
}
}
public String getId() {
return this.id;
}
public Long getSubjectId() {
return this.subjectId;
}
public String getSubjectName() {
return this.subjectName;
}
public Map<String, String> getSubjectData() {
return this.subjectData;
}
public String getScope() {
return this.scope;
}
public boolean isIdentified() {
return this.identified;
}
public long getCreateTime() {
return this.createTime;
}
public long getLastTime() {
return this.lastTime;
}
public void setId(final String id) {
this.id = id;
}
public void setSubjectId(final Long subjectId) {
this.subjectId = subjectId;
}
public void setSubjectName(final String subjectName) {
this.subjectName = subjectName;
}
public void setSubjectData(final Map<String, String> subjectData) {
this.subjectData = subjectData;
}
public void setScope(final String scope) {
this.scope = scope;
}
public void setIdentified(final boolean identified) {
this.identified = identified;
}
public void setCreateTime(final long createTime) {
this.createTime = createTime;
}
public void setLastTime(final long lastTime) {
this.lastTime = lastTime;
}
}

View File

@ -0,0 +1,5 @@
package com.bonus.config;
public interface AuthenticationPredicate {
boolean authenticated(AccessToken accessToken);
}

View File

@ -0,0 +1,366 @@
package com.bonus.config;
import cn.hutool.core.codec.Base64Decoder;
import com.google.common.collect.Sets;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import java.util.Arrays;
import java.util.Set;
@RefreshScope
@ConfigurationProperties(
prefix = "secure"
)
public class SecureProperties {
public static final String PREFIX = "secure";
private boolean enabled = true;
private boolean prohibitUnannotatedHandler = false;
private String tokenSymbol = "X-Token";
private String permissionKey = "secure:pms";
private long permissionTTL = 7200L;
private long expireAfter = 7200L;
private ServerStore server = new ServerStore();
private Security security = new Security();
private MdcLogParameter mdc = new MdcLogParameter();
public boolean isEnabled() {
return this.enabled;
}
public boolean isProhibitUnannotatedHandler() {
return this.prohibitUnannotatedHandler;
}
public String getTokenSymbol() {
return this.tokenSymbol;
}
public String getPermissionKey() {
return this.permissionKey;
}
public long getPermissionTTL() {
return this.permissionTTL;
}
public long getExpireAfter() {
return this.expireAfter;
}
public ServerStore getServer() {
return this.server;
}
public Security getSecurity() {
return this.security;
}
public MdcLogParameter getMdc() {
return this.mdc;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
public void setProhibitUnannotatedHandler(final boolean prohibitUnannotatedHandler) {
this.prohibitUnannotatedHandler = prohibitUnannotatedHandler;
}
public void setTokenSymbol(final String tokenSymbol) {
this.tokenSymbol = tokenSymbol;
}
public void setPermissionKey(final String permissionKey) {
this.permissionKey = permissionKey;
}
public void setPermissionTTL(final long permissionTTL) {
this.permissionTTL = permissionTTL;
}
public void setExpireAfter(final long expireAfter) {
this.expireAfter = expireAfter;
}
public void setServer(final ServerStore server) {
this.server = server;
}
public void setSecurity(final Security security) {
this.security = security;
}
public void setMdc(final MdcLogParameter mdc) {
this.mdc = mdc;
}
public static class ServerStore {
private String storeKey = "secure:token";
private String subjectRefTokenKey = "secure:subject-token";
private long ttl = 14400L;
public String getStoreKey() {
return this.storeKey;
}
public String getSubjectRefTokenKey() {
return this.subjectRefTokenKey;
}
public long getTtl() {
return this.ttl;
}
public void setStoreKey(final String storeKey) {
this.storeKey = storeKey;
}
public void setSubjectRefTokenKey(final String subjectRefTokenKey) {
this.subjectRefTokenKey = subjectRefTokenKey;
}
public void setTtl(final long ttl) {
this.ttl = ttl;
}
}
public static class Security {
private String encryptUriPrefix = "/security";
private String keyExchangePath = "/exchange/key";
private String publicKeyExchangePath = "/exchange/public-key";
private Set<String> ignoredUri = Sets.newHashSet();
private String requestBodySignParamName = "body";
private String clientKeySignParamName = "clientKey";
private Set<String> addonSignHeaderPrefix = Sets.newHashSet(new String[]{"X-Security-Ext"});
private Set<String> tokenHeaderNames = Sets.newHashSet(new String[]{"X-Security-Token", "X-Security-Tenant-Id", "X-Security-Sn"});
private String tokenSignHeaderName = "X-Security-Token-Sign";
private String serverKeySignParamName = "serverKey";
private String serverSm4Key = "McaCOPft5/J3bUG4pdVjhg==";
private byte[] serverSm4KeyBytes;
private String serverSm2Key;
private byte[] serverSm2KeyBytes;
private String clientSm2Key;
private byte[] clientSm2KeyBytes;
private String timestampHeaderName;
private String nonceHeaderName;
private String signHeaderName;
private String serverEncryptedClientKeyHeaderName;
private long maxWindowSeconds;
private String playKey;
public void setServerSm4Key(String serverSm4Key) {
this.serverSm4Key = serverSm4Key;
this.serverSm4KeyBytes = Base64Decoder.decode(serverSm4Key);
}
public Security() {
this.serverSm4KeyBytes = Base64Decoder.decode(this.serverSm4Key);
this.serverSm2Key = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgCtqk5Jj7pPWh91d9mPA4Kd7fOfzBULrnAERNDV+4XBCgCgYIKoEcz1UBgi2hRANCAARykhB6sXHWTbB60Pr+laPqEP5JBRpEcySONKKP5Q03o/g3OpnQXc7aVMdLUxL8wD1wQHEu4KHmHQr7jvVt0rkM";
this.serverSm2KeyBytes = Base64Decoder.decode(this.serverSm2Key);
this.clientSm2Key = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEcpIQerFx1k2wetD6/pWj6hD+SQUaRHMkjjSij+UNN6P4NzqZ0F3O2lTHS1MS/MA9cEBxLuCh5h0K+471bdK5DA==";
this.clientSm2KeyBytes = Base64Decoder.decode(this.serverSm2Key);
this.timestampHeaderName = "X-Security-Timestamp";
this.nonceHeaderName = "X-Security-Nonce";
this.signHeaderName = "X-Security-Sign";
this.serverEncryptedClientKeyHeaderName = "X-Security-Server-Encrypted-Client-Key";
this.maxWindowSeconds = 90L;
this.playKey = "__play:";
}
public String getEncryptUriPrefix() {
return this.encryptUriPrefix;
}
public String getKeyExchangePath() {
return this.keyExchangePath;
}
public String getPublicKeyExchangePath() {
return this.publicKeyExchangePath;
}
public Set<String> getIgnoredUri() {
return this.ignoredUri;
}
public String getRequestBodySignParamName() {
return this.requestBodySignParamName;
}
public String getClientKeySignParamName() {
return this.clientKeySignParamName;
}
public Set<String> getAddonSignHeaderPrefix() {
return this.addonSignHeaderPrefix;
}
public Set<String> getTokenHeaderNames() {
return this.tokenHeaderNames;
}
public String getTokenSignHeaderName() {
return this.tokenSignHeaderName;
}
public String getServerKeySignParamName() {
return this.serverKeySignParamName;
}
public String getServerSm4Key() {
return this.serverSm4Key;
}
public byte[] getServerSm4KeyBytes() {
return this.serverSm4KeyBytes;
}
public String getServerSm2Key() {
return this.serverSm2Key;
}
public byte[] getServerSm2KeyBytes() {
return this.serverSm2KeyBytes;
}
public String getClientSm2Key() {
return this.clientSm2Key;
}
public byte[] getClientSm2KeyBytes() {
return this.clientSm2KeyBytes;
}
public String getTimestampHeaderName() {
return this.timestampHeaderName;
}
public String getNonceHeaderName() {
return this.nonceHeaderName;
}
public String getSignHeaderName() {
return this.signHeaderName;
}
public String getServerEncryptedClientKeyHeaderName() {
return this.serverEncryptedClientKeyHeaderName;
}
public long getMaxWindowSeconds() {
return this.maxWindowSeconds;
}
public String getPlayKey() {
return this.playKey;
}
public void setEncryptUriPrefix(final String encryptUriPrefix) {
this.encryptUriPrefix = encryptUriPrefix;
}
public void setKeyExchangePath(final String keyExchangePath) {
this.keyExchangePath = keyExchangePath;
}
public void setPublicKeyExchangePath(final String publicKeyExchangePath) {
this.publicKeyExchangePath = publicKeyExchangePath;
}
public void setIgnoredUri(final Set<String> ignoredUri) {
this.ignoredUri = ignoredUri;
}
public void setRequestBodySignParamName(final String requestBodySignParamName) {
this.requestBodySignParamName = requestBodySignParamName;
}
public void setClientKeySignParamName(final String clientKeySignParamName) {
this.clientKeySignParamName = clientKeySignParamName;
}
public void setAddonSignHeaderPrefix(final Set<String> addonSignHeaderPrefix) {
this.addonSignHeaderPrefix = addonSignHeaderPrefix;
}
public void setTokenHeaderNames(final Set<String> tokenHeaderNames) {
this.tokenHeaderNames = tokenHeaderNames;
}
public void setTokenSignHeaderName(final String tokenSignHeaderName) {
this.tokenSignHeaderName = tokenSignHeaderName;
}
public void setServerKeySignParamName(final String serverKeySignParamName) {
this.serverKeySignParamName = serverKeySignParamName;
}
public void setServerSm4KeyBytes(final byte[] serverSm4KeyBytes) {
this.serverSm4KeyBytes = serverSm4KeyBytes;
}
public void setServerSm2Key(final String serverSm2Key) {
this.serverSm2Key = serverSm2Key;
}
public void setServerSm2KeyBytes(final byte[] serverSm2KeyBytes) {
this.serverSm2KeyBytes = serverSm2KeyBytes;
}
public void setClientSm2Key(final String clientSm2Key) {
this.clientSm2Key = clientSm2Key;
}
public void setClientSm2KeyBytes(final byte[] clientSm2KeyBytes) {
this.clientSm2KeyBytes = clientSm2KeyBytes;
}
public void setTimestampHeaderName(final String timestampHeaderName) {
this.timestampHeaderName = timestampHeaderName;
}
public void setNonceHeaderName(final String nonceHeaderName) {
this.nonceHeaderName = nonceHeaderName;
}
public void setSignHeaderName(final String signHeaderName) {
this.signHeaderName = signHeaderName;
}
public void setServerEncryptedClientKeyHeaderName(final String serverEncryptedClientKeyHeaderName) {
this.serverEncryptedClientKeyHeaderName = serverEncryptedClientKeyHeaderName;
}
public void setMaxWindowSeconds(final long maxWindowSeconds) {
this.maxWindowSeconds = maxWindowSeconds;
}
public void setPlayKey(final String playKey) {
this.playKey = playKey;
}
}
public static class MdcLogParameter {
private String subjectId = "x-id";
private String subjectName = "x-name";
public String getSubjectId() {
return this.subjectId;
}
public String getSubjectName() {
return this.subjectName;
}
public void setSubjectId(final String subjectId) {
this.subjectId = subjectId;
}
public void setSubjectName(final String subjectName) {
this.subjectName = subjectName;
}
}
}

View File

@ -0,0 +1,101 @@
package com.bonus.config;
import com.google.common.collect.Maps;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Optional;
public class WebContext {
private static final ThreadLocal<Context> THREAD_CONTEXT = new InheritableThreadLocal<Context>() {
protected Context initialValue() {
return new Context();
}
};
private WebContext() {
}
public static void reset() {
THREAD_CONTEXT.remove();
}
public static Context get() {
return (Context)THREAD_CONTEXT.get();
}
public static void set(Context context) {
THREAD_CONTEXT.set(context);
}
public static class Context {
private HttpServletRequest request;
private HttpServletResponse response;
private AccessToken accessToken;
private Map<String, Object> attributes = Maps.newHashMap();
public Context(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
public void setAttribute(String key, Object data) {
this.attributes.put(key, data);
}
public Object getAttribute(String key) {
return this.attributes.get(key);
}
public void removeAttribute(String key) {
this.attributes.remove(key);
}
public void clearAttribute() {
this.attributes.clear();
}
public Optional<AccessToken> getAccessToken() {
return Optional.ofNullable(this.accessToken);
}
public Optional<HttpServletRequest> getRequest() {
return Optional.ofNullable(this.request);
}
public Optional<HttpServletResponse> getResponse() {
return Optional.ofNullable(this.response);
}
public Map<String, Object> getAttributes() {
return this.attributes;
}
public void setRequest(final HttpServletRequest request) {
this.request = request;
}
public void setResponse(final HttpServletResponse response) {
this.response = response;
}
public void setAccessToken(final AccessToken accessToken) {
this.accessToken = accessToken;
}
public void setAttributes(final Map<String, Object> attributes) {
this.attributes = attributes;
}
public Context() {
}
public Context(final HttpServletRequest request, final HttpServletResponse response, final AccessToken accessToken, final Map<String, Object> attributes) {
this.request = request;
this.response = response;
this.accessToken = accessToken;
this.attributes = attributes;
}
}
}

View File

@ -0,0 +1,11 @@
package com.bonus.config.json;
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
import java.time.format.DateTimeFormatter;
public class InstantsSerializer extends InstantSerializer {
public InstantsSerializer(DateTimeFormatter defaultFormat) {
super(InstantSerializer.INSTANCE, false, defaultFormat);
}
}

View File

@ -0,0 +1,80 @@
package com.bonus.config.json;
import com.bonus.config.WebContext;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;
@AutoConfiguration(
before = {JacksonAutoConfiguration.class}
)
@ConditionalOnClass({ObjectMapper.class})
public class JacksonConfiguration {
public static final StdSerializer<Long> LONG_STD_SERIALIZER;
@Bean
Jackson2ObjectMapperBuilderCustomizer objectMapperBuilderCustomizer() {
return (builder) -> {
builder.serializerByType(Long.TYPE, LONG_STD_SERIALIZER);
builder.serializerByType(Long.class, LONG_STD_SERIALIZER);
builder.locale(Locale.CHINA);
builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
builder.modules(new Module[]{new SimpleModule(PackageVersion.VERSION) {
{
this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
this.addSerializer(Instant.class, new InstantsSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
}
}});
};
}
static {
LONG_STD_SERIALIZER = new StdSerializer<Long>(Long.TYPE) {
@Override
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (value == null) {
jsonGenerator.writeNull();
} else {
Optional<HttpServletRequest> requestOptional = WebContext.get().getRequest();
if ((Boolean)requestOptional.map((request) -> {
return Objects.isNull(request.getHeader("User-Agent")) || request.getHeader("User-Agent").contains("okhttp");
}).orElse(false)) {
jsonGenerator.writeNumber(value);
} else {
jsonGenerator.writeString(value.toString());
}
}
}
};
}
}