From 5d4ea5327da11886cf2642ba35f409403a0b22a6 Mon Sep 17 00:00:00 2001 From: jjLv <1981429112@qq.com> Date: Mon, 14 Apr 2025 23:57:22 +0800 Subject: [PATCH] =?UTF-8?q?json=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canteen/core/config/AccessToken.java | 242 ++++++++++++ .../core/config/AuthenticationPredicate.java | 5 + .../canteen/core/config/SecureManager.java | 154 ++++++++ .../canteen/core/config/SecureProperties.java | 367 ++++++++++++++++++ .../bonus/canteen/core/config/SmUtils.java | 126 ++++++ .../bonus/canteen/core/config/WebContext.java | 102 +++++ .../core/config/json/InstantsSerializer.java | 11 + .../config/json/JacksonConfiguration.java | 78 ++++ .../core/config/json/JsonNodeTypeHandler.java | 57 +++ 9 files changed, 1142 insertions(+) create mode 100644 bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/AccessToken.java create mode 100644 bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/AuthenticationPredicate.java create mode 100644 bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SecureManager.java create mode 100644 bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SecureProperties.java create mode 100644 bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SmUtils.java create mode 100644 bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/WebContext.java create mode 100644 bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/InstantsSerializer.java create mode 100644 bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/JacksonConfiguration.java create mode 100644 bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/JsonNodeTypeHandler.java diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/AccessToken.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/AccessToken.java new file mode 100644 index 0000000..628d59b --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/AccessToken.java @@ -0,0 +1,242 @@ +package com.bonus.canteen.core.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 subjectData = Maps.newHashMap(); + private String scope; + private boolean identified; + private long createTime; + private long lastTime; + + public static Optional 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 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 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 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; + } +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/AuthenticationPredicate.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/AuthenticationPredicate.java new file mode 100644 index 0000000..9d8d7a9 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/AuthenticationPredicate.java @@ -0,0 +1,5 @@ +package com.bonus.canteen.core.config; + +public interface AuthenticationPredicate { + boolean authenticated(AccessToken accessToken); +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SecureManager.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SecureManager.java new file mode 100644 index 0000000..a3ea273 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SecureManager.java @@ -0,0 +1,154 @@ +package com.bonus.canteen.core.config;//package com.bonus.canteen.core.config; +// +//import cn.hutool.core.map.MapUtil; +//import com.bonus.canteen.core.customer.service.AuthorizingService; +//import com.bonus.canteen.core.secure.PmsCache; +//import com.fasterxml.jackson.core.JsonProcessingException; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import com.google.common.collect.Maps; +//import com.google.common.collect.Sets; +//import org.apache.commons.collections4.CollectionUtils; +//import org.apache.commons.collections4.ListUtils; +//import org.apache.commons.lang3.BooleanUtils; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.springframework.data.redis.core.BoundHashOperations; +//import org.springframework.data.redis.core.StringRedisTemplate; +//import java.time.Instant; +//import java.util.*; +//import java.util.function.Function; +//import java.util.stream.Collectors; +//import java.util.stream.Stream; +// +//public class SecureManager { +// private static final Logger log = LoggerFactory.getLogger(SecureManager.class); +// private static SecureProperties secureProperties; +// private static StringRedisTemplate redisTemplate; +// private static AuthorizingService authorizingService; +// private static ObjectMapper objectMapper; +// +// private SecureManager() { +// } +// +// public static void setSecureProperties(SecureProperties secureProperties) { +// if (SecureManager.secureProperties == null) { +// SecureManager.secureProperties = secureProperties; +// } +// } +// +// public static void setRedisTemplate(StringRedisTemplate redisTemplate) { +// if (SecureManager.redisTemplate == null) { +// SecureManager.redisTemplate = redisTemplate; +// } +// } +// +// public static void setAuthorizingService(AuthorizingService authorizingService) { +// if (SecureManager.authorizingService == null) { +// SecureManager.authorizingService = authorizingService; +// } +// } +// +// public static void setObjectMapper(ObjectMapper objectMapper) { +// if (SecureManager.objectMapper == null) { +// SecureManager.objectMapper = objectMapper; +// } +// } +// +// private static String getPmsKey(long subjectId) { +// return "permissions:subject_id:" + subjectId; +// } +// +// private static String getRoleKey(long subjectId) { +// return "roles:subject_id:" + subjectId; +// } +// +// public static void clearAllRoleAndPermission() { +// redisTemplate.delete(secureProperties.getPermissionKey()); +// } +// +// public static void clearRoleAndPermission(long subjectId) { +// redisTemplate.boundHashOps(secureProperties.getPermissionKey()).delete(new Object[]{getRoleKey(subjectId), getPmsKey(subjectId)}); +// } +// +// public static void clearRole(long subjectId) { +// redisTemplate.boundHashOps(secureProperties.getPermissionKey()).delete(new Object[]{getRoleKey(subjectId)}); +// } +// +// public static void clearPermission(long subjectId) { +// redisTemplate.boundHashOps(secureProperties.getPermissionKey()).delete(new Object[]{getPmsKey(subjectId)}); +// } +// +// public static boolean isLogin() { +// return (Boolean)WebContext.get().getAccessToken().map(AccessToken::isAuthenticated).orElse(false); +// } +// +// public static Optional getSubjectId() { +// return WebContext.get().getAccessToken().map(AccessToken::getSubjectId); +// } +// +// public static Optional getSubjectName() { +// return WebContext.get().getAccessToken().map(AccessToken::getSubjectName); +// } +// +// public static Map getSubjectData() { +// return (Map)WebContext.get().getAccessToken().map(AccessToken::getSubjectData).orElse(Maps.newHashMap()); +// } +// +// public static Map attachData(Map data) { +// try { +// if (MapUtil.isEmpty(data)) { +// return Maps.newHashMapWithExpectedSize(0); +// } else { +// AccessToken accessToken = (AccessToken)WebContext.get().getAccessToken().orElse(AccessToken.create()); +// Objects.requireNonNull(accessToken); +// data.forEach(accessToken::setData); +// accessToken.bind().store(); +// return accessToken.getSubjectData(); +// } +// } catch (Throwable var2) { +// throw var2; +// } +// } +// +// public static Map attachData(String key, String value) { +// try { +// return attachData(Collections.singletonMap(key, value)); +// } catch (Throwable var3) { +// throw var3; +// } +// } +// +// public static void logout() { +// WebContext.get().getAccessToken().ifPresent(AccessToken::clear); +// } +// +// public static void revokeAuthenticate() { +// WebContext.get().getAccessToken().ifPresent((accessToken) -> { +// accessToken.revokeAuthenticate().store(); +// }); +// } +// +// public static void revokeAuthenticate(long subjectId, int reservedRecentNum) { +// StringRedisTemplate var10000 = redisTemplate; +// String var10001 = secureProperties.getServer().getSubjectRefTokenKey(); +// Set keys = var10000.keys(var10001 + ":" + subjectId + ":*"); +// if (CollectionUtils.size(keys) > reservedRecentNum) { +// assert keys != null; +// +// Map> createTimeAsc_keys = (Map)keys.stream().collect(Collectors.groupingBy((key) -> { +// return Long.parseLong(key.split(":")[4]); +// }, TreeMap::new, Collectors.toList())); +// List keysAsc = createTimeAsc_keys.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); +// List beDeleteRefKeys = keysAsc.subList(0, keysAsc.size() - reservedRecentNum); +// List beDeleteTokenKeys = beDeleteRefKeys.stream().map((dk) -> { +// String var100001 = secureProperties.getServer().getStoreKey(); +// return var100001 + ":" + dk.split(":")[3]; +// }).collect(Collectors.toList()); +// redisTemplate.delete(ListUtils.sum(beDeleteRefKeys, beDeleteTokenKeys)); +// } +// } +// +// public String toString() { +// return "SecureManager()"; +// } +//} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SecureProperties.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SecureProperties.java new file mode 100644 index 0000000..36ba7ea --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SecureProperties.java @@ -0,0 +1,367 @@ +package com.bonus.canteen.core.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 org.springframework.stereotype.Component; + +import java.util.Set; + +@RefreshScope +@Component +@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 ignoredUri = Sets.newHashSet(); + private String requestBodySignParamName = "body"; + private String clientKeySignParamName = "clientKey"; + private Set addonSignHeaderPrefix = Sets.newHashSet(new String[]{"X-Security-Ext"}); + private Set 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 = "D55F4709BE51FCDC71D6385885A5CAEE70A09438F862BEB4E56F64A70C76EF5F"; + this.serverSm2KeyBytes = Base64Decoder.decode(this.serverSm2Key); + this.clientSm2Key = "04768E8E44656FFD4BA58C0270002A28365A5F6B0F6D40E88B9221CDFAAA8E82C8CCEDBA5FC2D03F20B11492EBE90CC04782682AFE326363A503F086C04A14092C"; + 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 getIgnoredUri() { + return this.ignoredUri; + } + + public String getRequestBodySignParamName() { + return this.requestBodySignParamName; + } + + public String getClientKeySignParamName() { + return this.clientKeySignParamName; + } + + public Set getAddonSignHeaderPrefix() { + return this.addonSignHeaderPrefix; + } + + public Set 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 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 addonSignHeaderPrefix) { + this.addonSignHeaderPrefix = addonSignHeaderPrefix; + } + + public void setTokenHeaderNames(final Set 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; + } + } +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SmUtils.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SmUtils.java new file mode 100644 index 0000000..59a9016 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/SmUtils.java @@ -0,0 +1,126 @@ +package com.bonus.canteen.core.config; + +import cn.hutool.core.codec.Base64Decoder; +import cn.hutool.core.codec.Base64Encoder; +import cn.hutool.core.util.PrimitiveArrayUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.crypto.Mode; +import cn.hutool.crypto.Padding; +import cn.hutool.crypto.SmUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.symmetric.SM4; +import com.bonus.canteen.core.common.utils.SpringContextHolder; +import com.google.common.base.Joiner; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.util.SortedMap; + +public class SmUtils { + private static final Logger log = LoggerFactory.getLogger(SmUtils.class); + private static SecureProperties secureProperties = SpringContextHolder.getBean(SecureProperties.class); + public static void setSecureProperties(SecureProperties secureProperties) { + if (SmUtils.secureProperties == null) { + SmUtils.secureProperties = secureProperties; + } + } + + public static String signBySm3(SortedMap data, String clientKey) { + data.put(secureProperties.getSecurity().getClientKeySignParamName(), clientKey); + String joinedParam = Joiner.on("&").useForNull("").withKeyValueSeparator("=").join(data); + log.debug("Sign param:{}", joinedParam); + return Base64Encoder.encode(SmUtil.sm3().digest(joinedParam)); + } + + public static String signBySm3WithServerKey(SortedMap data, String clientKey) { + data.put(secureProperties.getSecurity().getServerKeySignParamName(), secureProperties.getSecurity().getServerSm4Key()); + return signBySm3(data, clientKey); + } + + public static String signAuthTokenBySm3(SortedMap data, String clientKey) { + return signBySm3WithServerKey(data, clientKey); + } + + public static String decryptBySm2(String data) { + return SmUtil.sm2(secureProperties.getSecurity().getServerSm2Key(), secureProperties.getSecurity().getClientSm2Key()).decryptStr(data, KeyType.PrivateKey); + } + + public static String signBySm2(String data) { + return Base64Encoder.encode(SmUtil.sm2(secureProperties.getSecurity().getServerSm2Key(), secureProperties.getSecurity().getClientSm2Key()).sign(data.getBytes(StandardCharsets.UTF_8))); + } + + public static String decryptBySm4(String data, byte[] key) { + byte[] decodedData = Base64Decoder.decode(data); + if (decodedData.length <= 16) { + throw new IllegalArgumentException("Encrypted data byte length must greater than 16"); + } else { + byte[] iv = PrimitiveArrayUtil.sub(decodedData, 0, 16); + byte[] dataBytes = PrimitiveArrayUtil.sub(decodedData, 16, decodedData.length); + return (new SM4(Mode.CBC, Padding.PKCS5Padding, key, iv)).decryptStr(dataBytes); + } + } + + public static String decryptBySm4(String data, String key) { + return decryptBySm4(data, Base64Decoder.decode(key)); + } + + public static String decryptBySm4WithServerKey(String data) { + return decryptBySm4(data, secureProperties.getSecurity().getServerSm4KeyBytes()); + } + + public static String encryptBySm4(String data, byte[] key) { + SM4 var10000 = new SM4(Mode.CBC, Padding.PKCS5Padding, key, RandomUtil.randomBytes(16)); + String var10001 = RandomUtil.randomString(16); + return var10000.encryptBase64(var10001 + data); + } + + public static String encryptBySm4WithServerKey(String data) { + return encryptBySm4(data, secureProperties.getSecurity().getServerSm4KeyBytes()); + } + + public static void main(String[] args) throws NoSuchAlgorithmException { + //sm2() 移到 SmUtilsTest.java + sm4(); + sm4WithoutIV(); + } + + private static void sm4() throws NoSuchAlgorithmException { + System.out.println("================sm4=================="); + BouncyCastleProvider provider = new BouncyCastleProvider(); + KeyGenerator generator = KeyGenerator.getInstance("SM4", provider); + SecretKey secretKey = generator.generateKey(); + byte[] encoded = secretKey.getEncoded(); + String key = Base64Encoder.encode(encoded); + System.out.println("key: " + key); + String data = "liolay"; + String encryptBySm4 = encryptBySm4(data, encoded); + System.out.println("encrypt: " + encryptBySm4); + PrintStream var10000 = System.out; + String var10001 = decryptBySm4(encryptBySm4, encoded); + var10000.println("decrypt: " + var10001); + var10000 = System.out; + var10001 = decryptBySm4(encryptBySm4, key); + var10000.println("decrypt: " + var10001); + } + + private static void sm4WithoutIV() throws NoSuchAlgorithmException { + System.out.println("================sm4WithoutIV=================="); + BouncyCastleProvider provider = new BouncyCastleProvider(); + KeyGenerator generator = KeyGenerator.getInstance("SM4", provider); + SecretKey secretKey = generator.generateKey(); + byte[] key = secretKey.getEncoded(); + String keyEncode = Base64Encoder.encode(key); + System.out.println("key: " + keyEncode); + String plainData = "liolay"; + String encryptedData = encryptBySm4(plainData, key); + System.out.println("encrypt: " + encryptedData); + String decryptData = decryptBySm4(encryptedData, key); + System.out.println("decrypt: " + decryptData); + } +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/WebContext.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/WebContext.java new file mode 100644 index 0000000..1862d08 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/WebContext.java @@ -0,0 +1,102 @@ +package com.bonus.canteen.core.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 THREAD_CONTEXT = new InheritableThreadLocal() { + @Override + 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 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 getAccessToken() { + return Optional.ofNullable(this.accessToken); + } + + public Optional getRequest() { + return Optional.ofNullable(this.request); + } + + public Optional getResponse() { + return Optional.ofNullable(this.response); + } + + public Map 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 attributes) { + this.attributes = attributes; + } + + public Context() { + } + + public Context(final HttpServletRequest request, final HttpServletResponse response, final AccessToken accessToken, final Map attributes) { + this.request = request; + this.response = response; + this.accessToken = accessToken; + this.attributes = attributes; + } + } +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/InstantsSerializer.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/InstantsSerializer.java new file mode 100644 index 0000000..cada8d8 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/InstantsSerializer.java @@ -0,0 +1,11 @@ +package com.bonus.canteen.core.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); + } +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/JacksonConfiguration.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/JacksonConfiguration.java new file mode 100644 index 0000000..4ecbcf0 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/JacksonConfiguration.java @@ -0,0 +1,78 @@ +package com.bonus.canteen.core.config.json; + +import com.bonus.canteen.core.config.WebContext; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.Module; +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.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +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} +//) +@Configuration +//@ConditionalOnClass({ObjectMapper.class}) +public class JacksonConfiguration { + public static final StdSerializer 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.TYPE) { + @Override + public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (value == null) { + jsonGenerator.writeNull(); + } else { + Optional 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()); + } + } + } + }; + } +} diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/JsonNodeTypeHandler.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/JsonNodeTypeHandler.java new file mode 100644 index 0000000..c0a0b79 --- /dev/null +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/config/json/JsonNodeTypeHandler.java @@ -0,0 +1,57 @@ +package com.bonus.canteen.core.config.json; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedTypes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +@MappedTypes({JsonNode.class}) +public class JsonNodeTypeHandler extends BaseTypeHandler { + private static final Logger log = LoggerFactory.getLogger(JsonNodeTypeHandler.class); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void setNonNullParameter(PreparedStatement preparedStatement, int i, JsonNode jsonNode, JdbcType jdbcType) throws SQLException { + preparedStatement.setString(i, jsonNode.toString()); + } + + private JsonNode read(String json) { + if (json != null && !json.isEmpty()) { + try { + return this.objectMapper.readTree(json); + } catch (JsonProcessingException var3) { + log.warn("JSON parse failed", var3); + return null; + } + } else { + return null; + } + } + + @Override + public JsonNode getNullableResult(ResultSet rs, String columnName) throws SQLException { + String json = rs.getString(columnName); + return this.read(json); + } + + @Override + public JsonNode getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + String json = rs.getString(columnIndex); + return this.read(json); + } + + @Override + public JsonNode getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + String json = cs.getString(columnIndex); + return this.read(json); + } +}