最近项目需要添加解码x509Certificate功能,可以使用openssl或者mbedtls库。对这两个库的使用总结一下。

一 Openssl解码x509 Certificate

  1. 初始化

      将一段buffer转化成openssl格式

const unsigned char* certificateValue = (unsigned char*)certificate->Value().data(); //这里的certificate是接收到的一段buffer
X509* m_certificate = d2i_X509(nullptr, &certificateValue, certificate->Value().size());

  2. 获得版本号

int32_t certVersion = X509_get_version(m_certificate);

    3. 获得序列号

const ASN1_INTEGER* ans1SerialNum = X509_get_serialNumber(m_certificate);
BIGNUM* bigSerialNUm = ASN1_INTEGER_to_BN(ans1SerialNum, nullptr);
char* serialNum = BN_bn2hex(bigSerialNUm);
serialNumber = std::string(serialNum, strlen(serialNum));
BN_free(bigSerialNUm);
OPENSSL_free(serialNum);

  4. 获得公钥类型

const EVP_PKEY* pubKey = X509_get_pubkey(m_certificate);
switch (pubKey->type) {
case EVP_PKEY_RSA:
type = X509CertPubKeyType::PUB_KEY_TYPE_RSA;
break;
case EVP_PKEY_EC:
type = X509CertKeyAlgType::PUB_KEY_TYPE_ECKEY;
break;
case EVP_PKEY_DSA:
type = X509CertKeyAlgType::PUB_KEY_TYPE_ECDSA;
break;
case EVP_PKEY_DH:
type = X509CertKeyAlgType::PUB_KEY_TYPE_ECKEY_DH;
break;
default:
type = X509CertKeyAlgType::PUB_KEY_TYPE_UNKNOWN;
break;
}

  5. 获得公钥使用类型

X509_check_ca(m_certificate);
if ((m_certificate->ex_kusage & KU_DATA_ENCIPHERMENT) == KU_DATA_ENCIPHERMENT) {
type = X509CertKeyUseType::KEY_USE_TYPE_EXCH;
}
else if ((m_certificate->ex_kusage & KU_DIGITAL_SIGNATURE) == KU_DIGITAL_SIGNATURE) {
type = X509CertKeyUseType::KEY_USE_TYPE_SIGN;
}
else {
type = X509CertKeyUseType::KEY_USE_TYPE_UNKNOWN;
}

  6. 获得签名算法类型

const ASN1_OBJECT* signAlg = m_certificate->sig_alg.algorithm;
const int32_t oidMaxLen = 128;
char oid[oidMaxLen] = { 0 };
OBJ_obj2txt(oid, oidMaxLen, signAlg, 1);
std::string strOid(oid, strlen(oid)); const std::string CERT_SIG_ALG_RSA_RSA = "1.2.840.113549.1.1.1";
const std::string CERT_SIG_ALG_MD2RSA = "1.2.840.113549.1.1.2";
const std::string CERT_SIG_ALG_MD4RSA = "1.2.840.113549.1.1.3";
const std::string CERT_SIG_ALG_MD5RSA = "1.2.840.113549.1.1.4";
const std::string CERT_SIG_ALG_SHA1RSA = "1.2.840.113549.1.1.5";
const std::string CERT_SIG_ALG_SM3SM2 = "1.2.156.10197.1.501"; if (strOid == CERT_SIG_ALG_RSA_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_RSA_RSA;
}
else if (strOid == CERT_SIG_ALG_MD2RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD2RSA;
}
else if (strOid == CERT_SIG_ALG_MD4RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD4RSA;
}
else if (strOid == CERT_SIG_ALG_MD5RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD5RSA;
}
else if (strOid == CERT_SIG_ALG_SHA1RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA1RSA;
}
else if (strOid == CERT_SIG_ALG_SM3SM2) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SM3SM2;
}
else {
type = X509CertSigAlgType::SIG_ALG_TYPE_UNKNOWN;
}

  7. 获得发布者名字

X509_NAME* issuerName = X509_get_issuer_name(m_certificate);
name = ConvertName(issuerName); //自定义函数

  8. 获得证书持有者

X509_NAME* subjectName = X509_get_subject_name(m_certificate);
name = ConvertName(subjectName);

  9. 获得证书有效时间起点

const ASN1_TIME* start = X509_get_notBefore(m_certificate);
time = ConvertTime(start);//自定义函数

  10. 获得证书结束时间

const ASN1_TIME* end = X509_get_notAfter(m_certificate);
time = ConvertTime(end);

  11. 获得公钥使用

const ASN1_BIT_STRING* keyUsage = (ASN1_BIT_STRING*)X509_get_ext_d2i(m_certificate, NID_key_usage, nullptr, nullptr);
uint16_t val = keyUsage->data[0];
if (keyUsage->length > 1) {
val |= keyUsage->data[1] << 8;
}
if (val & MBEDTLS_X509_KU_DIGITAL_SIGNATURE) {
usage += "Digital Signature, ";
}
if (val & MBEDTLS_X509_KU_NON_REPUDIATION) {
usage += "Non-Repudiation, ";
}
if (val & MBEDTLS_X509_KU_KEY_ENCIPHERMENT) {
usage += "Key Encipherment, ";
}
if (val & MBEDTLS_X509_KU_DATA_ENCIPHERMENT) {
usage += "Data Encipherment, ";
}
if (val & MBEDTLS_X509_KU_KEY_AGREEMENT) {
usage += "Key Agreement, ";
}
if (val & MBEDTLS_X509_KU_KEY_CERT_SIGN) {
usage += "Certificate Signature, ";
}
if (val & MBEDTLS_X509_KU_CRL_SIGN) {
usage += "CRL Signature, ";
}
const int32_t valMaxLen = 32;
char value[valMaxLen] = { 0 };
sprintf_s(value, valMaxLen, "(%x)", val);
usage += std::string(value, strlen(value));

  12. 获得强化公钥使用

EXTENDED_KEY_USAGE* enUsage = (EXTENDED_KEY_USAGE*)X509_get_ext_d2i(m_certificate, NID_ext_key_usage, nullptr, nullptr);
for (int i = 0; i < sk_ASN1_OBJECT_num(enUsage); i++) {
const int32_t objMaxLen = 128;
char objId[objMaxLen] = { 0 };
char objName[objMaxLen] = { 0 };
const ASN1_OBJECT* obj = sk_ASN1_OBJECT_value(enUsage, i);
OBJ_obj2txt(objId, sizeof(objId), obj, 1);
OBJ_obj2txt(objName, sizeof(objName), obj, 0);
if (!usage.empty()) {
usage += "; ";
}
usage += objName + std::string(" (") + objId + ")";
}
sk_ASN1_OBJECT_pop_free(enUsage, ASN1_OBJECT_free);

  13. 获得基础限制

BASIC_CONSTRAINTS* bcons = (BASIC_CONSTRAINTS*)X509_get_ext_d2i(m_certificate, NID_basic_constraints, nullptr, nullptr);
if (bcons->ca == 0) {
constraints += "Subject Type=End Entity; Path Length Constraint=None";
}
else {
std::string pathLenConstraint = nullptr == bcons->pathlen ? "None" : std::string((char*)bcons->pathlen->data);
constraints += "Subject Type=CA; " + std::string("Path Length Constraint=") + pathLenConstraint;
}
BASIC_CONSTRAINTS_free(bcons);

  14. 获得SAN

STACK_OF(GENERAL_NAME)* extensions = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(m_certificate, NID_subject_alt_name, nullptr, nullptr);
for (int i = 0; i < sk_GENERAL_NAME_num(extensions); i++) {
const GENERAL_NAME* nval = sk_GENERAL_NAME_value(extensions, i);
if (nval->type == GEN_DNS) {
const unsigned char* dnsName = ASN1_STRING_get0_data(nval->d.dNSName);
dnsNames.push_back("DNS Name=" + std::string((const char*)dnsName));
}
else if (nval->type == GEN_IPADD) {
const unsigned char* ipAddr = ASN1_STRING_get0_data(nval->d.iPAddress);
ipAddrs.push_back("IP Address=" + ConvertIpAddr(ipAddr));//ConvertIpAddr是自定义函数
}
else if (nval->type == GEN_URI) {
const unsigned char* uri = ASN1_STRING_get0_data(nval->d.uniformResourceIdentifier);
uris.push_back("URL=" + std::string((const char*)uri));
}
else if (nval->type == GEN_DIRNAME) {
X509_NAME* dirName = nval->d.directoryName;
dirNames.push_back("Directory Name=" + ConvertName(dirName));
}
else if (nval->type == GEN_EMAIL) {
const unsigned char* email = ASN1_STRING_get0_data(nval->d.rfc822Name);
emails.push_back("RFC822 Name=" + std::string((const char*)email));
}
}
sk_GENERAL_NAME_pop_free(extensions, GENERAL_NAME_free)

  15. 自定义函数ConvertName

std::string ConvertName(X509_NAME * name)
{
if (nullptr == name) {
  return "";
}
const int32_t partNameMaxLen = 256;
char partName[partNameMaxLen] = { 0 };
std::string strName;
int returnLen = X509_NAME_get_text_by_NID(name, NID_countryName, partName, partNameMaxLen);
if (returnLen > 0) {
strName += "C=" + std::string(partName, strlen(partName)) + ", ";
}
memset(partName, 0, partNameMaxLen);
returnLen = X509_NAME_get_text_by_NID(name, NID_organizationalUnitName, partName, partNameMaxLen);
if (returnLen > 0) {
strName += "OU=" + std::string(partName, strlen(partName)) + ", ";
}
memset(partName, 0, partNameMaxLen);
returnLen = X509_NAME_get_text_by_NID(name, NID_commonName, partName, partNameMaxLen);
if (returnLen > 0) {
strName += "CN=" + std::string(partName, strlen(partName));
} return strName;
}

  16. 自定义函数ConvertTime

std::string ConvertTime(const ASN1_TIME * time)
{
if (nullptr == time) {
return "";
}
std::shared_ptr<tm> tmTime(new tm());
int res = ASN1_TIME_to_tm(time, tmTime.get());
if (res == 0) {
return "";
}
const int32_t bufMaxLen = 256;
char buf[bufMaxLen] = { 0 };
int32_t basicYear = 1900;
int32_t basicMon = 1;
int32_t basicDay = 0;
int32_t basicHour = 8;
int32_t basicMin = 0;
int32_t basicSec = 0;
#ifdef _WIN32
sprintf_s(buf, "%d-%d-%d %d:%d:%d", tmTime->tm_year + basicYear, tmTime->tm_mon + basicMon, tmTime->tm_mday + basicDay,
tmTime->tm_hour + basicHour, tmTime->tm_min + basicMin, tmTime->tm_sec + basicSec);
#else
sprintf(buf, "%d-%d-%d %d:%d:%d", tmTime->tm_year + basicYear, tmTime->tm_mon + basicMon, tmTime->tm_mday + basicDay,
tmTime->tm_hour + basicHour, tmTime->tm_min + basicMin, tmTime->tm_sec + basicSec);
#endif
return std::string(buf, strlen(buf));
}

  17. 自定义函数ConvertIp

std::string ConvertIpAddr(const unsigned char* ipv4octet)
{
if (nullptr == ipv4octet) {
return "";
}
std::string ipAddr;
for (auto i = 0; i < 4; i++)
{
if (!ipAddr.empty())
{
ipAddr += '.';
} char bits[4] = { 0 };
#ifdef _WIN32
sprintf_s(bits, sizeof(bits), "%d", ipv4octet[i]);
#else
snprintf(bits, sizeof(bits), "%d", ipv4octet[i]);
#endif // _WIN32
ipAddr.append(bits);
}
return ipAddr;
}

二 Mbedtls解码x509 Certificate

mbedtls的相关资料很少,自己也是研究了很长时间。并且SAN只支持Hostname

  1. 初始化

      将一段buffer转化成mbedtls类型

mbedtls_x509_crt_init(m_certificate);
uint32_t status = mbedtls_x509_crt_parse(m_certificate, (const unsigned char*)certificate->Value().data(), certificate->Value().size());

  2. 获得版本号

int32_t certVersion = m_certificate->version;

  3. 获得序列号

mbedtls_mpi mpi;
mbedtls_mpi_init(&mpi);
uint32_t status = mbedtls_mpi_read_binary(&mpi, m_certificate->serial.p, m_certificate->serial.len);
const int32_t strMaxLen = 128;
char str[strMaxLen] = { 0 };
size_t returnLen;
uint32_t radix = 16;
status = mbedtls_mpi_write_string(&mpi, radix, str, strMaxLen, &returnLen);
serialNumber = std::string(str, strlen(str));
mbedtls_mpi_free(&mpi);

  4. 获得公钥类型

