记得上下班打卡 | git大法好,push需谨慎

Commit d590089a authored by 姜秀龙's avatar 姜秀龙

Merge branch 'refs/heads/jxl-card' into test-ecs

parents 7356741c 4cbd21cc
package com.liquidnet.service.adam.constant;
public final class AdamTpaConst {
private AdamTpaConst() {
}
/** 国家网络身份认证 */
public static final String PLATFORM_NIA = "NIA";
}
package com.liquidnet.service.adam.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.io.Serializable;
@ApiModel(value = "AdamNiaLoginParam", description = "国家网络身份认证登录入参(R01 凭证认证)")
@Data
public class AdamNiaLoginParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(position = 1, required = true, value = "业务序列号,须与前端 SDK bizSeq 一致[32]")
@NotBlank(message = "bizSeq不能为空")
@Size(min = 32, max = 32, message = "bizSeq长度须为32位")
private String bizSeq;
@ApiModelProperty(position = 2, required = true, value = "认证请求数据 idCardAuthData")
@NotBlank(message = "idCardAuthData不能为空")
private String idCardAuthData;
}
......@@ -22,7 +22,7 @@ public class AdamThirdPartParam implements Serializable {
@ApiModelProperty(position = 13, required = true, value = "头像[255]", example = "http://pic.zhengzai.tv/default/avatar.png")
@Size(max = 255, message = "已超出头像链接长度限制")
private String avatar;
@ApiModelProperty(position = 14, required = true, value = "平台类型[255]", allowableValues = "WEIBO,WECHAT,QQ")
@ApiModelProperty(position = 14, required = true, value = "平台类型[255]", allowableValues = "WEIBO,WECHAT,QQ,NIA")
@Pattern(regexp = LnsRegex.Valid.TRIPLE_PF_FOR_ULGOIN, message = "平台类型无效")
@NotBlank(message = "平台类型不能为空")
private String platform;
......
package com.liquidnet.service.adam.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Getter;
import java.io.Serializable;
/**
* 网证认证成功返回,仅做身份认证不判断绑定。
* 前端拿到 openId 后自行调用 login/tpa 完成登录/注册绑定判断。
*/
@Getter
@Builder
@ApiModel(value = "AdamNiaAuthVo", description = "网证认证结果")
public class AdamNiaAuthVo implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "平台类型,固定 NIA")
private final String platform;
@ApiModelProperty(value = "网证用户唯一标识(40字节标识的标准Base64,56字符),作为 login/tpa 的 openId 使用")
private final String openId;
}
......@@ -119,7 +119,7 @@ public class LnsRegex {
/**
* 支持的第三方账号平台类型(用户中心:登录注册)
*/
public static final String TRIPLE_PF_FOR_ULGOIN = "\\b(WEIBO|WECHAT|QQ)\\b";
public static final String TRIPLE_PF_FOR_ULGOIN = "\\b(WEIBO|WECHAT|QQ|NIA)\\b";
/**
* 支持的支付终端
*/
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.liquidnet</groupId>
<artifactId>liquidnet-common-third</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>liquidnet-common-third-secure-access</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.liquidnet</groupId>
<artifactId>liquidnet-common-base</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.anicert.module</groupId>
<artifactId>anicert-sign-bouncycastle</artifactId>
<version>3.2.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/anicert-sign-bouncycastle-3.2.0.jar</systemPath>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.26</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-setting</artifactId>
<version>5.8.26</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-log</artifactId>
<version>5.8.26</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.68</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
</project>
package cn.anicert.secure.access.bean;
import java.io.Serializable;
public class SARequestPackageVO implements Serializable {
private static final long serialVersionUID = 1L;
private Object bizPackage;
private String customerNo;
private String forwardUrl;
public SARequestPackageVO() {
}
public SARequestPackageVO(Object bizPackage, String customerNo, String forwardUrl) {
this.bizPackage = bizPackage;
this.customerNo = customerNo;
this.forwardUrl = forwardUrl;
}
public Object getBizPackage() {
return bizPackage;
}
public void setBizPackage(Object bizPackage) {
this.bizPackage = bizPackage;
}
public String getCustomerNo() {
return customerNo;
}
public void setCustomerNo(String customerNo) {
this.customerNo = customerNo;
}
public String getForwardUrl() {
return forwardUrl;
}
public void setForwardUrl(String forwardUrl) {
this.forwardUrl = forwardUrl;
}
}
package cn.anicert.secure.access.bean;
import java.io.Serializable;
public class SARequestVO implements Serializable {
private static final long serialVersionUID = 1L;
private Object requestPackage;
private String requestSign;
public SARequestVO() {
}
public SARequestVO(Object requestPackage, String requestSign) {
this.requestPackage = requestPackage;
this.requestSign = requestSign;
}
public Object getRequestPackage() {
return requestPackage;
}
public void setRequestPackage(Object requestPackage) {
this.requestPackage = requestPackage;
}
public String getRequestSign() {
return requestSign;
}
public void setRequestSign(String requestSign) {
this.requestSign = requestSign;
}
}
package cn.anicert.secure.access.bean;
import java.io.Serializable;
public class SAResponseVO implements Serializable {
private static final long serialVersionUID = 1L;
private Object responsePackage;
private String responseSign;
public SAResponseVO() {
}
public SAResponseVO(Object responsePackage, String responseSign) {
this.responsePackage = responsePackage;
this.responseSign = responseSign;
}
public Object getResponsePackage() {
return responsePackage;
}
public void setResponsePackage(Object responsePackage) {
this.responsePackage = responsePackage;
}
public String getResponseSign() {
return responseSign;
}
public void setResponseSign(String responseSign) {
this.responseSign = responseSign;
}
}
package cn.anicert.secure.access.bean;
import java.io.Serializable;
public class SASignRequestPackageVO implements Serializable {
private static final long serialVersionUID = 1L;
private String customerNo;
private String originalData;
public SASignRequestPackageVO() {
}
public SASignRequestPackageVO(String customerNo, String originalData) {
this.customerNo = customerNo;
this.originalData = originalData;
}
public String getCustomerNo() {
return customerNo;
}
public void setCustomerNo(String customerNo) {
this.customerNo = customerNo;
}
public String getOriginalData() {
return originalData;
}
public void setOriginalData(String originalData) {
this.originalData = originalData;
}
}
package cn.anicert.secure.access.config;
import cn.anicert.secure.access.config.bean.AccessConfig;
import cn.anicert.secure.access.enums.Configs;
import cn.anicert.secure.access.exceptions.SecureAccessConfigException;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog;
import cn.hutool.setting.Setting;
import cn.hutool.setting.SettingUtil;
import java.util.Arrays;
public class ConfigReader {
private final static String CONFIG_FILE_NAME = "secure-access.setting";
private static Setting setting;
public static AccessConfig readConfig(String... filePath) {
if (filePath == null || filePath.length == 0) {
setting = SettingUtil.getFirstFound(CONFIG_FILE_NAME);
} else {
setting = SettingUtil.getFirstFound(filePath);
}
StaticLog.info("[安全接入平台]当前配置文件路径为:{}", setting.getSettingPath());
StaticLog.info("[安全接入平台]开始读取配置文件…………");
AccessConfig accessConfig = new AccessConfig();
Arrays.stream(Configs.values()).forEach(config -> {
if (config.isNecessary()) {
if (setting.containsKey(config.getKey())) {
String value = setting.getStr(config.getKey());
if (StrUtil.isBlank(value)) {
StaticLog.error("[安全接入平台]{}未配置", config.getName());
throwConfigException();
}
} else {
StaticLog.error("[安全接入平台]{}未配置", config.getName());
throwConfigException();
}
}
});
setting.autoLoad(true);
setting.toBean(accessConfig);
StaticLog.info("[安全接入平台]读取配置文件结束");
return accessConfig;
}
public static AccessConfig writeConfig(String key, String value) {
setting.set(key, value);
setting.store();
AccessConfig accessConfig = new AccessConfig();
return setting.toBean(accessConfig);
}
private static void throwConfigException() {
throw new SecureAccessConfigException("[安全接入平台]读取配置文件失败");
}
}
package cn.anicert.secure.access.config.bean;
public class AccessConfig {
//配置部分
private String orgId;
private String customerNo;
private String accessUrl;
private String signPrivateKey;
private String signVerifyPublicKey;
private String dataEncryptKey;
//请求部分
private int connectionRequestTimeout = 50000;
private int responseTimeout = 30000;
private int poolMaxConn = 100;
public String getCustomerNo() {
return customerNo;
}
public void setCustomerNo(String customerNo) {
this.customerNo = customerNo;
}
public String getAccessUrl() {
return accessUrl;
}
public void setAccessUrl(String accessUrl) {
this.accessUrl = accessUrl;
}
public String getSignPrivateKey() {
return signPrivateKey;
}
public void setSignPrivateKey(String signPrivateKey) {
this.signPrivateKey = signPrivateKey;
}
public String getSignVerifyPublicKey() {
return signVerifyPublicKey;
}
public void setSignVerifyPublicKey(String signVerifyPublicKey) {
this.signVerifyPublicKey = signVerifyPublicKey;
}
public String getDataEncryptKey() {
return dataEncryptKey;
}
public void setDataEncryptKey(String dataEncryptKey) {
this.dataEncryptKey = dataEncryptKey;
}
public int getConnectionRequestTimeout() {
return connectionRequestTimeout;
}
public void setConnectionRequestTimeout(int connectionRequestTimeout) {
this.connectionRequestTimeout = connectionRequestTimeout;
}
public int getResponseTimeout() {
return responseTimeout;
}
public void setResponseTimeout(int responseTimeout) {
this.responseTimeout = responseTimeout;
}
public int getPoolMaxConn() {
return poolMaxConn;
}
public void setPoolMaxConn(int poolMaxConn) {
this.poolMaxConn = poolMaxConn;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(String orgId) {
this.orgId = orgId;
}
}
package cn.anicert.secure.access.constants;
public class InterfaceConstant {
private static final String BASE_URL = "/secureaccess/interf";
public static final String UNIFIED_AUTH_URL = BASE_URL + "/unified_auth/request";
public static final String P7_SIGN_URL = BASE_URL + "/pkcs7/signDataByP7";
}
package cn.anicert.secure.access.core;
import cn.anicert.secure.access.bean.SAResponseVO;
import cn.anicert.secure.access.constants.InterfaceConstant;
import cn.anicert.secure.access.utils.ConfigUtil;
import cn.anicert.secure.access.utils.HttpUtil;
import cn.anicert.secure.access.utils.JSONUtil;
import cn.anicert.secure.access.utils.SignUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog;
public class SecureAccess {
public static void init(String... configPath) {
ConfigUtil.initConfig(configPath);
}
public static String unified_auth(String bizSeq, String reqStr) {
String url = ConfigUtil.getConfig().getAccessUrl() + InterfaceConstant.UNIFIED_AUTH_URL;
String responseStr = sendReq(bizSeq, url, reqStr);
if (StrUtil.isNotBlank(responseStr) && verifySign(bizSeq, responseStr)) {
return dealResult(bizSeq, responseStr);
}
return "";
}
public static String pkcs7Sign(String bizSeq, String reqStr) {
String url = ConfigUtil.getConfig().getAccessUrl() + InterfaceConstant.P7_SIGN_URL;
String responseStr = sendReq(bizSeq, url, reqStr);
if (StrUtil.isNotBlank(responseStr) && verifySign(bizSeq, responseStr)) {
return dealResult(bizSeq, responseStr);
}
return "";
}
private static String sendReq(String bizSeq, String url, String reqStr) {
try {
StaticLog.info("[安全接入平台],bizSeq--[{}]--请求开始", bizSeq);
StaticLog.info("[安全接入平台],bizSeq--[{}]--请求数据--[{}]", bizSeq, reqStr);
String responseStr = HttpUtil.getInstance().postRequest(url, reqStr);
if (StrUtil.isNotBlank(responseStr)) {
StaticLog.info("[安全接入平台],bizSeq--[{}]--请求结果--[{}]", bizSeq, responseStr);
return responseStr;
} else {
StaticLog.error("[安全接入平台],bizSeq--[{}]--请求返回结果为空", bizSeq);
}
} catch (Exception e) {
StaticLog.error(e, "[安全接入平台],bizSeq--[{}]--请求失败", bizSeq);
}
return "";
}
private static boolean verifySign(String bizSeq, String result) {
try {
SAResponseVO responseVO = JSONUtil.json2Object(result, SAResponseVO.class);
if (StrUtil.isNotBlank(responseVO.getResponseSign())) {
String signStr = JSONUtil.toJson(responseVO.getResponsePackage());
boolean verifyResult = SignUtil.verifySign(signStr, responseVO.getResponseSign());
if (!verifyResult) {
StaticLog.error("[安全接入平台],bizSeq--[{}]--验签不匹配", bizSeq);
}
return verifyResult;
} else {
StaticLog.error("[安全接入平台],bizSeq--[{}]--验签报文体为空", bizSeq);
return false;
}
} catch (Exception e) {
StaticLog.error(e, "[安全接入平台],bizSeq--[{}]--验签异常", bizSeq);
return false;
}
}
private static String dealResult(String bizSeq, String result) {
try {
SAResponseVO responseVO = JSONUtil.json2Object(result, SAResponseVO.class);
return JSONUtil.toJson(responseVO.getResponsePackage());
} catch (Exception e) {
StaticLog.error(e, "[安全接入平台],bizSeq--[{}]--处理返回报文异常", bizSeq);
return "";
}
}
}
package cn.anicert.secure.access.crypto.service;
import javax.security.auth.Subject;
/**
* @Description
* @Date 2024/5/29 15:30
* @Author yourongbin
**/
public interface ISoftCryptoService {
/**
* 数字信封加密
* @param cert 证书的base64编码
* @param data 加密数据
* @return
*/
public String encodeEnvelop(String cert, String data) throws Exception ;
/**
* 数字信封加密
* @param cert 证书的base64编码
* @param dataByte
* @return
*/
public String encodeEnvelop(String cert, byte[] dataByte) throws Exception;
/**
* 数字信封解密 返回明文
* @param priKey
* @param envelop
* @return
* @throws Exception
*/
public String decodeEnvelop(String priKey, String envelop) throws Exception;
/**
* 数字信封解密 返回明文
* @param priKey
* @param envelop
* @return
* @throws Exception
*/
public String decodeEnvelop(String priKey, byte[] envelop) throws Exception;
/**
* sm2加密
* @param publicKey
* @param data
* @return
* @throws Exception
*/
public String encrypt(String publicKey, String data) throws Exception;
/**
* sm2解密
* @param privateKey
* @param encryptedData
* @return
* @throws Exception
*/
public String decrypt(String privateKey, String encryptedData) throws Exception;
/**
* p7加签
* @param prikey 私钥
* @param cert 证书
* @param oriData 数据
* @return
* @throws Exception
*/
public String p7Sign(String prikey, String cert ,String oriData) throws Exception;
/**
* p7验签
* @param sign 签名值
* @param source 原文
* @return
* @throws Exception
*/
public Boolean p7Verify(String sign,String source) throws Exception;
/**
* p1加签
* @param prikey
* @param oriData
* @return
* @throws Exception
*/
public String p1Sign(String prikey,String oriData) throws Exception;
/**
* p1验签
* @param publicKey
* @param data
* @param signature
* @return
* @throws Exception
*/
public boolean verify(String publicKey, String data, String signature) throws Exception;
/**
* sm2加签
* @param prikey
* @param oriData
* @return
* @throws Exception
*/
public String sm2Sign(String prikey,String oriData) throws Exception;
/**
* sm2验签
* @param publicKey
* @param data
* @param signature
* @return
* @throws Exception
*/
public boolean sm2SignVerify(String publicKey, String data, String signature) throws Exception;
/**
* p1验签
* @param publicKey
* @param data
* @param charset
* @param signature
* @return
* @throws Exception
*/
public boolean verify(String publicKey, String data, String charset, String signature) throws Exception;
/**
* 获取业务站点号
* @param signature
* @return
* @throws Exception
*/
public String getCommonNameFromP7Sign(String signature) throws Exception;
/**
* sm3算法
* @param source
* @return
* @throws Exception
*/
public String sm3Hash(String source) throws Exception;
}
package cn.anicert.secure.access.crypto.service.impl;
import cn.anicert.module.sign.bc.ICryptoService;
import cn.anicert.module.sign.bc.mix.MixCryptoFactory;
import cn.anicert.module.sign.bc.std.Asymmetric;
import cn.anicert.module.sign.bc.std.Symmetric;
import cn.anicert.secure.access.crypto.util.CertUtils;
import cn.anicert.secure.access.crypto.util.SM2Utils;
import cn.anicert.secure.access.crypto.util.SM3Utils;
import cn.anicert.secure.access.crypto.service.ISoftCryptoService;
import cn.hutool.core.io.FileUtil;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* @Description
* @Date 2024/5/29 15:35
* @Author yourongbin
**/
public class SoftCryptoServiceImpl implements ISoftCryptoService {
private ICryptoService signService = MixCryptoFactory.getInstance(Asymmetric.SM2, Symmetric.SM4);
@Override
public String encodeEnvelop(String cert, String data) throws Exception {
return encodeEnvelop(cert, data.getBytes());
}
@Override
public String encodeEnvelop(String cert, byte[] dataByte) throws Exception {
return Base64.getEncoder().encodeToString(SM2Utils.encodeEnvelop(cert, dataByte));
}
@Override
public String decodeEnvelop(String priKey, String envelop) throws Exception {
return decodeEnvelop(priKey, Base64.getDecoder().decode(envelop));
}
@Override
public String decodeEnvelop(String priKey, byte[] envelop) throws Exception {
return new String(SM2Utils.decodeEnvelop(priKey, envelop), StandardCharsets.UTF_8);
}
@Override
public String encrypt(String publicKey, String data) throws Exception {
return SM2Utils.encrypt(publicKey, data);
}
@Override
public String decrypt(String privateKey, String encryptedData) throws Exception {
return SM2Utils.decrypt(privateKey, encryptedData);
}
@Override
public String p7Sign(String prikey, String cert, String oriData) throws Exception {
return SM2Utils.pkcs7SignDetach(prikey, cert, oriData);
}
@Override
public Boolean p7Verify(String sign, String source) throws Exception {
return SM2Utils.pkcs7Verify(Base64.getDecoder().decode(sign), source.getBytes());
}
@Override
public String p1Sign(String prikey, String oriData) throws Exception {
return SM2Utils.sign(prikey, oriData);
}
@Override
public boolean verify(String publicKey, String data, String signature) throws Exception {
return verify(publicKey, data, "utf-8", signature);
}
@Override
public String sm2Sign(String prikey, String oriData) throws Exception {
return Base64.getEncoder().encodeToString(
signService.sign(prikey, oriData.getBytes(StandardCharsets.UTF_8)));
}
@Override
public boolean sm2SignVerify(String publicKey, String data, String signature) throws Exception {
byte[] byteSign = Base64.getDecoder().decode(signature);
byte[] byteBody = data.getBytes(StandardCharsets.UTF_8);
return signService.verifySign(publicKey, byteSign, byteBody);
}
@Override
public boolean verify(String publicKey, String data, String charset, String signature) throws Exception {
return SM2Utils.verify(publicKey, data, charset, signature);
}
@Override
public String getCommonNameFromP7Sign(String signature) throws Exception {
return CertUtils.getSubjectDn(signature);
}
@Override
public String sm3Hash(String source) throws Exception {
return SM3Utils.sm3Hash(source.getBytes("UTF-16LE"));
}
}
package cn.anicert.secure.access.crypto.util;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Primitive;
import java.io.IOException;
/**
* @Description
* @Date 2024/5/30 17:18
* @Author yourongbin
**/
public class ASN1Util {
public static ASN1Object checkAndGetASN1Object(byte[] in)
throws IllegalArgumentException
{
ASN1InputStream din = null;
ASN1Primitive pkcs;
try
{
din = new ASN1InputStream(in);
pkcs = din.readObject();
} catch (IOException var11) {
throw new IllegalArgumentException("failed to construct sequence from byte[]:" + var11.getMessage());
} finally {
if (din != null) {
try {
din.close();
} catch (Exception var10) {
var10.printStackTrace();
}
}
}
return pkcs;
}
}
package cn.anicert.secure.access.crypto.util;
public class Base64 {
public Base64() {
}
public static String encode(byte[] data) {
return org.apache.commons.codec.binary.Base64.encodeBase64String(data);
}
public static byte[] decode(String data) {
return org.apache.commons.codec.binary.Base64.decodeBase64(data);
}
}
package cn.anicert.secure.access.crypto.util;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
/**
* @company cn.anicert
* @author: wangpengyuan
* @create: 2024-03-07
* @description 密码学基础类
**/
public class BaseCryptoUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
}
\ No newline at end of file
package cn.anicert.secure.access.crypto.util;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.SignedData;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import java.io.ByteArrayInputStream;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Enumeration;
import java.util.StringTokenizer;
/**
* @company cn.anicert
* @author: wangpengyuan
* @create: 2024-03-07
* @description 证书工具类
**/
public class CertUtils extends BaseCryptoUtil {
public static Certificate parseSM2Certificate(String cert) throws Exception {
byte[] certBytes = Base64.decode(cert);
return parseSM2Certificate(certBytes);
}
public static Certificate parseSM2Certificate(byte[] certBytes) throws Exception {
ByteArrayInputStream is = new ByteArrayInputStream(certBytes);
ASN1InputStream asn1InputStream = new ASN1InputStream(is);
ASN1Primitive asn1Primitive = asn1InputStream.readObject();
return Certificate.getInstance(asn1Primitive);
}
public static String getCertInfo(Certificate cert, long infoType) {
String info = null;
try {
X500Name subjectX509Name = cert.getSubject();
String subjectName = subjectX509Name.toString().trim();
X500Name issuerX509Name = cert.getIssuer();
String issuerName = issuerX509Name.toString().trim();
// 用户国家名
String striC = null;
// 用户组织名
String striO = null;
// 用户部门名
String striOU = null;
// 用户省州名
String striS = null;
// 用户通用名
String striCN = null;
// 用户城市名
String striL = null;
// 用户EMAIL地址
String striE = null;
StringTokenizer stSubjectDN = new StringTokenizer(subjectName, ",");
while (stSubjectDN.hasMoreTokens()) {
String strSubj = stSubjectDN.nextToken().trim();
if (strSubj.contains("C=")) {
striC = strSubj.substring(strSubj.indexOf("C=") + 2);
} else if (strSubj.contains("O=")) {
striO = strSubj.substring(strSubj.indexOf("O=") + 2);
} else if (strSubj.contains("OU=")) {
striOU = strSubj.substring(strSubj.indexOf("OU=") + 3);
} else if (strSubj.contains("CN=")) {
striCN = strSubj.substring(strSubj.indexOf("CN=") + 3);
} else if (strSubj.contains("L=")) {
striL = strSubj.substring(strSubj.indexOf("L=") + 2);
} else if (strSubj.contains("EMAILADDRESS=")) {// E 和 EMAILADDRESS 都是email address的意思,优先取E
striE = strSubj.substring(strSubj.indexOf("EMAILADDRESS=") + 13);
} else if (strSubj.contains("E=")) {
striE = strSubj.substring(strSubj.indexOf("E=") + 2);
} else if (strSubj.contains("ST=")) {
striS = strSubj.substring(strSubj.indexOf("ST=") + 3);
}
}
// 用户国家名(备用名)
String strC = null;
// 用户组织名(备用名)
String strO = null;
// 用户部门名(备用名)
String strOU = null;
// 用户省州名(备用名)
String strS = null;
// 用户通用名(备用名)
String strCN = null;
// 用户城市名(备用名)
String strL = null;
// 用户EMAIL地址(备用名)
String strE = null;
Extensions extensions = cert.getTBSCertificate().getExtensions();
int itype = (int) infoType;
String infostr = null;
switch (itype) {
case 1:
info = String.valueOf(cert.getVersion());// TODO test
break;
case 2:
info = Hex.toHexString(cert.getSerialNumber().getValue().toByteArray());// TODO
break;
case 3:
info = cert.getSignatureAlgorithm().getAlgorithm().getId();// TODO
break;
case 8:
infostr = issuerName;
StringTokenizer stIssuerDN = new StringTokenizer(infostr, ",");
while (stIssuerDN.hasMoreTokens()) {
String strIss = stIssuerDN.nextToken().trim();
if (strIss.indexOf("CN=") == 0) {
info = strIss.substring(3);
}
}
break;
case 11:
break;
case 12:
break;
case 13:
info = striC;
break;
case 14:
info = striO;
break;
case 15:
info = striOU;
break;
case 16:
info = striS;
break;
case 17:
info = striCN;
break;
case 18:
info = striL;
break;
case 19:
info = striE;
break;
case 20:
info = issuerName;
break;
case 21:
info = subjectName;
break;
case 22:
info = null;
break;
case 23:
info = strC;
break;
case 24:
info = strO;
break;
case 25:
info = strOU;
break;
case 26:
info = strS;
break;
case 27:
info = strCN;
break;
case 28:
info = strL;
break;
case 29:
info = strE;
break;
case 30:
SubjectPublicKeyInfo spki = cert.getSubjectPublicKeyInfo();
byte[] bPubKey = spki.getPublicKeyData().getBytes();
info = Base64.toBase64String(bPubKey);
break;
case 31:
break;
case 32:
Instant newEndDate = cert.getEndDate().getDate().toInstant();
LocalDateTime newEndTime = LocalDateTime.ofInstant(newEndDate, ZoneId.systemDefault());
info = DateTimeFormatter.ofPattern("yyyy/MM/dd/HH/mm/ss").format(newEndTime);
break;
default:
info = null;
break;
}
} catch (Exception e) {
info = null;
throw new RuntimeException(e);
}
return info;
}
public static String getSubjectDn(String signature) throws Exception {
try {
ContentInfo info = ContentInfo.getInstance(ASN1Util.checkAndGetASN1Object(Base64.decode(signature)));
SignedData sData = SignedData.getInstance(info.getContent());
Enumeration certEnum = sData.getCertificates().getObjects();
ASN1Sequence certSeq = (ASN1Sequence) certEnum.nextElement();
Certificate signCert = Certificate.getInstance(certSeq);
String subjectTemp = signCert.getSubject().toString();
String[] subjectArray = subjectTemp.split(",");
String subjectCN = "";
for (String subject : subjectArray) {
if (subject.toUpperCase().contains("CN=")) {
subjectCN = subject.substring(subject.toUpperCase().indexOf("CN=") + 3);
}
}
return subjectCN;
} catch (Exception e) {
throw new Exception();
}
}
// public static void main(String[] args) throws Exception{
// String sign = "MIIDogYKKoEcz1UGAQQCAqCCA5IwggOOAgEBMQ4wDAYIKoEcz1UBgxEFADAMBgoqgRzPVQYBBAIBoIICnjCCApowggI+oAMCAQICEhACBQMjATAQixyTax6fhHhfmTAMBggqgRzPVQGDdQUAMEQxCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhDSE5DVERJRDERMA8GA1UECwwIQ0hOQ1RESUQxDzANBgNVBAMMBllXQ0EwMTAeFw0yMzAxMzAwMjE5MjNaFw0zMzAxMjcwMjE5MjNaMDExDzANBgNVBAMMBlJaRlcwMjERMA8GA1UECwwIQ0hOQ1RESUQxCzAJBgNVBAYTAkNOMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEsHlD650v3WqpL9e4Ttn4tqmnslXLCvnpY2mSWc97oeDzDojKMlTiabGl3RDOMtfOAS0GTLaUOSqJtpsjQ8//8KOCAR8wggEbMA4GA1UdDwEB/wQEAwIGwDAJBgNVHRMEAjAAMBAGCGCGSAGG+EQCBARYWFhYMBIGCiqBHIbvMgIBARcEBFhYWFgwgZcGA1UdHwSBjzCBjDBMoEqgSKRGMEQxDzANBgNVBAMMBllXQ0EwMTERMA8GA1UECwwIQ0hOQ1RESUQxETAPBgNVBAoMCENITkNURElEMQswCQYDVQQGEwJDTjA8oDqgOIY2aHR0cDovLzE3Mi4yOC4zMC44OjE4MDYwL2Nhd2ViL2NybC9ZV0NBMDEvWVdDQTAxXzAuY3JsMB0GA1UdDgQWBBQjW5xU5r7FxfDzwGmuIURdI4wPITAfBgNVHSMEGDAWgBSbWAb0NoA4TNCfLq9OLouWdM2BqDAMBggqgRzPVQGDdQUAA0gAMEUCICYfXqrK3a+Urgs7MpV54ayfHXJwLzpqHnmy3h82oarjAiEAvJtl9XE0Pfp59eiuZcTdsFiWZizZCpRQE75qmDxI+jIxgcgwgcUCAQEwWjBEMQswCQYDVQQGEwJDTjERMA8GA1UECgwIQ0hOQ1RESUQxETAPBgNVBAsMCENITkNURElEMQ8wDQYDVQQDDAZZV0NBMDECEhACBQMjATAQixyTax6fhHhfmTAMBggqgRzPVQGDEQUAMA0GCSqBHM9VAYItAQUABEcwRQIhALey3ijM74mPVSZHxnrr5acW0RX97/EO+D+U4aLPNcpWAiBzSme9krCy4ojVo9QDe+ifq7bSGffbBsC16yAburM7cQ==";
// System.out.println(getSubjectDn(sign));
// }
}
\ No newline at end of file
package cn.anicert.secure.access.crypto.util;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.math.ec.ECCurve;
public class SM2KeyUtil {
private static final X9ECParameters ecParameters;
private static final ECNamedCurveSpec sm2Spec;
private static final BouncyCastleProvider bcProvider;
private static final KeyPairGenerator keyGenerator;
private static final KeyFactory keyFactory;
private static final ECCurve curve;
public SM2KeyUtil() {
}
public static KeyPair genKeyPair() {
return keyGenerator.generateKeyPair();
}
public static byte[] encodeWithPoint(BCECPublicKey pub) {
return pub.getQ().getEncoded(false);
}
public static BCECPublicKey decodeWithPoint(byte[] data) throws InvalidKeySpecException {
ECPoint point = EC5Util.convertPoint(curve.decodePoint(data));
return (BCECPublicKey)keyFactory.generatePublic(new ECPublicKeySpec(point, sm2Spec));
}
public static String encodeStrWithPoint(BCECPublicKey pub) {
return Base64.encode(encodeWithPoint(pub));
}
public static BCECPublicKey decodeWithPoint(String base64) throws InvalidKeySpecException {
return decodeWithPoint(Base64.decode(base64));
}
public static String getX64(BCECPublicKey pub) {
return Base64.encode(pub.getQ().getXCoord().getEncoded());
}
public static String getY64(BCECPublicKey pub) {
return Base64.encode(pub.getQ().getYCoord().getEncoded());
}
public static BCECPublicKey fromXY(String x64, String y64) throws InvalidKeySpecException {
return fromXY(Base64.decode(x64), Base64.decode(y64));
}
public static byte[] getX(BCECPublicKey pub) {
return pub.getQ().getXCoord().getEncoded();
}
public static byte[] getY(BCECPublicKey pub) {
return pub.getQ().getYCoord().getEncoded();
}
public static BCECPublicKey fromXY(byte[] x, byte[] y) throws InvalidKeySpecException {
ECPoint point = EC5Util.convertPoint(curve.createPoint(new BigInteger(1, x), new BigInteger(1, y)));
return (BCECPublicKey)keyFactory.generatePublic(new ECPublicKeySpec(point, sm2Spec));
}
public static byte[] encodeWithBigInteger(BCECPrivateKey pri) {
return pri.getD().toByteArray();
}
public static BCECPrivateKey decodeWithBigInteger(byte[] data) throws InvalidKeySpecException {
return (BCECPrivateKey)keyFactory.generatePrivate(new ECPrivateKeySpec(new BigInteger(1, data), sm2Spec));
}
public static String encodeStrWithBigInteger(BCECPrivateKey pri) {
return Base64.encode(encodeWithBigInteger(pri));
}
public static BCECPrivateKey decodeWithBigInteger(String base64) throws InvalidKeySpecException {
return decodeWithBigInteger(Base64.decode(base64));
}
public static byte[] encodeWithKey(Key key) {
return key.getEncoded();
}
public static BCECPublicKey decodeWithX509(byte[] data) throws InvalidKeySpecException {
return (BCECPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(data));
}
public static BCECPrivateKey decodeWithPKCS8(byte[] data) throws InvalidKeySpecException {
return (BCECPrivateKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(data));
}
public static String encodeStrWithKey(Key key) {
return Base64.encode(encodeWithKey(key));
}
public static BCECPublicKey decodeWithX509(String base64) throws InvalidKeySpecException {
return decodeWithX509(Base64.decode(base64.replaceAll("-----.*-----", "")));
}
public static BCECPrivateKey decodeWithPKCS8(String base64) throws InvalidKeySpecException {
return decodeWithPKCS8(Base64.decode(base64.replaceAll("-----.*-----", "")));
}
public static CipherParameters forParameters(Key key) throws InvalidKeyException {
if (key instanceof PublicKey) {
return ECUtil.generatePublicKeyParameter((PublicKey)key);
} else if (key instanceof PrivateKey) {
return ECUtil.generatePrivateKeyParameter((PrivateKey)key);
} else {
throw new InvalidKeyException("not expect key type");
}
}
static {
try {
ecParameters = GMNamedCurves.getByName("sm2p256v1");
sm2Spec = new ECNamedCurveSpec(GMObjectIdentifiers.sm2p256v1.toString(), ecParameters.getCurve(), ecParameters.getG(), ecParameters.getN());
bcProvider = new BouncyCastleProvider();
keyGenerator = KeyPairGenerator.getInstance("EC", bcProvider);
keyGenerator.initialize(sm2Spec, new SecureRandom());
keyFactory = KeyFactory.getInstance("EC", bcProvider);
curve = ecParameters.getCurve();
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException var1) {
throw new RuntimeException("无法提供SM2算法", var1);
}
}
public static void main(String[] args) {
KeyPair smKeyPair = SM2KeyUtil.genKeyPair();
String publicKey = Base64.encode(smKeyPair.getPublic().getEncoded());
String privateKey = Base64.encode(smKeyPair.getPrivate().getEncoded());
System.out.println("public key:"+ publicKey);
System.out.println("private key:"+ privateKey);
}
}
package cn.anicert.secure.access.crypto.util;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.EncryptedContentInfo;
import org.bouncycastle.asn1.cms.EnvelopedData;
import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
import org.bouncycastle.asn1.cms.RecipientInfo;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.pkcs.ContentInfo;
import org.bouncycastle.asn1.pkcs.IssuerAndSerialNumber;
import org.bouncycastle.asn1.pkcs.SignedData;
import org.bouncycastle.asn1.pkcs.SignerInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.engines.SM2Engine.Mode;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECFieldFp;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Enumeration;
/**
* @company cn.anicert
* @author: wangpengyuan
* @create: 2024-03-07
* @description SM2工具类
**/
public class SM2Utils extends BaseCryptoUtil {
//////////////////////////////////////////////////////////////////////////////////////
/*
* 以下为SM2推荐曲线参数
*/
public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
public final static BigInteger SM2_ECC_P = CURVE.getQ();
public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
public final static BigInteger SM2_ECC_N = CURVE.getOrder();
public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
public final static BigInteger SM2_ECC_GX = new BigInteger(
"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
public final static BigInteger SM2_ECC_GY = new BigInteger(
"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
SM2_ECC_N, SM2_ECC_H);
public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());
private static final String ALGO_NAME_EC = "EC";
public static Base64.Decoder decoder = Base64.getDecoder();
public static Base64.Encoder encoder = Base64.getEncoder();
private static final int C1_LEN = 65;
private static final int C3_LEN = 32;
public static ECPrivateKeyParameters getPriKey(String priKeyStr) {
return getPriKey(Base64.getDecoder().decode(priKeyStr));
}
public static ECPrivateKeyParameters getPriKey(byte[] priKeyByte) {
return new ECPrivateKeyParameters(new BigInteger(1, priKeyByte), DOMAIN_PARAMS);
}
public static String normalSign(String privateKey, String data) throws InvalidKeySpecException, InvalidKeyException, CryptoException {
BCECPrivateKey parameters = SM2KeyUtil.decodeWithPKCS8(privateKey);
CipherParameters param = ECUtil.generatePrivateKeyParameter(parameters);
SM2Signer signer = new SM2Signer();
signer.init(true, param);
signer.update(data.getBytes(StandardCharsets.UTF_8), 0, data.length());
return Base64.getEncoder().encodeToString(signer.generateSignature());
}
public static boolean normalVerifySign(String publicKey, String sign, String data) throws InvalidKeySpecException, InvalidKeyException, CryptoException {
BCECPublicKey bcecPublicKey = SM2KeyUtil.decodeWithX509(publicKey);
CipherParameters publicKeyParameter = ECUtil.generatePublicKeyParameter(bcecPublicKey);
SM2Signer signer = new SM2Signer();
signer.init(false, publicKeyParameter);
signer.update(data.getBytes(StandardCharsets.UTF_8), 0, data.length());
return signer.verifySignature(Base64.getDecoder().decode(sign));
}
public static String sign(String privateKey, String data) throws Exception {
return Base64.getEncoder().encodeToString(sign(getPriKey(privateKey), data.getBytes(StandardCharsets.UTF_8)));
}
/**
* @param priKeyParameters 私钥
* @param srcData 带签名原文
* @description: [priKeyParameters, srcData]
* @return: byte[] DER编码后的签名值
*/
public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] srcData)
throws CryptoException {
SM2Signer signer = new SM2Signer();
ParametersWithRandom pwr = new ParametersWithRandom(priKeyParameters, new SecureRandom());
signer.init(true, pwr);
signer.update(srcData, 0, srcData.length);
return signer.generateSignature();
}
public static String pkcs7SignDetach(String prikey, String cert, String oriData) throws Exception {
Certificate signCert = CertUtils.parseSM2Certificate(cert);
String sign = sign(prikey, oriData);
byte[] signByte = Base64.getDecoder().decode(sign);
//开始拼接P7
int version = 1;
int signerversion = 1;
String hashAlg = "SM3";
ASN1ObjectIdentifier hashOid = new ASN1ObjectIdentifier("1.2.156.10197.1").branch("401");
ASN1ObjectIdentifier encryptOid = new ASN1ObjectIdentifier("1.2.156.10197.1").branch("301.1");
ASN1ObjectIdentifier dataOid = new ASN1ObjectIdentifier("1.2.156.10197.6.1.4.2").branch("1");
ASN1ObjectIdentifier signDataOid = new ASN1ObjectIdentifier("1.2.156.10197.6.1.4.2").branch("2");
// Get all the digest
ASN1EncodableVector digestV = new ASN1EncodableVector();
AlgorithmIdentifier a = new AlgorithmIdentifier(hashOid);
digestV.add(a);
ASN1Set digestSet = new DERSet(digestV);
// Get all the certificates
ASN1EncodableVector certV = new ASN1EncodableVector();
certV.add(signCert);
ASN1Set certSet = new DERSet(certV);
ASN1EncodableVector contentV = new ASN1EncodableVector();
contentV.add(dataOid);
DERSequence contentSeq = new DERSequence(contentV);
// Create signerinfo structure.
ASN1EncodableVector signInfoV = new ASN1EncodableVector();
// Add the signerInfo version
signInfoV.add(new ASN1Integer(signerversion));
// Add Issuer SerialNumber
IssuerAndSerialNumber isAnds = new IssuerAndSerialNumber(
signCert.getIssuer(), signCert.getSerialNumber().getPositiveValue());
signInfoV.add(isAnds);
// Add the digestAlgorithm
signInfoV.add(new AlgorithmIdentifier(
hashOid));
// Add the digestEncryptionAlgorithm
signInfoV.add(new AlgorithmIdentifier(encryptOid));
// Add the signValue
signInfoV.add(new DEROctetString(signByte));
DERSequence signInfoSeq = new DERSequence(signInfoV);
ASN1Set signInfoSet = new DERSet(signInfoSeq);
ASN1EncodableVector body = new ASN1EncodableVector();
body.add(new ASN1Integer(version));
body.add(digestSet);
body.add(contentSeq);
body.add(new DERTaggedObject(false, 0, certSet));
body.add(signInfoSet);
DERSequence bodySeq = new DERSequence(body);
ASN1EncodableVector whole = new ASN1EncodableVector();
whole.add(signDataOid);
whole.add(new DERTaggedObject(0, bodySeq));
DERSequence wholeSeq = new DERSequence(whole);
return Base64.getEncoder().encodeToString(wholeSeq.getEncoded());
}
public static boolean pkcs7Verify(byte[] signValue, byte[] inputData) throws Exception {
SignedData data = SignedData.getInstance(ContentInfo.getInstance(ASN1Sequence.getInstance(signValue)).getContent());
Enumeration certEnum = data.getCertificates().getObjects();
ASN1Sequence certSeq = (ASN1Sequence) certEnum.nextElement();
Certificate signCert = Certificate.getInstance(certSeq);
String publicKey = CertUtils.getCertInfo(signCert, 30);
Enumeration signerInfoEnum = data.getSignerInfos().getObjects();
ASN1Sequence signerInfoSeq = (ASN1Sequence) signerInfoEnum.nextElement();
SignerInfo signerInfo = SignerInfo.getInstance(signerInfoSeq);
byte[] pubKey = Base64.getDecoder().decode(publicKey);
byte[] sign = signerInfo.getEncryptedDigest().getOctets();
byte[] ori = null;
ASN1Encodable content = data.getContentInfo().getContent();
if (content == null) {//detach,使用传入的原文
if (inputData == null) {
throw new RuntimeException("找不到原文数据");
}
ori = inputData;
} else {//atach,传入的原文参数失效,使用P7中解析的原文
DEROctetString contentOct = (DEROctetString) content;
ori = contentOct.getOctets();
}
return verify(pubKey, inputData, sign);
}
public static boolean verify(String publicKey, String data, String charset, String signature) throws Exception {
return verify(Base64.getDecoder().decode(publicKey), data.getBytes(charset), Base64.getDecoder().decode(signature));
}
public static boolean verify(byte[] publicKey, byte[] data, byte[] signature) throws Exception {
return verify(getPubKey(publicKey), data, signature);
}
/**
* 验签
*
* @param pubKeyParameters 公钥
* @param srcData 原文
* @param sign DER编码的签名值
* @return 验签成功返回true,失败返回false
*/
public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] srcData, byte[] sign) {
SM2Signer signer = new SM2Signer();
signer.init(false, pubKeyParameters);
signer.update(srcData, 0, srcData.length);
return signer.verifySignature(sign);
}
public static ECPublicKeyParameters getPubKey(byte[] pubKeyByte) {
return new ECPublicKeyParameters(CURVE.decodePoint(pubKeyByte), DOMAIN_PARAMS);
}
public static byte[] encodeEnvelop(String userCert, byte[] dataInput) throws Exception {
//生成对称密钥
byte[] key = SM4Utils.generateKey();
byte[] encData = SM4Utils.encrypt_ECB_Padding(key, dataInput);
Certificate certOb = CertUtils.parseSM2Certificate(userCert);
String pubKey = CertUtils.getCertInfo(certOb, 30);
byte[] encKey = encryptReByte(pubKey, key);
//将C1C3C2模式裸数据改成ASN1格式
ASN1EncodableVector encDataVec = changeC1C3C2ToAsn1(encKey);
ASN1Integer version = new ASN1Integer(1);
ASN1Integer receptionVer = new ASN1Integer(1);
IssuerAndSerialNumber isAndSN = new IssuerAndSerialNumber(certOb.getIssuer(), certOb.getSerialNumber().getPositiveValue());
AlgorithmIdentifier derAlgEncKey = new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.156.10197.1").branch("301.3"));
DEROctetString derEncKey = new DEROctetString(new DERSequence(encDataVec));
ASN1EncodableVector recipientVector = new ASN1EncodableVector();
recipientVector.add(receptionVer);
recipientVector.add(isAndSN);
recipientVector.add(derAlgEncKey);
recipientVector.add(derEncKey);
ASN1Sequence recipientInfo = new DERSequence(recipientVector);
AlgorithmIdentifier derAlg = new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.156.10197.1").branch("104"));
DERSet recipientInfos = new DERSet(recipientInfo);
ASN1ObjectIdentifier contentType = new ASN1ObjectIdentifier("1.2.156.10197.6.1.4.2").branch("1");
ASN1OctetString encContent = new DEROctetString(encData);
BERTaggedObject berTag = new BERTaggedObject(false, 0, encContent);
ASN1EncodableVector contVector = new ASN1EncodableVector();
contVector.add(contentType);
contVector.add(derAlg);
contVector.add(berTag);
DERSequence cont = new DERSequence(contVector);
ASN1EncodableVector envVector = new ASN1EncodableVector();
envVector.add(version);
envVector.add(recipientInfos);
envVector.add(cont);
DERSequence env = new DERSequence(envVector);
ASN1EncodableVector p7Vector = new ASN1EncodableVector();
p7Vector.add(new ASN1ObjectIdentifier("1.2.156.10197.6.1.4.2").branch("3"));
DERTaggedObject dtCont = new DERTaggedObject(true, 0, env);
p7Vector.add(dtCont);
DERSequence p7 = new DERSequence(p7Vector);
return p7.getEncoded();
}
/**
* sm2加密
*
* @param publicKey
* @param data
* @return
* @throws Exception
*/
public static String encrypt(String publicKey, String data) throws Exception {
return Base64.getEncoder().encodeToString(encryptReByte(Base64.getDecoder().decode(publicKey), data.getBytes(StandardCharsets.UTF_8)));
}
public static byte[] encryptReByte(String publicKey, byte[] data) throws Exception {
return encryptReByte(Base64.getDecoder().decode(publicKey), data);
}
/**
* @param publicKey 公钥
* @param data 加密源数据
* @description: [publicKey, data]
* @return: byte[]
*/
public static byte[] encryptReByte(byte[] publicKey, byte[] data) throws Exception {
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(CURVE.decodePoint(publicKey), DOMAIN_PARAMS);
ParametersWithRandom pwr = new ParametersWithRandom(publicKeyParameters, new SecureRandom());
SM2Engine engine = new SM2Engine(Mode.C1C3C2);
engine.init(true, pwr);
return engine.processBlock(data, 0, data.length);
}
/**
* sm2解密
*
* @param privateKey
* @param encryptedData
* @return
* @throws Exception
*/
public static String decrypt(String privateKey, String encryptedData) throws Exception {
return new String(decryptReByte(Base64.getDecoder().decode(privateKey), Base64.getDecoder().decode(encryptedData)), StandardCharsets.UTF_8);
}
/**
* 数字信封解密
*
* @param priKey
* @param envelop
* @return
* @throws Exception
*/
public static byte[] decodeEnvelop(String priKey, byte[] envelop) throws Exception {
ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(envelop));
ContentInfo info = ContentInfo.getInstance(aIn.readObject());
EnvelopedData envData = EnvelopedData.getInstance(info.getContent());
ASN1Set recipientInfoSet = envData.getRecipientInfos();
Enumeration recipientInfoEnum = recipientInfoSet.getObjects();
RecipientInfo recipientInfo = RecipientInfo.getInstance(recipientInfoEnum.nextElement());
KeyTransRecipientInfo keyTransRecipientInfo = (KeyTransRecipientInfo) recipientInfo.getInfo();
//非对称加密算法解密对称密钥
byte[] kek = keyTransRecipientInfo.getEncryptedKey().getOctets();
//解C1C3C2结构
byte[] key = decryptReByte(priKey, changeAsn1ToC1C3C2(kek));
//对称密钥解密数据
EncryptedContentInfo encryptedContentInfo = envData.getEncryptedContentInfo();
byte[] encContent = encryptedContentInfo.getEncryptedContent().getOctets();
return SM4Utils.decrypt_ECB_Padding(key, encContent);
}
public static byte[] decryptReByte(String privateKey, byte[] encryptedData) throws Exception {
return decryptReByte(Base64.getDecoder().decode(privateKey), encryptedData);
}
/**
* @param privateKey 私钥
* @param encryptedData 待解密的密文
* @description: [privateKey, encryptedData]
* @return: byte[]
*/
public static byte[] decryptReByte(byte[] privateKey, byte[] encryptedData) throws Exception {
ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(new BigInteger(1, privateKey), DOMAIN_PARAMS);
SM2Engine engine = new SM2Engine(Mode.C1C3C2);
engine.init(false, privateKeyParameters);
return engine.processBlock(encryptedData, 0, encryptedData.length);
}
/**
* 将ASN1格式转成c1c3c2
*/
public static byte[] changeAsn1ToC1C3C2(byte[] asn1) throws IOException {
ASN1InputStream aIn = new ASN1InputStream(asn1);
ASN1Sequence seq = (ASN1Sequence) aIn.readObject();
BigInteger x = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
BigInteger y = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
byte[] c3 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();
byte[] c2 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();
ECPoint c1Point = GMNamedCurves.getByName("sm2p256v1").getCurve().createPoint(x, y);
byte[] c1 = c1Point.getEncoded(false);
return concatenateByteArrays(c1, c3, c2);
}
public static byte[] concatenateByteArrays(byte[]... arrays) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for (byte[] array : arrays) {
outputStream.write(array);
}
return outputStream.toByteArray();
}
/**
* 将c1c3c2转成标准的ASN1格式
*/
public static ASN1EncodableVector changeC1C3C2ToAsn1(byte[] c1c3c2) throws IOException {
byte[] c1 = Arrays.copyOfRange(c1c3c2, 0, C1_LEN);
byte[] c3 = Arrays.copyOfRange(c1c3c2, C1_LEN, C1_LEN + C3_LEN);
byte[] c2 = Arrays.copyOfRange(c1c3c2, C1_LEN + C3_LEN, c1c3c2.length);
byte[] c1X = Arrays.copyOfRange(c1, 1, 33);
byte[] c1Y = Arrays.copyOfRange(c1, 33, 65);
BigInteger r = new BigInteger(1, c1X);
BigInteger s = new BigInteger(1, c1Y);
ASN1Integer x = new ASN1Integer(r);
ASN1Integer y = new ASN1Integer(s);
DEROctetString derDig = new DEROctetString(c3);
DEROctetString derEnc = new DEROctetString(c2);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(x);
v.add(y);
v.add(derDig);
v.add(derEnc);
return v;
}
// public static void main(String[] args) throws InvalidKeySpecException, InvalidKeyException, CryptoException {
// String privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgaXYLE/mEwONu3ohCCMd2uOjUdeRnp0qy/yMcpzC1R3KgCgYIKoEcz1UBgi2hRANCAATWabavR0v2s/iud6oQzpkd4IFx2Caa7kQVZ95pIP++h567ozYXGDq6c0qjmZsd0SD93gVx5oEgsvSo6whQ4/DC";
// String content = "talk is cheap,show me the code";
// String publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1mm2r0dL9rP4rneqEM6ZHeCBcdgmmu5EFWfeaSD/voeeu6M2Fxg6unNKo5mbHdEg/d4FceaBILL0qOsIUOPwwg==";
// String signData = SM2Utils.normalSign(privateKey, content);
// System.out.println("sign:" + signData);
// Boolean bool = SM2Utils.normalVerifySign(publicKey, signData, content);
// System.out.println("verify sign:" + bool);
// }
}
package cn.anicert.secure.access.crypto.util;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Hex;
import java.util.Arrays;
public class SM3Utils extends BaseCryptoUtil {
/**
* 计算SM3摘要值
*
* @param srcData 原文
* @return 摘要值,对于SM3算法来说是32字节
*/
public static String sm3Hash(byte[] srcData) {
SM3Digest digest = new SM3Digest();
digest.update(srcData, 0, srcData.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
String result = Hex.toHexString(hash);
return result.toUpperCase();
}
/**
* 计算两项身份信息hash
*
* @param idno 身份证号
* @param name 姓名
* @return 摘要值,对于SM3算法来说是32字节
*/
public static String sm3Hash(String idno, String name) {
String str = idno + name;
byte[] srcData = str.getBytes();
return sm3Hash(srcData);
}
}
package cn.anicert.secure.access.crypto.util;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
import org.bouncycastle.crypto.macs.GMac;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.paddings.BlockCipherPadding;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
public class SM4Utils extends BaseCryptoUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static final String ALGORITHM_NAME = "SM4";
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
public static final String ALGORITHM_NAME_ECB_NOPADDING = "SM4/ECB/NoPadding";
public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding";
public static final String ALGORITHM_NAME_CBC_NOPADDING = "SM4/CBC/NoPadding";
/**
* SM4算法目前只支持128位(即密钥16字节)
*/
public static final int DEFAULT_KEY_SIZE = 16;
public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {
return generateKey(DEFAULT_KEY_SIZE);
}
public static byte[] generateKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException {
// KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
// kg.init(keySize, new SecureRandom());
SecureRandom secureRandom = new SecureRandom();
byte[] random = new byte[keySize];
secureRandom.nextBytes(random);
return random;
// return kg.generateKey().getEncoded();
}
public static byte[] encrypt_ECB_Padding(byte[] key, byte[] data)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
public static byte[] decrypt_ECB_Padding(byte[] key, byte[] cipherText)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
public static byte[] encrypt_ECB_NoPadding(byte[] key, byte[] data)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
public static byte[] decrypt_ECB_NoPadding(byte[] key, byte[] cipherText)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
public static byte[] encrypt_CBC_Padding(byte[] key, byte[] iv, byte[] data)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException {
Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.ENCRYPT_MODE, key, iv);
return cipher.doFinal(data);
}
public static byte[] decrypt_CBC_Padding(byte[] key, byte[] iv, byte[] cipherText)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
InvalidAlgorithmParameterException {
Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key, iv);
return cipher.doFinal(cipherText);
}
public static byte[] encrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] data)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException {
Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.ENCRYPT_MODE, key, iv);
return cipher.doFinal(data);
}
public static byte[] decrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] cipherText)
throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
InvalidAlgorithmParameterException {
Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.DECRYPT_MODE, key, iv);
return cipher.doFinal(cipherText);
}
public static byte[] doCMac(byte[] key, byte[] data) throws NoSuchProviderException, NoSuchAlgorithmException,
InvalidKeyException {
Key keyObj = new SecretKeySpec(key, ALGORITHM_NAME);
return doMac("SM4-CMAC", keyObj, data);
}
public static byte[] doGMac(byte[] key, byte[] iv, int tagLength, byte[] data) {
org.bouncycastle.crypto.Mac mac = new GMac(new GCMBlockCipher(new SM4Engine()), tagLength * 8);
return doMac(mac, key, iv, data);
}
/**
* 默认使用PKCS7Padding/PKCS5Padding填充的CBCMAC
*
* @param key
* @param iv
* @param data
* @return
*/
public static byte[] doCBCMac(byte[] key, byte[] iv, byte[] data) {
SM4Engine engine = new SM4Engine();
org.bouncycastle.crypto.Mac mac = new CBCBlockCipherMac(engine, engine.getBlockSize() * 8, new PKCS7Padding());
return doMac(mac, key, iv, data);
}
/**
* @param key
* @param iv
* @param padding 可以传null,传null表示NoPadding,由调用方保证数据必须是BlockSize的整数倍
* @param data
* @return
* @throws Exception
*/
public static byte[] doCBCMac(byte[] key, byte[] iv, BlockCipherPadding padding, byte[] data) throws Exception {
SM4Engine engine = new SM4Engine();
if (padding == null) {
if (data.length % engine.getBlockSize() != 0) {
throw new Exception("if no padding, data length must be multiple of SM4 BlockSize");
}
}
org.bouncycastle.crypto.Mac mac = new CBCBlockCipherMac(engine, engine.getBlockSize() * 8, padding);
return doMac(mac, key, iv, data);
}
private static byte[] doMac(org.bouncycastle.crypto.Mac mac, byte[] key, byte[] iv, byte[] data) {
CipherParameters cipherParameters = new KeyParameter(key);
mac.init(new ParametersWithIV(cipherParameters, iv));
mac.update(data, 0, data.length);
byte[] result = new byte[mac.getMacSize()];
mac.doFinal(result, 0);
return result;
}
private static byte[] doMac(String algorithmName, Key key, byte[] data) throws NoSuchProviderException,
NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
mac.init(key);
mac.update(data);
return mac.doFinal();
}
private static Cipher generateECBCipher(String algorithmName, int mode, byte[] key)
throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
InvalidKeyException {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
private static Cipher generateCBCCipher(String algorithmName, int mode, byte[] key, byte[] iv)
throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException, NoSuchPaddingException {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(mode, sm4Key, ivParameterSpec);
return cipher;
}
}
package cn.anicert.secure.access.enums;
public enum Configs {
customerNo("customerNo", "业务站点号", true),
accessUrl("accessUrl", "安全接入平台地址", true),
signPrivateKey("signPrivateKey", "签名私钥", true),
signVerifyPublicKey("signVerifyPublicKey", "验签公钥", true),
dataEncryptKey("dataEncryptKey", "数据加密秘钥", false);
private String key;
private String name;
private boolean necessary;
Configs(String key, String name) {
this(key, name, false);
}
Configs(String key, String name, boolean necessary) {
this.key = key;
this.name = name;
this.necessary = necessary;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isNecessary() {
return necessary;
}
public void setNecessary(boolean necessary) {
this.necessary = necessary;
}
}
package cn.anicert.secure.access.exceptions;
public class SecureAccessConfigException extends RuntimeException {
public SecureAccessConfigException() {
super();
}
public SecureAccessConfigException(String message) {
super(message);
}
}
package cn.anicert.secure.access.utils;
import cn.anicert.secure.access.config.ConfigReader;
import cn.anicert.secure.access.config.bean.AccessConfig;
import cn.anicert.secure.access.enums.Configs;
public class ConfigUtil {
private static AccessConfig accessConfig;
public static synchronized void initConfig(String... filePath) {
accessConfig = ConfigReader.readConfig(filePath);
}
public static synchronized void initConfig(AccessConfig config) {
accessConfig = config;
}
public static AccessConfig getConfig() {
return accessConfig;
}
public static void updateConfigDataEncryptKey(String newKey) {
accessConfig=ConfigReader.writeConfig(Configs.dataEncryptKey.getKey(), newKey);
}
}
package cn.anicert.secure.access.utils;
import cn.hutool.log.StaticLog;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.util.Timeout;
import javax.net.ssl.SSLContext;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
public class HttpUtil {
private final PoolingHttpClientConnectionManager connPoolMng;
private final RequestConfig requestConfig;
private final SSLContext sslContext;
private volatile static HttpUtil httpUtilInstance;
/**
* 私有构造方法
* 单例中连接池初始化一次
*/
private HttpUtil() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
//初始化http连接池
sslContext = SSLContextBuilder.create()
.loadTrustMaterial(new TrustAllStrategy())
.build();
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https",new SSLConnectionSocketFactory(sslContext))
.build();
connPoolMng = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
connPoolMng.setMaxTotal(ConfigUtil.getConfig().getPoolMaxConn());
connPoolMng.setDefaultMaxPerRoute(ConfigUtil.getConfig().getPoolMaxConn());
//初始化请求超时控制参数
requestConfig = RequestConfig.custom()
//从线程池中获取线程超时时间
.setConnectionRequestTimeout(Timeout.ofMilliseconds(ConfigUtil.getConfig().getConnectionRequestTimeout()))
//设置数据读取超时时间
.setResponseTimeout(Timeout.ofMilliseconds(ConfigUtil.getConfig().getResponseTimeout()))
.build();
}
/**
* 单例模式
* 使用双检锁机制,线程安全且在多线程情况下能保持高性能
*
* @return HttpUtil
*/
public static HttpUtil getInstance() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
if (httpUtilInstance == null) {
synchronized (HttpUtil.class) {
if (httpUtilInstance == null) {
httpUtilInstance = new HttpUtil();
}
}
}
return httpUtilInstance;
}
/**
* 获取client客户端
*
* @return
*/
public CloseableHttpClient getClient() {
return HttpClients.custom()
.setConnectionManager(connPoolMng)
.setDefaultRequestConfig(requestConfig)
.build();
}
/**
* post请求
*
* @param postUrl
* @param jsonStr
* @return String
* @throws Exception
*/
public String postRequest(String postUrl, String jsonStr) throws Exception {
String result;
HttpPost post = new HttpPost(postUrl);
StringEntity reqEntity = new StringEntity(jsonStr, ContentType.APPLICATION_JSON);
post.setEntity(reqEntity);
result = getClient().execute(post, response -> {
StaticLog.debug("[安全接入平台]--请求地址--{}--响应状态--{}", postUrl, response.getCode());
return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
});
return result;
}
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package cn.anicert.secure.access.utils;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
public class JSONUtil {
private static final ObjectMapper mapper = new ObjectMapper();
public static final JavaType[] EMPTY_TYPES = new JavaType[0];
public static final String YYYYMMDDHHMMSS = "yyyy-MM-dd HH:mm:ss";
public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public JSONUtil() {
}
public static <T> T json2Object(String jsonStr, Class<T> clazz) {
try {
return mapper.readValue(jsonStr, clazz);
} catch (Exception var3) {
throw new RuntimeException(var3);
}
}
public static String object2Json(Object obj) {
try {
return mapper.writeValueAsString(obj);
} catch (Exception var2) {
throw new RuntimeException(var2);
}
}
public static String toJson(Object obj) {
try {
return mapper.writeValueAsString(obj);
} catch (Exception var2) {
throw new RuntimeException(var2);
}
}
public static String toPrettyJason(Object obj) {
try {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (Exception var2) {
throw new RuntimeException(var2);
}
}
public static String object2DateFormatJson(Object obj) {
return object2DateFormatJson(obj, "yyyy-MM-dd HH:mm:ss");
}
public static String object2DateFormatJson(Object obj, String dateFmt) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(Feature.ALLOW_COMMENTS, false);
mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
mapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);
mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
mapper.setDateFormat(new SimpleDateFormat(dateFmt));
mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
mapper.registerModule(new JavaTimeModule());
return mapper.writeValueAsString(obj);
} catch (Exception var3) {
throw new RuntimeException(var3);
}
}
public static <T> List<T> json2ObjectList(String jsonString, Class<T> clazz) {
try {
if (jsonString == null) {
return null;
} else {
List<T> list = (List)mapper.readValue(jsonString, new TypeReference<List<T>>() {
});
return list;
}
} catch (Exception var3) {
throw new RuntimeException(var3);
}
}
public static List<String> json2StringList(String jsonString) {
try {
if (!"".equals(jsonString) && null != jsonString) {
JsonNode jsonNode = mapper.readTree(jsonString);
List<String> list = new ArrayList();
jsonNode.forEach((node) -> {
list.add(node.toString());
});
return list;
} else {
return null;
}
} catch (Exception var3) {
throw new RuntimeException(var3);
}
}
public static Map<String, Object> json2Map(String jsonString) {
try {
return (Map)mapper.readValue(jsonString, new TypeReference<Map<String, Object>>() {
});
} catch (Exception var2) {
throw new RuntimeException(var2);
}
}
public static List<Map<String, Object>> json2MapList(String jsonString) {
try {
return (List)mapper.readValue(jsonString, new TypeReference<List<Map<String, Object>>>() {
});
} catch (Exception var2) {
throw new RuntimeException(var2);
}
}
public static Object[] json2Array(String jsonString) {
try {
List<Object> objs = (List)mapper.readValue(jsonString, new TypeReference<List>() {
});
return objs.toArray();
} catch (Exception var2) {
throw new RuntimeException(var2);
}
}
public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
}
public static JsonNode json2JSONArray(String jsonString) {
try {
JsonNode jsonNode = mapper.readTree(jsonString);
JsonNodeType nodeType = jsonNode.getNodeType();
if (nodeType.equals(JsonNodeType.ARRAY)) {
return jsonNode;
} else {
throw new IllegalArgumentException("json字符串不是数组");
}
} catch (Exception var3) {
throw new RuntimeException(var3);
}
}
public static String array2Json(String[] strs) {
try {
ArrayNode arrayNode = new ArrayNode(new JsonNodeFactory(false));
String[] var2 = strs;
int var3 = strs.length;
for(int var4 = 0; var4 < var3; ++var4) {
String str = var2[var4];
JsonNode jsonNode = mapper.readTree(str);
arrayNode.add(jsonNode);
}
return arrayNode.toString();
} catch (Exception var7) {
throw new RuntimeException(var7);
}
}
public static <T> T[] jsonArrayToObjectArray(String jsonStr, Class<T[]> t) {
try {
T[] ts = (T[]) mapper.readValue(jsonStr, t);
return ts;
} catch (Exception var3) {
throw new RuntimeException(var3);
}
}
static {
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(Feature.ALLOW_COMMENTS, false);
mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
mapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);
mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
mapper.setDateFormat(sdf);
mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
mapper.registerModule(new JavaTimeModule());
}
}
package cn.anicert.secure.access.utils;
import cn.anicert.module.sign.bc.ICryptoService;
import cn.anicert.module.sign.bc.exception.BadKeyException;
import cn.anicert.module.sign.bc.exception.BadSignatureException;
import cn.anicert.module.sign.bc.mix.MixCryptoFactory;
import cn.anicert.module.sign.bc.std.Asymmetric;
import cn.anicert.module.sign.bc.std.Symmetric;
import org.apache.commons.codec.binary.Base64;
import java.nio.charset.StandardCharsets;
public class SignUtil {
private final static ICryptoService cryptoService = MixCryptoFactory.getInstance(Asymmetric.SM2, Symmetric.SM4);
public static String sign(String signBody) throws BadSignatureException, BadKeyException {
return sign(signBody, ConfigUtil.getConfig().getSignPrivateKey());
}
public static String sign(String signBody, String signPriKey) throws BadSignatureException, BadKeyException {
return Base64.encodeBase64String(cryptoService.sign(signPriKey, signBody.getBytes(StandardCharsets.UTF_8)));
}
public static boolean verifySign(String body, String sign) throws BadSignatureException, BadKeyException {
return verifySign(body, sign, ConfigUtil.getConfig().getSignVerifyPublicKey());
}
public static boolean verifySign(String body, String sign, String key) throws BadSignatureException, BadKeyException {
byte[] byteSign = Base64.decodeBase64(sign);
byte[] byteBody = body.getBytes(StandardCharsets.UTF_8);
return cryptoService.verifySign(key, byteSign, byteBody);
}
}
package com.liquidnet.common.third.secureaccess.biz;
import cn.anicert.secure.access.bean.SARequestPackageVO;
import cn.anicert.secure.access.bean.SARequestVO;
import cn.anicert.secure.access.core.SecureAccess;
import cn.anicert.secure.access.crypto.service.ISoftCryptoService;
import cn.anicert.secure.access.utils.ConfigUtil;
import cn.anicert.secure.access.utils.JSONUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.liquidnet.common.third.secureaccess.config.SecureAccessProperties;
import com.liquidnet.common.third.secureaccess.model.NiaAuthRequest;
import com.liquidnet.common.third.secureaccess.model.NiaAuthResult;
import com.liquidnet.common.third.secureaccess.model.NiaPidParsed;
import com.liquidnet.common.third.secureaccess.util.NiaPidUtils;
import com.liquidnet.commons.lang.util.JsonUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import static com.liquidnet.common.third.secureaccess.util.NiaPidUtils.maskPid;
import java.time.LocalDateTime;
@Slf4j
@Component
@RequiredArgsConstructor
public class NiaAuthBiz {
private static final String SUCCESS_CODE = "C0000000";
private static final int RAW_LOG_MAX_LEN = 2000;
private final SecureAccessProperties properties;
private final ISoftCryptoService cryptoService;
public boolean isReady() {
return StringUtils.isNoneBlank(
properties.getCustomerNo(),
properties.getAccessUrl(),
properties.getSignPrivateKey(),
properties.getSignVerifyPublicKey())
&& ConfigUtil.getConfig() != null;
}
/**
* 调用安全接入平台转发网络身份认证 auth/request。
*/
public NiaAuthResult authenticate(NiaAuthRequest request) {
if (!isReady()) {
return fail(request.getBizSeq(), "CONFIG", "网证认证配置未完成", null);
}
if (StringUtils.isAnyBlank(request.getBizSeq(), request.getIdCardAuthData())) {
return fail(request.getBizSeq(), "PARAM", "bizSeq 或 idCardAuthData 不能为空", null);
}
try {
ObjectNode bizPackage = JsonUtils.OM().createObjectNode();
bizPackage.put("appName", properties.getAppName());
bizPackage.put("timestamp", LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_MS_PATTERN));
bizPackage.put("bizSeq", request.getBizSeq());
bizPackage.put("mode", properties.getMode());
bizPackage.put("customerNo", properties.getCustomerNo());
bizPackage.put("idCardAuthData", request.getIdCardAuthData());
SARequestPackageVO requestPackage = new SARequestPackageVO();
requestPackage.setCustomerNo(properties.getCustomerNo());
requestPackage.setForwardUrl(properties.getForwardUrl());
requestPackage.setBizPackage(bizPackage);
log.info("[nia-auth] 请求SAP bizSeq={}, mode={}, customerNo={}, appName={}, forwardUrl={}, idCardAuthDataLen={}",
request.getBizSeq(),
properties.getMode(),
properties.getCustomerNo(),
properties.getAppName(),
properties.getForwardUrl(),
request.getIdCardAuthData().length());
String packageJson = JSONUtil.toJson(requestPackage);
SARequestVO saRequest = new SARequestVO();
saRequest.setRequestPackage(requestPackage);
saRequest.setRequestSign(cryptoService.sm2Sign(properties.getSignPrivateKey(), packageJson));
String responseJson = SecureAccess.unified_auth(request.getBizSeq(), JSONUtil.toJson(saRequest));
if (StringUtils.isBlank(responseJson)) {
log.warn("[nia-auth] SAP响应为空或验签失败 bizSeq={}, accessUrl={}",
request.getBizSeq(), properties.getAccessUrl());
return fail(request.getBizSeq(), "SAP", "安全接入平台返回为空或验签失败", null);
}
JsonNode sapRoot = JsonUtils.OM().readTree(responseJson);
log.info("[nia-auth] SAP响应 bizSeq={}, platformCode={}, platformMsg={}",
request.getBizSeq(),
firstText(sapRoot, "platformCode"),
firstText(sapRoot, "platformMsg"));
log.debug("[nia-auth] SAP原始响应 bizSeq={}, body={}",
request.getBizSeq(), abbreviate(responseJson, RAW_LOG_MAX_LEN));
return parseResponse(request.getBizSeq(), responseJson);
} catch (Exception e) {
log.error("[nia-auth] bizSeq={} 认证异常", request.getBizSeq(), e);
return fail(request.getBizSeq(), "ERROR", e.getMessage(), null);
}
}
private NiaAuthResult parseResponse(String bizSeq, String responseJson) {
try {
JsonNode root = JsonUtils.OM().readTree(responseJson);
String platformCode = firstText(root, "platformCode");
if (StringUtils.isNotBlank(platformCode) && !"200".equals(platformCode)) {
String platformMsg = firstText(root, "platformMsg");
log.warn("[nia-auth] SAP平台失败 bizSeq={}, platformCode={}, platformMsg={}",
bizSeq, platformCode, platformMsg);
return fail(bizSeq, platformCode,
StringUtils.defaultIfBlank(platformMsg, "安全接入平台请求失败"), responseJson);
}
JsonNode bizPackageNode = resolveBizPackage(root);
if (bizPackageNode == null || bizPackageNode.isMissingNode()) {
log.warn("[nia-auth] 响应缺少bizPackage bizSeq={}, body={}", bizSeq, abbreviate(responseJson, RAW_LOG_MAX_LEN));
return fail(bizSeq, "PARSE", "认证响应缺少 bizPackage", responseJson);
}
String resultCode = firstText(bizPackageNode, "resultCode");
String resultDesc = firstText(bizPackageNode, "resultDesc");
String bizSerialNo = firstText(bizPackageNode, "bizSerialNo");
log.info("[nia-auth] 解析平台响应 bizSeq={}, resultCode={}, resultDesc={}, bizSerialNo={}",
bizSeq, resultCode, resultDesc, bizSerialNo);
if (StringUtils.isBlank(resultCode)) {
log.warn("[nia-auth] 响应缺少resultCode bizSeq={}, bizPackage={}",
bizSeq, abbreviate(bizPackageNode.toString(), RAW_LOG_MAX_LEN));
return fail(bizSeq, "UNKNOWN", "平台响应缺少 resultCode", responseJson);
}
if (!SUCCESS_CODE.equals(resultCode)) {
return NiaAuthResult.builder()
.success(false)
.resultCode(resultCode)
.resultDesc(resultDesc)
.bizSeq(bizSeq)
.bizSerialNo(bizSerialNo)
.stage("PLATFORM")
.rawResponse(abbreviate(responseJson, RAW_LOG_MAX_LEN))
.build();
}
JsonNode data = bizPackageNode.path("data");
if (data.isMissingNode()) {
data = root.path("data");
}
String pid = firstText(data, "PID", "pid");
if (StringUtils.isBlank(pid)) {
log.warn("[nia-auth] 成功响应缺少PID bizSeq={}, data={}", bizSeq, data);
return fail(bizSeq, "PLATFORM", "认证成功但响应缺少 PID", responseJson);
}
NiaPidParsed pidParsed = NiaPidUtils.parse(pid);
if (pidParsed == null) {
log.warn("[nia-auth] PID解码失败 bizSeq={}, pid={}", bizSeq, maskPid(pid));
return fail(bizSeq, "PLATFORM", "认证凭据 PID 解码失败", responseJson);
}
log.info("[nia-auth] PID结构 bizSeq={}, totalLen={}, version={}, identifierHex={}, identifierVisual=[{}], timestamp={}, signatureLen={}, signatureHex={}",
bizSeq,
pidParsed.getTotalLen(),
pidParsed.getVersion(),
pidParsed.identifierHex(),
pidParsed.identifierVisual(),
pidParsed.getTimestamp(),
pidParsed.getSignature() == null ? 0 : pidParsed.getSignature().length,
pidParsed.signatureHexPrefix(16));
String openId = NiaPidUtils.extractOpenId(pid);
if (!NiaPidUtils.isValidOpenId(openId)) {
log.warn("[nia-auth] PID标识解析失败 bizSeq={}", bizSeq);
return fail(bizSeq, "PLATFORM", "认证成功但 PID 标识解析失败", responseJson);
}
log.info("[nia-auth] 认证成功 bizSeq={}, bizSerialNo={}, openId={}",
bizSeq, bizSerialNo, openId);
return NiaAuthResult.builder()
.success(true)
.resultCode(resultCode)
.resultDesc(resultDesc)
.bizSeq(bizSeq)
.bizSerialNo(bizSerialNo)
.openId(openId)
.photoCompareScore(firstText(data, "photoCompareScore"))
.encryptedIdInfo(firstText(data, "encryptedIdInfo"))
.stage("SUCCESS")
.rawResponse(abbreviate(responseJson, RAW_LOG_MAX_LEN))
.build();
} catch (Exception e) {
log.error("[nia-auth] bizSeq={} 解析响应失败, body={}", bizSeq, abbreviate(responseJson, RAW_LOG_MAX_LEN), e);
return fail(bizSeq, "PARSE", "认证响应解析失败: " + e.getMessage(), responseJson);
}
}
/**
* 解析认证结果 bizPackage。
* SAP unified_auth 典型结构:{ platformData: { bizPackage: {...}, sign } }
*/
private static JsonNode resolveBizPackage(JsonNode root) {
if (root == null) {
return null;
}
JsonNode platformData = root.path("platformData");
if (!platformData.isMissingNode()) {
JsonNode platformBizPackage = platformData.path("bizPackage");
if (!platformBizPackage.isMissingNode()) {
return platformBizPackage;
}
}
JsonNode bizPackage = root.path("bizPackage");
if (!bizPackage.isMissingNode() && bizPackage.has("resultCode")) {
return bizPackage;
}
if (!bizPackage.isMissingNode() && bizPackage.has("bizPackage")) {
JsonNode nested = bizPackage.path("bizPackage");
if (!nested.isMissingNode()) {
return nested;
}
}
if (root.has("resultCode")) {
return root;
}
return null;
}
private static String firstText(JsonNode node, String... fields) {
if (node == null || node.isMissingNode()) {
return null;
}
for (String field : fields) {
String value = text(node, field);
if (StringUtils.isNotBlank(value)) {
return value;
}
}
return null;
}
private static String text(JsonNode node, String field) {
JsonNode value = node.path(field);
return value.isMissingNode() || value.isNull() ? null : value.asText();
}
private static String abbreviate(String value, int maxLen) {
if (value == null) {
return null;
}
return value.length() <= maxLen ? value : value.substring(0, maxLen) + "...(truncated)";
}
private static NiaAuthResult fail(String bizSeq, String code, String desc, String rawResponse) {
return NiaAuthResult.builder()
.success(false)
.resultCode(code)
.resultDesc(desc)
.bizSeq(bizSeq)
.stage(code)
.rawResponse(abbreviate(rawResponse, RAW_LOG_MAX_LEN))
.build();
}
}
package com.liquidnet.common.third.secureaccess.config;
import cn.anicert.secure.access.config.bean.AccessConfig;
import cn.anicert.secure.access.crypto.service.ISoftCryptoService;
import cn.anicert.secure.access.crypto.service.impl.SoftCryptoServiceImpl;
import cn.anicert.secure.access.utils.ConfigUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Slf4j
@Configuration
public class SecureAccessConfiguration {
private final SecureAccessProperties properties;
public SecureAccessConfiguration(SecureAccessProperties properties) {
this.properties = properties;
}
@Bean
public ISoftCryptoService softCryptoService() {
return new SoftCryptoServiceImpl();
}
@PostConstruct
public void initSecureAccessSdk() {
if (StringUtils.isAnyBlank(
properties.getCustomerNo(),
properties.getAccessUrl(),
properties.getSignPrivateKey(),
properties.getSignVerifyPublicKey())) {
log.warn("[secure-access] 配置不完整,网证认证功能不可用");
return;
}
AccessConfig accessConfig = new AccessConfig();
accessConfig.setOrgId(properties.getOrgId());
accessConfig.setCustomerNo(properties.getCustomerNo());
accessConfig.setAccessUrl(properties.getAccessUrl());
accessConfig.setSignPrivateKey(properties.getSignPrivateKey());
accessConfig.setSignVerifyPublicKey(properties.getSignVerifyPublicKey());
accessConfig.setDataEncryptKey(properties.getDataEncryptKey());
ConfigUtil.initConfig(accessConfig);
log.info("[secure-access] SDK 初始化完成, customerNo={}", properties.getCustomerNo());
}
}
package com.liquidnet.common.third.secureaccess.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "liquidnet.secure-access")
public class SecureAccessProperties {
/** 机构 ID */
private String orgId;
/** 业务站点号 */
private String customerNo;
/** 安全接入平台根地址,如 https://sap.ctdid.cn:29002 */
private String accessUrl;
/** 机构侧 P1 签名私钥(Base64) */
private String signPrivateKey;
/** 安全接入平台验签公钥(Base64) */
private String signVerifyPublicKey;
/** 数据加密证书(Base64) */
private String dataEncryptKey;
/** 应用名称,对应接口文档 appName */
private String appName;
/** 认证模式:R01 网络身份认证凭证 */
private String mode = "R01";
/** 公共服务平台 auth/request 地址(测试/生产由配置切换) */
private String forwardUrl = "http://authtest.ctdid.cn:10002/uentrance/interf/auth/request";
}
package com.liquidnet.common.third.secureaccess.model;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class NiaAuthRequest {
/** 与前端 SDK 一致的 32 位业务序列号 */
private final String bizSeq;
/** 前端拉起网证 App 后 getAuthResult 获取 */
private final String idCardAuthData;
}
package com.liquidnet.common.third.secureaccess.model;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class NiaAuthResult {
private final boolean success;
private final String resultCode;
private final String resultDesc;
private final String bizSeq;
private final String bizSerialNo;
/** 网络身份应用标识标准 Base64(40 字节 → 56 字符) */
private final String openId;
private final String photoCompareScore;
private final String encryptedIdInfo;
/** 失败阶段:CONFIG / PARAM / SAP / PARSE / PLATFORM / UNKNOWN */
private final String stage;
/** SAP/平台原始响应摘要,便于联调 */
private final String rawResponse;
@Override
public String toString() {
return "NiaAuthResult{success=" + success
+ ", resultCode=" + resultCode
+ ", resultDesc=" + resultDesc
+ ", bizSeq=" + bizSeq
+ ", bizSerialNo=" + bizSerialNo
+ ", openId=" + openId
+ ", stage=" + stage
+ '}';
}
}
package com.liquidnet.common.third.secureaccess.model;
import lombok.Builder;
import lombok.Getter;
import org.apache.commons.codec.binary.Hex;
/**
* PID(认证服务凭据)按表 6 解码后的结构。
*/
@Getter
@Builder
public class NiaPidParsed {
private static final int TIMESTAMP_LEN = 14;
private final int totalLen;
private final int version;
/** 第 1–40 字节:网络身份应用标识(二进制,非文本) */
private final byte[] identifier;
/** 第 41–54 字节:yyyymmddhhmmss */
private final String timestamp;
/** 第 55+ 字节:签名 */
private final byte[] signature;
public String identifierHex() {
return identifier == null ? null : Hex.encodeHexString(identifier).toUpperCase();
}
/** 标识段可视化:可打印字符原样,不可打印用 '.' */
public String identifierVisual() {
if (identifier == null) {
return null;
}
StringBuilder sb = new StringBuilder(identifier.length);
for (byte b : identifier) {
sb.append(b >= 32 && b < 127 ? (char) b : '.');
}
return sb.toString();
}
public String signatureHexPrefix(int maxBytes) {
if (signature == null || signature.length == 0) {
return null;
}
int len = Math.min(signature.length, maxBytes);
byte[] head = new byte[len];
System.arraycopy(signature, 0, head, 0, len);
String hex = Hex.encodeHexString(head);
return signature.length > maxBytes ? hex + "..." : hex;
}
}
package com.liquidnet.common.third.secureaccess.util;
import com.liquidnet.common.third.secureaccess.model.NiaPidParsed;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
/**
* PID 解析与 openId 编码,与网证官方说明一致:
* <pre>
* byte[] raw = Base64.getDecoder().decode(pid);
* System.arraycopy(raw, 1, identifier, 0, 40);
* </pre>
* 存库 openId 取 40 字节标识的<strong>标准 Base64</strong>(56 字符,可放进 varchar(64))。
*/
public final class NiaPidUtils {
public static final int IDENTIFIER_BYTE_LEN = 40;
/** 标准 Base64(含填充)固定 56 字符 */
public static final int OPEN_ID_LEN = 56;
private static final Decoder JDK_BASE64_DECODER = java.util.Base64.getDecoder();
private static final Encoder JDK_BASE64_ENCODER = java.util.Base64.getEncoder();
private static final int TIMESTAMP_LEN = 14;
private static final int MIN_PID_LEN = 1 + IDENTIFIER_BYTE_LEN + TIMESTAMP_LEN;
private NiaPidUtils() {
}
public static NiaPidParsed parse(String pidBase64) {
if (StringUtils.isBlank(pidBase64)) {
return null;
}
byte[] raw;
try {
raw = JDK_BASE64_DECODER.decode(pidBase64.trim());
} catch (IllegalArgumentException ex) {
return null;
}
if (raw.length < MIN_PID_LEN) {
return null;
}
int version = raw[0] & 0xFF;
byte[] identifier = new byte[IDENTIFIER_BYTE_LEN];
System.arraycopy(raw, 1, identifier, 0, IDENTIFIER_BYTE_LEN);
byte[] tsBytes = new byte[TIMESTAMP_LEN];
System.arraycopy(raw, 1 + IDENTIFIER_BYTE_LEN, tsBytes, 0, TIMESTAMP_LEN);
String timestamp = new String(tsBytes, StandardCharsets.US_ASCII);
if (!timestamp.matches("\\d{14}")) {
return null;
}
byte[] signature = new byte[raw.length - MIN_PID_LEN];
if (signature.length > 0) {
System.arraycopy(raw, MIN_PID_LEN, signature, 0, signature.length);
}
return NiaPidParsed.builder()
.totalLen(raw.length)
.version(version)
.identifier(identifier)
.timestamp(timestamp)
.signature(signature)
.build();
}
/** 40 字节标识 → 标准 Base64(56 字符) */
public static String extractOpenId(String pid) {
NiaPidParsed parsed = parse(pid);
if (parsed == null || parsed.getIdentifier() == null) {
return null;
}
return encodeOpenId(parsed.getIdentifier());
}
public static String encodeOpenId(byte[] identifier) {
if (identifier == null || identifier.length != IDENTIFIER_BYTE_LEN) {
return null;
}
return JDK_BASE64_ENCODER.encodeToString(identifier);
}
/** 日志排查用:40 字节标识 → 大写 hex */
public static String toHex(byte[] identifier) {
if (identifier == null || identifier.length != IDENTIFIER_BYTE_LEN) {
return null;
}
return Hex.encodeHexString(identifier).toUpperCase();
}
public static byte[] toIdentifierBytes(String openId) {
if (StringUtils.isBlank(openId)) {
return null;
}
try {
byte[] decoded = JDK_BASE64_DECODER.decode(openId.trim());
return decoded.length == IDENTIFIER_BYTE_LEN ? decoded : null;
} catch (IllegalArgumentException ex) {
return null;
}
}
public static boolean isValidOpenId(String openId) {
if (StringUtils.isBlank(openId) || openId.length() != OPEN_ID_LEN) {
return false;
}
return toIdentifierBytes(openId) != null;
}
public static String maskPid(String pid) {
if (StringUtils.isBlank(pid)) {
return null;
}
if (pid.length() <= 16) {
return "***";
}
return pid.substring(0, 8) + "..." + pid.substring(pid.length() - 8);
}
}
package com.liquidnet.common.third.secureaccess.util;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class NiaPidUtilsTest {
private static final String PID_1 =
"Al7Y07Qo4P5eHosb4ADGQC4ou83HI+U6tgsUXC3uQYlZMDAwMDA3MTIyMDI2MDYyMzE5MTg1MjBGAiEAt5CKWHwrjso5x8i4Vl5cNiSCZAFwQefMXHyPoNIV4IMCIQCV1/pVvwyOlF6I6qLywzXHCGDmT6OVnBPX3+ZyMBWYjg==";
private static final String PID_2 =
"Al7Y07Qo4P5eHosb4ADGQC4ou83HI+U6tgsUXC3uQYlZMDAwMDA3MTIyMDI2MDYyNDExNTMxNDBFAiBfpnywTM/7efM4tAK5GglCWG39yMmFdYpfZDFRIpg30gIhAKkCuggZx2KoW0Uf1xy2BmF7OdBlCEqjlzujwUBq1ZDU";
private static final String OPEN_ID_B64 =
"XtjTtCjg/l4eixvgAMZALii7zccj5Tq2CxRcLe5BiVkwMDAwMDcxMg==";
@Test
void openId_shouldBeStandardBase64AndStableAcrossAuths() {
String openId1 = NiaPidUtils.extractOpenId(PID_1);
String openId2 = NiaPidUtils.extractOpenId(PID_2);
assertEquals(openId1, openId2);
assertEquals(56, openId1.length());
assertEquals(OPEN_ID_B64, openId1);
}
@Test
void openId_roundTrip() {
String openId = NiaPidUtils.extractOpenId(PID_1);
byte[] bytes = NiaPidUtils.toIdentifierBytes(openId);
assertNotNull(bytes);
assertEquals(openId, NiaPidUtils.encodeOpenId(bytes));
}
}
......@@ -17,5 +17,6 @@
<module>liquidnet-common-third-antchain</module>
<module>liquidnet-common-third-xuper</module>
<module>liquidnet-common-third-sqb</module>
<module>liquidnet-common-third-secure-access</module>
</modules>
</project>
......@@ -272,4 +272,15 @@ liquidnet:
public-key: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuf1oOZm3u5NraTs4F8AABXbtU2jSiWYp+IWmQ36vokuq6s2s3eKQR6l4RkrPPxjC86bIvjT4pApJZJrFMA4YcjY4G49wFZySfom4IPaZlKsOrNGJH0Kag0BSO9U5el1z7dMz7oP9cChbdl4mjKuqYtgnNtaPT+SqhXRQdFcc9kiVybAGs8WEGqsdwxsmD9aZTd4rQMvLEGWIj/MLdo7w1avc0WVSPQSM5jRHjjQmUzEuusv+QGcDt3ttNaip2uo1xoQdcwILYmS6fnWL8xKw4V8lX0CWypUKIZcIc1Y/1N8VeUN+8MirdrS5JSghq62Yifu9A3W/mANB+S6yYwD+WQIDAQAB'
merchant-id: 'b2d63146-934f-401f-a864-14926d952c16'
merchant-user-id: '6d0d632e-50e9-464e-bbb3-be76047ec835'
role: 'super_admin'
\ No newline at end of file
role: 'super_admin'
secure-access:
org-id: '00000712'
customer-no: '00000712AA'
access-url: 'https://sap.ctdid.cn:29002'
sign-private-key: 'MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgY1WpjKNKWWDbqDRaBTzjCwbQxUo/ZIWWES0Xrp9YzY6hRANCAATfC188MtTPD/ziXvqI2VHLOspvZ9bBh2n7LNWgaP2QIXBJ/0vIRoSMT7ZwklfgZu58gI5ig8N2XvGHxWBdyLfF' # 机构 P1 签名私钥,联调时填入
sign-verify-public-key: 'MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEUNi/1x2Bh3mGHdpTjs+jIdv62G7oAU/dKzczYtuz1yol0ivUwt3xpA7nPEacWK+IIj2ZfOev4G5qrndMEs7t6Q==' # SAP 验签公钥
data-encrypt-key: 'MIICcjCCAhagAwIBAgISEAIFAyYEAhMYU+LoH1l4EIShMAwGCCqBHM9VAYN1BQAwRDELMAkGA1UEBhMCQ04xETAPBgNVBAoMCENITkNURElEMREwDwYDVQQLDAhDSE5DVERJRDEPMA0GA1UEAwwGWVdDQTAxMB4XDTI2MDQwMjA1MjUxM1oXDTQyMDMyOTA1MjUxM1owMTEPMA0GA1UEAwwGUlpGVzAzMREwDwYDVQQLDAhDSE5DVERJRDELMAkGA1UEBhMCQ04wWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAAQunUvCkEN4ISqw4sp0+Tvr14uKQt0VpKkgpSwi0HhGOJlUrC9bmitCk0xa0+5zd6lw1Cx5o4M4xCmZQQfzxb9uo4H4MIH1MAkGA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgM4MIGXBgNVHR8EgY8wgYwwTKBKoEikRjBEMQ8wDQYDVQQDDAZZV0NBMDExETAPBgNVBAsMCENITkNURElEMREwDwYDVQQKDAhDSE5DVERJRDELMAkGA1UEBhMCQ04wPKA6oDiGNmh0dHA6Ly8xNzIuMjguMzAuODoxODA2MC9jYXdlYi9jcmwvWVdDQTAxL1lXQ0EwMV8wLmNybDAdBgNVHQ4EFgQU264a6MnK3/Ks3dVrI0BoEg2CLokwHwYDVR0jBBgwFoAUm1gG9DaAOEzQny6vTi6LlnTNgagwDAYIKoEcz1UBg3UFAANIADBFAiAPqpe2P76Eu0TBHKPqtflsCXTMUolq+FBXKK8nPhW9SwIhAKHGhPvqIAWcDHpu8xlHYJ9bkAwhJDDHNwspU7x6V0au' # 数据加密证书
app-name: '正在现场'
mode: 'R01'
forward-url: 'http://authtest.ctdid.cn:10002/uentrance/interf/auth/request'
\ No newline at end of file
......@@ -270,4 +270,16 @@ liquidnet:
public-key: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuf1oOZm3u5NraTs4F8AABXbtU2jSiWYp+IWmQ36vokuq6s2s3eKQR6l4RkrPPxjC86bIvjT4pApJZJrFMA4YcjY4G49wFZySfom4IPaZlKsOrNGJH0Kag0BSO9U5el1z7dMz7oP9cChbdl4mjKuqYtgnNtaPT+SqhXRQdFcc9kiVybAGs8WEGqsdwxsmD9aZTd4rQMvLEGWIj/MLdo7w1avc0WVSPQSM5jRHjjQmUzEuusv+QGcDt3ttNaip2uo1xoQdcwILYmS6fnWL8xKw4V8lX0CWypUKIZcIc1Y/1N8VeUN+8MirdrS5JSghq62Yifu9A3W/mANB+S6yYwD+WQIDAQAB'
merchant-id: 'b2d63146-934f-401f-a864-14926d952c16'
merchant-user-id: '6d0d632e-50e9-464e-bbb3-be76047ec835'
role: 'super_admin'
\ No newline at end of file
role: 'super_admin'
secure-access:
org-id: '00000712'
customer-no: '00000712AA'
access-url: 'https://sap.ctdid.cn:29002'
sign-private-key: 'MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgY1WpjKNKWWDbqDRaBTzjCwbQxUo/ZIWWES0Xrp9YzY6hRANCAATfC188MtTPD/ziXvqI2VHLOspvZ9bBh2n7LNWgaP2QIXBJ/0vIRoSMT7ZwklfgZu58gI5ig8N2XvGHxWBdyLfF' # 机构 P1 签名私钥,联调时填入
sign-verify-public-key: 'MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEUNi/1x2Bh3mGHdpTjs+jIdv62G7oAU/dKzczYtuz1yol0ivUwt3xpA7nPEacWK+IIj2ZfOev4G5qrndMEs7t6Q==' # SAP 验签公钥
data-encrypt-key: 'MIICcjCCAhagAwIBAgISEAIFAyYEAhMYU+LoH1l4EIShMAwGCCqBHM9VAYN1BQAwRDELMAkGA1UEBhMCQ04xETAPBgNVBAoMCENITkNURElEMREwDwYDVQQLDAhDSE5DVERJRDEPMA0GA1UEAwwGWVdDQTAxMB4XDTI2MDQwMjA1MjUxM1oXDTQyMDMyOTA1MjUxM1owMTEPMA0GA1UEAwwGUlpGVzAzMREwDwYDVQQLDAhDSE5DVERJRDELMAkGA1UEBhMCQ04wWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAAQunUvCkEN4ISqw4sp0+Tvr14uKQt0VpKkgpSwi0HhGOJlUrC9bmitCk0xa0+5zd6lw1Cx5o4M4xCmZQQfzxb9uo4H4MIH1MAkGA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgM4MIGXBgNVHR8EgY8wgYwwTKBKoEikRjBEMQ8wDQYDVQQDDAZZV0NBMDExETAPBgNVBAsMCENITkNURElEMREwDwYDVQQKDAhDSE5DVERJRDELMAkGA1UEBhMCQ04wPKA6oDiGNmh0dHA6Ly8xNzIuMjguMzAuODoxODA2MC9jYXdlYi9jcmwvWVdDQTAxL1lXQ0EwMV8wLmNybDAdBgNVHQ4EFgQU264a6MnK3/Ks3dVrI0BoEg2CLokwHwYDVR0jBBgwFoAUm1gG9DaAOEzQny6vTi6LlnTNgagwDAYIKoEcz1UBg3UFAANIADBFAiAPqpe2P76Eu0TBHKPqtflsCXTMUolq+FBXKK8nPhW9SwIhAKHGhPvqIAWcDHpu8xlHYJ9bkAwhJDDHNwspU7x6V0au' # 数据加密证书
app-name: '正在现场'
mode: 'R01'
# forward-url: 'http://authtest.ctdid.cn:10002/uentrance/interf/auth/request'
forward-url: 'http://auth.ctdid.cn:10002/uentrance/interf/auth/request'
\ No newline at end of file
......@@ -6,7 +6,7 @@ create table adam_third_party
(
mid bigint unsigned auto_increment primary key,
uid varchar(64) default '',
open_id varchar(64) comment '第三方ID',
open_id varchar(64) comment '第三方ID(NIA为56位标准Base64)',
avatar varchar(255) comment '第三方头像',
nickname varchar(100) comment '第三方昵称',
platform varchar(20) comment '平台名称',
......
......@@ -77,6 +77,11 @@
<artifactId>liquidnet-common-third-shumei</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.liquidnet</groupId>
<artifactId>liquidnet-common-third-secure-access</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
......
......@@ -14,9 +14,11 @@ import com.liquidnet.commons.lang.constant.LnsEnum;
import com.liquidnet.commons.lang.core.JwtValidator;
import com.liquidnet.commons.lang.util.*;
import com.liquidnet.service.adam.constant.AdamConst;
import com.liquidnet.service.adam.dto.AdamNiaLoginParam;
import com.liquidnet.service.adam.dto.AdamThirdPartParam;
import com.liquidnet.service.adam.dto.vo.AdamLoginInfoVo;
import com.liquidnet.service.adam.dto.vo.AdamUserInfoVo;
import com.liquidnet.service.adam.service.IAdamNiaLoginService;
import com.liquidnet.service.adam.service.AdamRdmService;
import com.liquidnet.service.adam.service.AdamWechatService;
import com.liquidnet.service.adam.service.IAdamUserService;
......@@ -79,6 +81,8 @@ public class AdamLoginController {
AdamUserSessionSupport adamUserSessionSupport;
@Autowired
SilentMobileOtpV2Support silentMobileOtpV2Support;
@Autowired
IAdamNiaLoginService adamNiaLoginService;
@Value("${liquidnet.reviewer.app-login.mobile}")
private String reviewMobile;
......@@ -328,6 +332,14 @@ public class AdamLoginController {
return this.loginVoResponseProcessing(loginInfoVo);
}
@ApiOperationSupport(order = 8)
@ApiOperation(value = "国家网络身份认证", notes = "仅做身份认证并返回 openId,前端拿到后调用 login/tpa 完成登录/注册绑定")
@PostMapping(value = {"login/nia"})
public ResponseDto<?> loginByNia(@Valid @RequestBody AdamNiaLoginParam param) {
log.info("login by nia, bizSeq={}", param.getBizSeq());
return adamNiaLoginService.login(param);
}
@ApiOperationSupport(order = 7)
@ApiOperation(value = "手机号静默登录")
@ApiImplicitParams({
......@@ -522,19 +534,7 @@ public class AdamLoginController {
}
private String ssoProcess(AdamUserInfoVo userInfoVo) {
Map<String, Object> claimsMap = CollectionUtil.mapStringObject();
claimsMap.put(CurrentUtil.TOKEN_SUB, userInfoVo.getUid());
claimsMap.put(CurrentUtil.TOKEN_MOBILE, userInfoVo.getMobile());
claimsMap.put(CurrentUtil.TOKEN_NICKNAME, userInfoVo.getNickname());
claimsMap.put(CurrentUtil.TOKEN_TYPE, CurrentUtil.TOKEN_TYPE_VAL_USER);
claimsMap.put(CurrentUtil.TOKEN_UCREATED, DateUtil.Formatter.yyyyMMddHHmmssTrim.format(userInfoVo.getCreateAt()));
log.debug("Gentoken:{}", claimsMap);
String jti = jwtValidator.generateJti();
String token = jwtValidator.create(claimsMap, jti);
adamUserSessionSupport.bindSession(jti, userInfoVo.getUid());
return token;
return adamUserSessionSupport.issueToken(userInfoVo);
}
private ResponseDto<AdamLoginInfoVo> loginVoResponseProcessing(AdamLoginInfoVo loginInfoVo) {
......
......@@ -365,7 +365,7 @@ public class AdamUserController {
@ApiOperationSupport(order = 8)
@ApiOperation(value = "解绑第三方账号")
@ApiImplicitParams({
@ApiImplicitParam(type = "form", required = true, dataType = "String", name = "platform", value = "平台类型", allowableValues = "WEIBO,WECHAT,QQ"),
@ApiImplicitParam(type = "form", required = true, dataType = "String", name = "platform", value = "平台类型", allowableValues = "WEIBO,WECHAT,QQ,NIA"),
})
@PostMapping(value = {"tpa/unbind/{platform}"})
public ResponseDto<List<AdamThirdPartInfoVo>> unbindTpa(@Pattern(regexp = LnsRegex.Valid.TRIPLE_PF_FOR_ULGOIN, message = "平台类型无效")
......
package com.liquidnet.service.adam.service;
import com.liquidnet.service.adam.dto.AdamNiaLoginParam;
import com.liquidnet.service.base.ResponseDto;
public interface IAdamNiaLoginService {
ResponseDto<?> login(AdamNiaLoginParam param);
}
package com.liquidnet.service.adam.service.impl;
import com.liquidnet.common.third.secureaccess.biz.NiaAuthBiz;
import com.liquidnet.common.third.secureaccess.model.NiaAuthRequest;
import com.liquidnet.common.third.secureaccess.model.NiaAuthResult;
import com.liquidnet.common.third.secureaccess.util.NiaPidUtils;
import com.liquidnet.service.adam.constant.AdamTpaConst;
import com.liquidnet.service.adam.dto.AdamNiaLoginParam;
import com.liquidnet.service.adam.dto.vo.AdamNiaAuthVo;
import com.liquidnet.service.adam.service.IAdamNiaLoginService;
import com.liquidnet.service.base.ResponseDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class AdamNiaLoginServiceImpl implements IAdamNiaLoginService {
private final NiaAuthBiz niaAuthBiz;
@Override
public ResponseDto<?> login(AdamNiaLoginParam param) {
if (!niaAuthBiz.isReady()) {
return ResponseDto.failure("10005", "网证认证服务暂未开通,请稍后再试");
}
log.info("[nia-login] 请求 bizSeq={}, idCardAuthDataLen={}",
param.getBizSeq(), param.getIdCardAuthData() == null ? 0 : param.getIdCardAuthData().length());
NiaAuthResult authResult = niaAuthBiz.authenticate(NiaAuthRequest.builder()
.bizSeq(param.getBizSeq())
.idCardAuthData(param.getIdCardAuthData())
.build());
if (!authResult.isSuccess()) {
log.warn("[nia-login] 认证失败 bizSeq={}, stage={}, code={}, desc={}",
param.getBizSeq(),
authResult.getStage(),
authResult.getResultCode(),
authResult.getResultDesc());
String errMsg = authResult.getResultDesc() != null && !authResult.getResultDesc().isEmpty()
? authResult.getResultDesc() : "网证认证失败,请重试";
return ResponseDto.failure("10005", errMsg);
}
if (!NiaPidUtils.isValidOpenId(authResult.getOpenId())) {
log.warn("[nia-login] PID标识解析失败 bizSeq={}", param.getBizSeq());
return ResponseDto.failure("10005", "网证认证失败,请重试");
}
AdamNiaAuthVo authVo = AdamNiaAuthVo.builder()
.platform(AdamTpaConst.PLATFORM_NIA)
.openId(authResult.getOpenId())
.build();
log.info("[nia-login] 认证成功 bizSeq={}, bizSerialNo={}, openId={}",
param.getBizSeq(), authResult.getBizSerialNo(), authResult.getOpenId());
return ResponseDto.success(authVo);
}
}
......@@ -2,10 +2,16 @@ package com.liquidnet.service.adam.support;
import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.commons.lang.core.JwtValidator;
import com.liquidnet.commons.lang.util.CollectionUtil;
import com.liquidnet.commons.lang.util.CurrentUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.service.adam.dto.vo.AdamUserInfoVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 用户 JTI 会话:adam:session:{jti}=uid,adam:session:user:{uid} 记录活跃 jti。
*/
......@@ -32,4 +38,17 @@ public class AdamUserSessionSupport {
redisUtil.setRemove(jwtValidator.userSessionsKey(uid), jti);
}
}
public String issueToken(AdamUserInfoVo userInfoVo) {
Map<String, Object> claimsMap = CollectionUtil.mapStringObject();
claimsMap.put(CurrentUtil.TOKEN_SUB, userInfoVo.getUid());
claimsMap.put(CurrentUtil.TOKEN_MOBILE, userInfoVo.getMobile());
claimsMap.put(CurrentUtil.TOKEN_NICKNAME, userInfoVo.getNickname());
claimsMap.put(CurrentUtil.TOKEN_TYPE, CurrentUtil.TOKEN_TYPE_VAL_USER);
claimsMap.put(CurrentUtil.TOKEN_UCREATED, DateUtil.Formatter.yyyyMMddHHmmssTrim.format(userInfoVo.getCreateAt()));
String jti = jwtValidator.generateJti();
String token = jwtValidator.create(claimsMap, jti);
bindSession(jti, userInfoVo.getUid());
return token;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment