Ver código fonte

登录防重放处理

jingyuanchao 2 anos atrás
pai
commit
fc9059ac44

+ 12 - 15
soc-auth/src/main/java/com/xunmei/auth/controller/TokenController.java

@@ -20,12 +20,11 @@ import javax.servlet.http.HttpServletRequest;
 
 /**
  * token 控制
- * 
+ *
  * @author xunmei
  */
 @RestController
-public class TokenController
-{
+public class TokenController {
     @Autowired
     private TokenService tokenService;
 
@@ -33,28 +32,28 @@ public class TokenController
     private SysLoginService sysLoginService;
 
     @PostMapping("login")
-    public R<?> login(@RequestBody LoginBody form)
-    {
+    public R<?> login(@RequestBody LoginBody form) {
 
         try {
+            if (!SecurityUtils.isApp()){
+                //登录重放问题处理,待前端完成放开
+                //sysLoginService.checkLogin(form.getAuthCode());
+            }
             LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword());
             userInfo.setToken(tokenService.createToken(userInfo).get("access_token").toString());
             // 获取登录token
             return R.ok(userInfo);
-        }catch (Exception e){
+        } catch (Exception e) {
             return R.fail(e.getMessage());
         }
         // 用户登录
-
     }
 
 
     @DeleteMapping("logout")
-    public R<?> logout(HttpServletRequest request)
-    {
+    public R<?> logout(HttpServletRequest request) {
         String token = SecurityUtils.getToken(request);
-        if (StringUtils.isNotEmpty(token))
-        {
+        if (StringUtils.isNotEmpty(token)) {
             String username = JwtUtils.getUserName(token);
             // 删除用户缓存记录
             AuthUtil.logoutByToken(token);
@@ -65,11 +64,9 @@ public class TokenController
     }
 
     @PostMapping("refresh")
-    public R<?> refresh(HttpServletRequest request)
-    {
+    public R<?> refresh(HttpServletRequest request) {
         LoginUser loginUser = tokenService.getLoginUser(request);
-        if (StringUtils.isNotNull(loginUser))
-        {
+        if (StringUtils.isNotNull(loginUser)) {
             // 刷新令牌有效期
             tokenService.refreshToken(loginUser);
             return R.ok();

+ 10 - 28
soc-auth/src/main/java/com/xunmei/auth/form/LoginBody.java

@@ -1,41 +1,23 @@
 package com.xunmei.auth.form;
 
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
 /**
  * 用户登录对象
- * 
+ *
  * @author xunmei
  */
-public class LoginBody
-{
-    /**
-     * 用户名
-     */
+@Data
+public class LoginBody {
+
+    @ApiModelProperty(value = "用户名", required = true)
     private String username;
 
-    /**
-     * 用户密码
-     */
+    @ApiModelProperty(value = "密码", required = true)
     private String password;
 
+    @ApiModelProperty(value = "随机码", required = true)
     private String authCode;
 
-    public String getUsername()
-    {
-        return username;
-    }
-
-    public void setUsername(String username)
-    {
-        this.username = username;
-    }
-
-    public String getPassword()
-    {
-        return password;
-    }
-
-    public void setPassword(String password)
-    {
-        this.password = password;
-    }
 }

+ 2 - 3
soc-auth/src/main/java/com/xunmei/auth/form/RegisterBody.java

@@ -2,10 +2,9 @@ package com.xunmei.auth.form;
 
 /**
  * 用户注册对象
- * 
+ *
  * @author xunmei
  */
-public class RegisterBody extends LoginBody
-{
+public class RegisterBody extends LoginBody {
 
 }

+ 39 - 0
soc-auth/src/main/java/com/xunmei/auth/service/SysLoginService.java

@@ -1,5 +1,8 @@
 package com.xunmei.auth.service;
 
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.ObjectUtil;
 import com.xunmei.common.core.constant.CacheConstants;
 import com.xunmei.common.core.constant.Constants;
 import com.xunmei.common.core.constant.SecurityConstants;
@@ -11,13 +14,17 @@ import com.xunmei.common.core.text.Convert;
 import com.xunmei.common.core.utils.StringUtils;
 import com.xunmei.common.core.utils.ip.IpUtils;
 import com.xunmei.common.redis.service.RedisService;
+import com.xunmei.common.security.utils.AsymmetricEncryptionUtil;
 import com.xunmei.common.security.utils.SecurityUtils;
 import com.xunmei.system.api.RemoteUserService;
 import com.xunmei.system.api.domain.User;
 import com.xunmei.system.api.model.LoginUser;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.BoundSetOperations;
 import org.springframework.stereotype.Component;
 
+import java.util.Date;
+
 /**
  * 登录校验方法
  * 
@@ -106,4 +113,36 @@ public class SysLoginService
         recordLogService.recordLogininfor(loginName, Constants.LOGOUT, "退出成功");
     }
 
+    public void checkLogin(String authCode) {
+        if (SecurityUtils.isApp()) {
+            return;
+        }
+        String decrypt = AsymmetricEncryptionUtil.decrypt(authCode);
+        if (ObjectUtil.isEmpty(decrypt) || null == decrypt) {
+            throw new RuntimeException("登录信息失效");
+        }
+        //如果redis中存在此key,说明已经登录过了
+        BoundSetOperations<String, Object> operations = redisService.getBoundSetOperations("loginAuth");
+        if (Boolean.TRUE.equals(operations.isMember(decrypt))) {
+            throw new RuntimeException("登录信息失效");
+        }
+        //如果不满足此规则,说明是伪造的
+        String[] split = decrypt.split(":");
+        if (split.length != 2) {
+            throw new RuntimeException("登录信息失效");
+        }
+        //如果不满足以下规则,说明是伪造的
+        String timeStamp = split[1];
+        if (!NumberUtil.isNumber(timeStamp)) {
+            throw new RuntimeException("登录信息失效");
+        }
+        //三分钟内有效
+        if (System.currentTimeMillis() - Long.parseLong(timeStamp) > 300000) {
+            throw new RuntimeException("登录信息失效");
+        }
+        operations.add(decrypt);
+        operations.expireAt(DateUtil.endOfDay(new Date()));
+
+
+    }
 }

+ 5 - 0
soc-common/soc-common-redis/src/main/java/com/xunmei/common/redis/service/RedisService.java

@@ -276,4 +276,9 @@ public class RedisService
     {
         return redisTemplate.keys(pattern);
     }
+
+
+    public BoundSetOperations getBoundSetOperations(String key){
+        return redisTemplate.boundSetOps(key);
+    }
 }

+ 242 - 0
soc-common/soc-common-security/src/main/java/com/xunmei/common/security/utils/AsymmetricEncryptionUtil.java

@@ -0,0 +1,242 @@
+package com.xunmei.common.security.utils;
+
+
+import cn.hutool.core.io.resource.ClassPathResource;
+import cn.hutool.core.io.resource.NoResourceException;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.security.*;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+
+@Slf4j
+public class AsymmetricEncryptionUtil {
+
+    private static final String RSA = "RSA";
+    private static final ConcurrentHashMap<String,Key> cache = new ConcurrentHashMap<>();
+
+    public static void main(String[] args) {
+        //test("123456");
+        String content = "GhZ/K5X9m/c2ArlDvH1H2IU0TOfAV0mR7vZJxXtanaS0GyNRPu/AzQld9Oe6LmaJRRSEleJQ6041u6IqeGKXnqsjrK1IQjwtJDgTAz3GvbxyOsedl0pol2FqdvQFw/y3rsFEFQsCYPPF7IYS/6YScSS+F7Qm/k+6fYryJG1xHoU=";
+        String decrypt = decrypt(content);
+        System.out.println("解密后明文为:"+decrypt);
+    }
+
+    public static void test(String content) {
+        try {
+            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
+            keyPairGenerator.initialize(1024);
+            KeyPair keyPair = keyPairGenerator.generateKeyPair();
+            //生成公钥
+            RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+            //生成密钥
+            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+            //加密
+            String encrypt = encryptByAsymmetric(content, rsaPublicKey);
+            System.out.println("加密密文:" + encrypt);
+            //解密
+            String decrypt = decryptByAsymmetric(encrypt, rsaPrivateKey);
+            System.out.println("原文内容:" + content);
+            System.out.println("解密明文:" + decrypt);
+        } catch (NoSuchAlgorithmException e) {
+            log.error("加解密失败", e);
+        }
+    }
+
+
+    /**
+     * 从文件中加载密钥
+     */
+    private static Key loadKeyFromFile(InputStream inputStream, Boolean isPublic) {
+
+        try {
+            //将InputStream读入Reader中
+            Reader reader = new InputStreamReader(inputStream);
+            //实例化一个StringBuilder以保存结果
+            StringBuilder result = new StringBuilder();
+            //读取每个字节并转换为char,添加到StringBuilder
+            for (int data = reader.read(); data != -1; data = reader.read()) {
+                result.append((char) data);
+            }
+            // 将文件内容转为字符串
+            String keyString = result.toString();
+            //String keyString = FileUtils.readFileToString(file, String.valueOf(StandardCharsets.UTF_8));
+            // 进行Base64解码
+            byte[] decode = cn.hutool.core.codec.Base64.decode(keyString);
+            // 获取密钥工厂
+            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
+            if (!isPublic) {
+                // 构建密钥规范
+                PKCS8EncodedKeySpec key = new PKCS8EncodedKeySpec(decode);
+                return keyFactory.generatePrivate(key);
+            }
+            // 构建密钥规范
+            X509EncodedKeySpec key = new X509EncodedKeySpec(decode);
+            // 获取公钥
+            return keyFactory.generatePublic(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException("获取密钥文件失败!");
+        }
+    }
+
+    public static String encrypt(String content) {
+        if (ObjectUtil.isEmpty(content)) {
+            return null;
+        }
+        if (cache.containsKey("publicKey")){
+            return encryptByAsymmetric(content, (RSAPublicKey) cache.get("publicKey"));
+        }
+        String result = null;
+        try {
+            final ClassPathResource resource = new ClassPathResource("key/publicKey.pub");
+            RSAPublicKey publicKey1 = (RSAPublicKey) loadKeyFromFile(resource.getStream(), true);
+            cache.put("publicKey",publicKey1);
+            result = encryptByAsymmetric(content, publicKey1);
+        } catch (Exception e) {
+            log.error("加密失败", e);
+        }
+        return result;
+    }
+
+    public static String decrypt(String encrypt) {
+        if (ObjectUtil.isEmpty(encrypt)) {
+            return null;
+        }
+        if (cache.containsKey("privateKey")){
+            return decryptByAsymmetric(encrypt, (RSAPrivateKey) cache.get("privateKey"));
+        }
+        String result = null;
+        try {
+            ClassPathResource resource = new ClassPathResource("key/private.pri");
+            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) loadKeyFromFile(resource.getStream(), false);
+            cache.put("privateKey",rsaPrivateKey);
+            result = decryptByAsymmetric(encrypt, rsaPrivateKey);
+        } catch (NoResourceException e) {
+            log.error("解密失败", e);
+        }
+        return result;
+    }
+
+    /**
+     * 解密
+     *
+     * @param encrypted : 密文
+     * @param key       : 密钥
+     * @return : 原文
+     * @throws Exception
+     */
+    private static String decryptByAsymmetric(String encrypted, Key key) {
+        try {
+            // 获取Cipher对象
+            Cipher cipher = Cipher.getInstance(RSA);
+            // 初始化模式(解密)和密钥
+            cipher.init(Cipher.DECRYPT_MODE, key);
+            return new String(getMaxResultDecrypt(encrypted, cipher));
+        } catch (
+                Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException("解密失败!");
+        }
+    }
+
+
+    /**
+     * 加密
+     *
+     * @param content : 加密内容
+     * @param key     : 密钥(公钥/密钥)
+     * @return : 密文
+     * @throws Exception
+     */
+    private static String encryptByAsymmetric(String content, Key key) {
+        try {
+            // 获取Cipher对象
+            Cipher cipher = Cipher.getInstance(RSA);
+            // 初始化模式(加密)和密钥
+            cipher.init(Cipher.ENCRYPT_MODE, key);
+            byte[] resultBytes = getMaxResultEncrypt(content, cipher);
+            return Base64.encodeBase64String(resultBytes);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException("加密失败!");
+        }
+    }
+
+    /**
+     * 分段处理加密数据
+     *
+     * @param content : 加密文本
+     * @param cipher  : Cipher对象
+     * @return
+     */
+    private static byte[] getMaxResultEncrypt(String content, Cipher cipher) throws Exception {
+        byte[] inputArray = content.getBytes();
+        int inputLength = inputArray.length;
+        // 最大加密字节数,超出最大字节数需要分组加密
+        int MAX_ENCRYPT_BLOCK = 117;
+        // 标识
+        int offSet = 0;
+        byte[] resultBytes = {};
+        byte[] cache = {};
+        while (inputLength - offSet > 0) {
+            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
+                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
+                offSet += MAX_ENCRYPT_BLOCK;
+            } else {
+                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
+                offSet = inputLength;
+            }
+            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
+            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
+        }
+        return resultBytes;
+    }
+
+    /**
+     * 分段处理解密数据
+     *
+     * @param decryptText : 加密文本
+     * @param cipher      : Cipher对象
+     * @throws Exception
+     */
+    private static byte[] getMaxResultDecrypt(String decryptText, Cipher cipher) throws Exception {
+        byte[] inputArray = Base64.decodeBase64(decryptText.getBytes(StandardCharsets.UTF_8));
+        int inputLength = inputArray.length;
+
+        // 最大解密字节数,超出最大字节数需要分组加密
+        int MAX_ENCRYPT_BLOCK = 128;
+        // 标识
+        int offSet = 0;
+        byte[] resultBytes = {};
+        byte[] cache = {};
+        while (inputLength - offSet > 0) {
+            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
+                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
+                offSet += MAX_ENCRYPT_BLOCK;
+            } else {
+                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
+                offSet = inputLength;
+            }
+            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
+            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
+        }
+        return resultBytes;
+    }
+
+
+}
+

+ 25 - 27
soc-common/soc-common-security/src/main/java/com/xunmei/common/security/utils/SecurityUtils.java

@@ -1,6 +1,7 @@
 package com.xunmei.common.security.utils;
 
 import cn.hutool.core.codec.Base64;
+import cn.hutool.extra.spring.SpringUtil;
 import com.xunmei.common.core.constant.SecurityConstants;
 import com.xunmei.common.core.constant.TokenConstants;
 import com.xunmei.common.core.context.SecurityContextHolder;
@@ -14,62 +15,58 @@ import javax.crypto.Cipher;
 import javax.crypto.spec.SecretKeySpec;
 import javax.servlet.http.HttpServletRequest;
 import java.nio.charset.StandardCharsets;
+import java.util.Optional;
 import java.util.UUID;
 
 /**
  * 权限获取工具类
- * 
+ *
  * @author xunmei
  */
-public class SecurityUtils
-{
+public class SecurityUtils {
+    private static final String[] deviceArray = new String[]{"iphone", "android", "ios", "harmonyos", "deviceInfo", "phone", "mobile", "wap", "netfront", "java", "opera mobi", "opera mini", "ucweb", "windows ce", "symbian", "series", "webos", "sony", "blackberry", "dopod", "nokia", "samsung", "palmsource", "xda", "pieplus", "meizu", "midp", "cldc", "motorola", "foma", "docomo", "up.browser", "up.link", "blazer", "helio", "hosin", "huawei", "novarra", "coolpad", "webos", "techfaith", "palmsource", "alcatel", "amoi", "ktouch", "nexian", "ericsson", "philips", "sagem", "wellcom", "bunjalloo", "maui", "smartphone", "iemobile", "spice", "bird", "zte-", "longcos", "pantech", "gionee", "portalmmm", "jig browser", "hiptop", "benq", "haier", "^lct", "320x320", "240x320", "176x220", "w3c ", "acs-", "alav", "alca", "amoi", "audi", "avan", "benq", "bird", "blac", "blaz", "brew", "cell", "cldc", "cmd-", "dang", "doco", "eric", "hipt", "inno", "ipaq", "java", "jigs", "kddi", "keji", "leno", "lg-c", "lg-d", "lg-g", "lge-", "maui", "maxo", "midp", "mits", "mmef", "mobi", "mot-", "moto", "mwbp", "nec-", "newt", "noki", "oper", "palm", "pana", "pant", "phil", "play", "port", "prox", "qwap", "sage", "sams", "sany", "sch-", "sec-", "send", "seri", "sgh-", "shar", "sie-", "siem", "smal", "smar", "sony", "sph-", "symb", "t-mo", "teli", "tim-"/*, "tosh"*/, "tsm-", "upg1", "upsi", "vk-v", "voda", "wap-", "wapa", "wapi", "wapp", "wapr", "webc", "winw", "winw", "xda", "xda-", "Googlebot-Mobile", "dart", "flutter", "xunmeiapp"};
 
     private static final String KEY = "rDWBHusbFTlOURS4";
+
     /**
      * 获取用户ID
      */
-    public static Long getUserId()
-    {
+    public static Long getUserId() {
         return SecurityContextHolder.getUserId();
     }
 
     /**
      * 获取用户名称
      */
-    public static String getUsername()
-    {
+    public static String getUsername() {
         return SecurityContextHolder.getUserName();
     }
 
     /**
      * 获取用户key
      */
-    public static String getUserKey()
-    {
+    public static String getUserKey() {
         return SecurityContextHolder.getUserKey();
     }
 
     /**
      * 获取登录用户信息
      */
-    public static LoginUser getLoginUser()
-    {
+    public static LoginUser getLoginUser() {
         return SecurityContextHolder.get(SecurityConstants.LOGIN_USER, LoginUser.class);
     }
 
     /**
      * 获取请求token
      */
-    public static String getToken()
-    {
+    public static String getToken() {
         return getToken(ServletUtils.getRequest());
     }
 
     /**
      * 根据request获取请求token
      */
-    public static String getToken(HttpServletRequest request)
-    {
+    public static String getToken(HttpServletRequest request) {
         // 从header获取token标识
         String token = request.getHeader(TokenConstants.AUTHENTICATION);
         return replaceTokenPrefix(token);
@@ -78,11 +75,9 @@ public class SecurityUtils
     /**
      * 裁剪token前缀
      */
-    public static String replaceTokenPrefix(String token)
-    {
+    public static String replaceTokenPrefix(String token) {
         // 如果前端设置了令牌前缀,则裁剪掉前缀
-        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX))
-        {
+        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
             token = token.replaceFirst(TokenConstants.PREFIX, "");
         }
         return token;
@@ -90,12 +85,11 @@ public class SecurityUtils
 
     /**
      * 是否为管理员
-     * 
+     *
      * @param userId 用户ID
      * @return 结果
      */
-    public static boolean isAdmin(Long userId)
-    {
+    public static boolean isAdmin(Long userId) {
         return userId != null && 1L == userId;
     }
 
@@ -105,8 +99,7 @@ public class SecurityUtils
      * @param password 密码
      * @return 加密字符串
      */
-    public static String encryptPassword(String password)
-    {
+    public static String encryptPassword(String password) {
         BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
         return passwordEncoder.encode(password);
     }
@@ -114,12 +107,11 @@ public class SecurityUtils
     /**
      * 判断密码是否相同
      *
-     * @param rawPassword 真实密码
+     * @param rawPassword     真实密码
      * @param encodedPassword 加密后字符
      * @return 结果
      */
-    public static boolean matchesPassword(String rawPassword, String encodedPassword)
-    {
+    public static boolean matchesPassword(String rawPassword, String encodedPassword) {
         BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
         return passwordEncoder.matches(rawPassword, encodedPassword);
     }
@@ -176,4 +168,10 @@ public class SecurityUtils
                 .toString());
     }
 
+    public static boolean isApp() {
+        final HttpServletRequest request = Optional.ofNullable(ServletUtils.getRequest()).orElseGet(() -> SpringUtil.getBean(HttpServletRequest.class));
+        final String ua = request.getHeader("User-Agent");
+        return ua != null && org.apache.commons.lang3.StringUtils.containsAny(ua.toLowerCase(), deviceArray);
+    }
+
 }

+ 1 - 0
soc-common/soc-common-security/src/main/resources/key/private.pri

@@ -0,0 +1 @@
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIpbXqHqR2pC3wET8ZEygRVHT/EhKcZPn2nM5Mu/xqrpFNQShvBo3F5ScacTzIImfAFKGcxfHNeJfaxC4oAhvg/7VYMSS4XjDt2HCmxwb+BnnMLGNDUx+3LDkLRVhDqAOkwGSy7BN8A1QIvjSSJGry+YgGRUQgLH/0RuUsoZVNHPAgMBAAECgYAH9PM/SpLq2IesrzHwUMA9sgk169tULVYUppTt5syNHbR18c7S2qT5w7IHksrrHT16cYGEUF//QUf59SrDha1Bdhf5SIdU0efOT9071qhsfEb1d8aWmzdnRuQ9GpEFkz/gTPNqIBo0po3HnEx77ZkvkNDPvCt7bXUY3pE3USuEuQJBAN4eJeam4STzewnEhVeu6evaYEWMClFewlF+Po1aR9zdFuS7wgidK6GKCMlbdbs1qt2GdLGOoqKCLbq8Ts6zRssCQQCfdk1yPaY4h13knTQPbjk0I4gDijANghkYG4/gm4EwaJTHG1cEyljA64xmvgt+so/v/4lHDvZlKhCt4GOg1fyNAkB3rCqjgRog/IY4Fwf43CbwijIAhpkwiiuMGMa/BfteBkjFaFv8pCHT4Tkms/5UpW+v9zd8SutZP2ZADCExwOnNAkB/1fxyWD/4U3UPQIOq1ydeyBlTFSY+vgxWCkqGusOPxld3Y1CYk4shZfRRrYtTj9zafxZTdAhNw7JJYnkcSFBFAkEAlYg7RTebs7pRewmmH/NTDqLL5luxPi337+NATWSeNnsSC0J6zP8IdIfx1zzi6+MMwf1AdppWCsrITInEf2LF2g==

+ 1 - 0
soc-common/soc-common-security/src/main/resources/key/publicKey.pub

@@ -0,0 +1 @@
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCKW16h6kdqQt8BE/GRMoEVR0/xISnGT59pzOTLv8aq6RTUEobwaNxeUnGnE8yCJnwBShnMXxzXiX2sQuKAIb4P+1WDEkuF4w7dhwpscG/gZ5zCxjQ1Mftyw5C0VYQ6gDpMBksuwTfANUCL40kiRq8vmIBkVEICx/9EblLKGVTRzwIDAQAB