mbedtls_pk_type_t pubKeyType = mbedtls_pk_get_type(&m_certificate->pk);
switch (pubKeyType) {
case mbedtls_pk_type_t::MBEDTLS_PK_RSA:
type = X509CertPubKeyType::PUB_KEY_TYPE_RSA;
break;
case mbedtls_pk_type_t::MBEDTLS_PK_ECKEY:
type = X509CertPubKeyType::PUB_KEY_TYPE_ECKEY;
break;
case mbedtls_pk_type_t::MBEDTLS_PK_ECKEY_DH:
type = X509CertPubKeyType::PUB_KEY_TYPE_ECKEY_DH;
break;
case mbedtls_pk_type_t::MBEDTLS_PK_ECDSA:
type = X509CertPubKeyType::PUB_KEY_TYPE_ECDSA;
break;
case mbedtls_pk_type_t::MBEDTLS_PK_RSA_ALT:
type = X509CertPubKeyType::PUB_KEY_TYPE_RSA_ALT;
break;
case mbedtls_pk_type_t::MBEDTLS_PK_RSASSA_PSS:
type = X509CertPubKeyType::PUB_KEY_TYPE_RSASSA_PSS;
break;
default:
type = X509CertPubKeyType::PUB_KEY_TYPE_UNKNOWN;
break;
}

  5. 获得公钥使用类型

if ((m_certificate->key_usage & MBEDTLS_X509_KU_DATA_ENCIPHERMENT) == MBEDTLS_X509_KU_DATA_ENCIPHERMENT) {
type = X509CertKeyUseType::KEY_USE_TYPE_EXCH;
}
else if ((m_certificate->key_usage & MBEDTLS_X509_KU_DIGITAL_SIGNATURE) == MBEDTLS_X509_KU_DIGITAL_SIGNATURE) {
type = X509CertKeyUseType::KEY_USE_TYPE_SIGN;
}
else {
type = X509CertKeyUseType::KEY_USE_TYPE_UNKNOWN;
}

  6. 获得签名算法类型

mbedtls_md_type_t mdType;
mbedtls_pk_type_t pkType;
uint32_t status = mbedtls_oid_get_sig_alg(&m_certificate->sig_oid, &mdType, &pkType);
if (mdType == MBEDTLS_MD_MD2 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD2RSA;
}
else if (mdType == MBEDTLS_MD_MD4 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD4RSA;
}
else if (mdType == MBEDTLS_MD_MD5 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_MD5RSA;
}
else if (mdType == MBEDTLS_MD_SHA1 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA1RSA;
}
else if (mdType == MBEDTLS_MD_SHA224 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA224RSA;
}
else if (mdType == MBEDTLS_MD_SHA256 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA256RSA;
}
else if (mdType == MBEDTLS_MD_SHA384 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA384RSA;
}
else if (mdType == MBEDTLS_MD_SHA512 && pkType == MBEDTLS_PK_RSA) {
type = X509CertSigAlgType::SIG_ALG_TYPE_SHA512RSA;
}
else {
type = X509CertSigAlgType::SIG_ALG_TYPE_UNKNOWN;
}

  7. 获得发布者名字

const char* shortName = nullptr;
uint32_t status = OpcUa_Good;
do{
if (MBEDTLS_ASN1_UTF8_STRING != m_certificate->issuer.val.tag) {
continue;
}
status = mbedtls_oid_get_attr_short_name(&m_certificate->issuer.oid, &shortName);
name += shortName + std::string("=") + std::string((char*)m_certificate->issuer.val.p, m_certificate->issuer.val.len);
}while (nullptr != m_certificate->issuer.next);

  8. 获得证书持有者

const char* shortName = nullptr;
uint32_t status = OpcUa_Good;
do {
if (MBEDTLS_ASN1_UTF8_STRING != m_certificate->subject.val.tag)
{
continue;
}
status = mbedtls_oid_get_attr_short_name(&m_certificate->subject.oid, &shortName);
name += shortName + std::string("=") + std::string((char*)m_certificate->subject.val.p, m_certificate->subject.val.len);
} while (nullptr != m_certificate->subject.next);

  9. 获得证书起始时间

const int32_t bufMaxLen = 256;
char buf[bufMaxLen] = { 0 };
uint32_t basicHour = 8;
sprintf_s(buf, "%d-%d-%d %d:%d:%d", m_certificate->valid_from.year, m_certificate->valid_from.mon, m_certificate->valid_from.day,
m_certificate->valid_from.hour + basicHour, m_certificate->valid_from.min, m_certificate->valid_from.sec);
time = std::string(buf, strlen(buf));

  10. 获得证书结束时间

const int32_t bufMaxLen = 256;
char buf[bufMaxLen] = { 0 };
uint32_t basicHour = 8;
sprintf_s(buf, "%d-%d-%d %d:%d:%d", m_certificate->valid_to.year, m_certificate->valid_to.mon, m_certificate->valid_to.day,
m_certificate->valid_to.hour + basicHour, m_certificate->valid_to.min, m_certificate->valid_to.sec);
time = std::string(buf, strlen(buf));

  11. 获得证书使用

uint32_t val = m_certificate->key_usage;
if (val & MBEDTLS_X509_KU_DIGITAL_SIGNATURE) {
usage += "Digital Signature, ";
}
if (val & MBEDTLS_X509_KU_NON_REPUDIATION) {
usage += "Non-Repudiation, ";
}
if (val & MBEDTLS_X509_KU_KEY_ENCIPHERMENT) {
usage += "Key Encipherment, ";
}
if (val & MBEDTLS_X509_KU_DATA_ENCIPHERMENT) {
usage += "Data Encipherment, ";
}
if (val & MBEDTLS_X509_KU_KEY_AGREEMENT) {
usage += "Key Agreement, ";
}
if (val & MBEDTLS_X509_KU_KEY_CERT_SIGN) {
usage += "Certificate Signature, ";
}
if (val & MBEDTLS_X509_KU_CRL_SIGN) {
usage += "CRL Signature, ";
}
const int32_t valMaxLen = 32;
char value[valMaxLen] = { 0 };
sprintf_s(value, valMaxLen, "(%x)", val);
usage += std::string(value, strlen(value));

  12. 获得强化公钥使用

mbedtls_x509_sequence* enKeyUsage = &m_certificate->ext_key_usage;
while( nullptr != enKeyUsage) {
const char* des = nullptr;
uint32_t status = mbedtls_oid_get_extended_key_usage(&enKeyUsage->buf, &des);
const int valMaxLen = 128;
char val[valMaxLen] = { 0 };
status = mbedtls_oid_get_numeric_string(val, valMaxLen, &enKeyUsage->buf);
if (!usage.empty()) {
usage += ";";
}
usage += des + std::string(" (") + std::string(val, strlen(val)) + ")";
enKeyUsage = enKeyUsage->next;
}

  13. 获得基础限制

if (m_certificate->ca_istrue == 0) {
constraints = "Subject Type=End Entity; Path Length Constraint=None";
}
else {
std::string pathLenConstraint = 0 == m_certificate->max_pathlen ? "None" : std::to_string(m_certificate->max_pathlen);
constraints += "Subject Type=CA; " + std::string("Path Length Constraint=") + pathLenConstraint;
}

  14. 获得SAN(仅支持Hostname)

mbedtls_asn1_sequence* san = &m_certificate->subject_alt_names;
while (nullptr != san) {
dnsNames.push_back(std::string((char*)san->buf.p, san->buf.len));//dsnNames类型是std::vector<std::string>
san = san->next;
}

纯原创,参考请标明出处,谢谢!!

Mbedtls和Opesnssl 解码x509Certificate的更多相关文章

  1. 基于MbedTLS的AES加密实现,含STM32H7和STM32F4的实现例程

    说明: 1.mbedTLS的前身是PolarSSL,开源免费. 主要提供了的SSL/TLS支持(在传输层对网络进行加密),各种加密算法,各种哈希算法,随机数生成以及X.509(密码学里公钥证书的格式标 ...

  2. 痞子衡嵌入式:对比MbedTLS算法库纯软件实现与i.MXRT上DCP,CAAM硬件加速器实现性能差异

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是MbedTLS算法库纯软件实现与i.MXRT上DCP,CAAM硬件加速器实现性能差异. 近期有 i.MXRT 客户在集成 OTA SBL ...

  3. linux字符串url编码与解码

    编码的两种方式 echo '手机' | tr -d '\n' | xxd -plain | sed 's/\(..\)/%\1/g' echo '手机' |tr -d '\n' |od -An -tx ...

  4. URI编码解码和base64

    概述 对于uri的编解码,在js中有3对函数,分别是escape/unescape,encodeURI/decodeURI,encodeURIComponent/decodeURIComponent. ...

  5. FFmpeg学习2:解码数据结构及函数总结

    在上一篇文章中,对FFmpeg的视频解码过程做了一个总结.由于才接触FFmpeg,还是挺陌生的,这里就解码过程再做一个总结. 本文的总结分为以下两个部分: 数据读取,主要关注在解码过程中所用到的FFm ...

  6. Unicode转义(\uXXXX)的编码和解码

    在涉及Web前端开发时, 有时会遇到\uXXXX格式表示的字符, 其中XXXX是16进制数字的字符串表示形式, 在js中这个叫Unicode转义字符, 和\n \r同属于转义字符. 在其他语言中也有类 ...

  7. C# base 64图片编码解码

    使用WinForm实现了图片base64编码解码的 效果图: 示例base 64编码字符串: /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKD ...

  8. java编码原理,java编码和解码问题

    java的编码方式原理 java的JVM的缺省编码方式由系统的“本地语言环境”设置确定,和操作系统的类型无关 . 在JAVA源文件-->JAVAC-->Class-->Java--& ...

  9. [LeetCode] Decode String 解码字符串

    Given an encoded string, return it's decoded string. The encoding rule is: k[encoded_string], where ...

随机推荐

  1. 基于laravel的有偿开源流程引擎

    系统主要文档已经编写完成,具体请前往查看[系统文档](https://www.kancloud.cn/lijianlin/jishullin_workflow_engine/1894424 " ...

  2. java里equals和hashCode之间什么关系

    如果要比较实际内存中的内容,那就要用equals方法,但是!!! 如果是你自己定义的一个类,比较自定义类用equals和==是一样的,都是比较句柄地址,因为自定义的类是继承于object,而objec ...

  3. 替换unimrcp的VAD模块

    摘要: unimrcp vad 模块voice activity dector一直认为比较粗暴,而且unimrcp的社区也很久没有更新了.使用原始unimrcp如果只是用来做Demo演示,通过手动调整 ...

  4. 用android studio多渠道打包

    1. 官方教程 https://developer.android.com/studio/build/build-variants.html 2. 设置Build Types参数 打开 Project ...

  5. 如何自制WC3地形纹理贴图

    http://world-editor-tutorials.thehelper.net/tilesets.php https://wenku.baidu.com/view/e761c953cc1755 ...

  6. 关于JavaScript点击按钮打开多个页面被浏览器以广告嫌疑拦截怎么解决

    JS点击按钮打开新的标签页,工作中遇到需要点击按钮打开一个或多个,需要用到window.open() 工作中我们可能需要打开多个,看以下代码: var data = [{ "id" ...

  7. Laravel5的验证码功能

    第三方扩展包 mews/captcha 作为基础来实现 Laravel 中的验证码功能 安装 注册 配置验证码文件 前端引用 后端验证 安装前准备(我这边没执行这个,安装成功,但是搜到的文件有写,不清 ...

  8. 超详细,Windows系统搭建Flink官方练习环境

    如何快速的投入到Flink的学习当中,很多人在搭建环境过程中浪费了太多的时间.一套一劳永逸的本机Flink开发环境可以让我们快速的投入到Flink的学习中去,将精力用在Flink的原理,实战.这也对于 ...

  9. 【Spring】IOC容器注解汇总,你想要的都在这儿了!!

    写在前面 之前,我们在[Spring]专题中更新了不少关于Spring注解相关的文章,有些小伙伴反馈说,看历史文章的话比较零散,经常会忘记自己看到哪一篇了.当打开一篇新文章时,总感觉自己似乎是看到过了 ...

  10. 转载:Oracle常见字段类型

    转载节选自:https://bbs.csdn.net/topics/220059184 数据类型 参数 描述 char(n) n=1 to 2000字节 定长字符串,n字节长,如果不指定长度,缺省为1 ...