配置Apple Universal Links

配置网站 创建 apple-app-site-association 在网站根目录或者 .well-known目录下创建 apple-app-site-association 文件。注意,文件没有后缀名。 文件是一个json文件,内容如下: { "applinks": { "apps": [], "details": [ { "appID": "团队ID.应用BundleID" "paths": ["/路径/*"] } ] } } 团队ID,和应用签名证书上的TeamId保存一致 路径 ,遵循url对应的规范,建议每一个App应用都使用独立的路径,便于管理 配置MIME 因为apple-app-site-association 的数据格式是json,所有对应MIME类型:application/json。 在对应的站点配置文件里设置,以nginx为例: # 如果文件在根目录 location /apple-app-site-association { default_type application/json; } # 如果文件在.well-known目录 location /.well-known/apple-app-site-association { default_type application/json; } 验证网站配置 打开 https://branch.io/resources/aasa-validator/ , 输入网站域名并验证配置是否正确。 关于缓存 Apple会缓存apple-app-site-association文件内容,缓存未必及时更新(一般不超过48小时),所以有时候通过Universal Links打开App会失败。 打开 https://app-site-association.cdn-apple.com/a/v1/你的网站域名可以查看缓存内容 创建 Universal Links网页 还需要在网站部署一个 Universal Links网页。网页的访问路径需要和apple-app-site-association文件中配置的路径一致。 当使用 Safari 浏览器打开Universal Links网页时,可唤起App。 ...

May 21, 2026 · 2 min · holdsky

Undefined symbols ... dispatch thunk

一个因为swift name mangled符号不一致导致的错误: dispatch thunk of Alamofire.MultipartFormData.append(_: Foundation.Data, withName: Swift.String, fileName: Swift.String?, mimeType: Swift.String?) -> ()", referenced from: 先说工程结构 工程A有一个静态framework target a,通过cocoapods以源码方式集成了封装Alamofire,封装Alamofire的一些能力。 workspace A |____ a (源码) |____Alamofire (源码) 构建输出静态framework a。 工程B有一个App Target,通过cocoapods以源码方式集成封装Alamofire,以静态库方式集成了 a。 workspace B |___App |____ a (静态库) |____ Alamofire (源码) ``` 在同一电脑,用同一个Xcode,设置了相同的swift version,结果App构建失败,找不到Alamofire里的符号。 经过一番观察,发现 "dispatch thunk of Alamofire.MultipartFormData.append(...)"是 Swift 编译器为带有默认参数、可变签名或需要方法桥接的函数生成的“thunk”符号。也就是说静态库a引用了这些由 Alamofire 生成的编译器私有符号,但最终链接器没有在任何被链接的库/框架中找到对应实现。 检查工程配置,首先排除Alamofire 的实现没有被链接到最终可执行文件(常见:静态库a引用但 App 没把 Alamofire 链接上)。 那么,最有可能的情况就是:工程B的Alamofire 与 工程A 使用不同的 Swift 编译环境/选项,导致 thunk 的名称/生成方式不一致,最终没有可匹配的符号。虽然Alamofire代码一摸一样,但是编译后的符号修饰方式不一样。 示意例子,例如同样是方法 test, 工程A中被修饰为 _$s12Alamofire4test tKF 工程B中被修饰为 $s12Alamofire4test_tKj ...

January 23, 2026 · 1 min · holdsky

TLS证书签发流程

签发类型 根据签发机构类型,TLS证书签发可以划分为两种: 公信CA(Certificate Authority)签发的证书(如DigiCert、Let’s Encrypt、Sectigo等) 企业内部私有CA签发的证书。 公信CA签发与企业内部私有CA签发,两者的区别是公信力不同。一般情况下,互联网环境不应信任企业内部私有CA签发的证书。 下面主要详细说明最常见的 公信CA签发域名验证(DV)、组织验证(OV)、扩展验证(EV)证书的过程,以及全网最流行的免费证书Let’s Encrypt为例的自动化流程。 公信CA签发证书的通用流程(DV/OV/EV) 生成CSR(Certificate Signing Request,证书签名请求) 在你的服务器上首先生成一对密钥:私钥(private key)和公钥(public key)。 使用私钥生成CSR文件,CSR里包含了: 域名(Common Name 或 SAN 列表) 组织信息(OV/EV证书需要) 公钥 国家、省份、城市、公司名称等(OV/EV需要) 常用命令(OpenSSL): openssl req -new -newkey rsa:2048 -nodes -keyout domain.key -out domain.csr 选择CA并提交申请 登录CA官网(或通过ACME协议自动化),选择证书类型(DV/OV/EV)、有效期。 上传CSR(或者有些CA让你直接在网页填写信息后帮你生成)。 域名控制权验证(DCV,Domain Control Validation) 这是所有公信证书都必须完成的步骤,证明你确实控制这个域名。常见三种方式: HTTP验证(最常见):CA给你一段随机token,你需要在域名下 .well-known/acme-challenge/ 路径放一个特定文件 示例: http://yourdomain.com/.well-known/acme-challenge/xxxxxx DNS验证:CA给你一段TXT记录,你需要在域名DNS管理面板添加 _acme-challenge.yourdomain.com TXT “xxxxxx” Email验证(较少用):给域名whois邮箱或管理员邮箱(如admin@、webmaster@)发邮件,点链接确认 DV证书只做这一步;OV/EV还要额外验证组织合法性。 (OV/EV专属)组织信息验证 核实公司工商注册信息(通过第三方数据库如Dun & Bradstreet) 核实营业执照、法人身份 电话回访确认申请人身份和授权 验证域名管理权归属公司(whois + 额外证明文件) CA签发证书 CA用自己的中间证书或根证书的私钥,对你的公钥+域名+有效期等信息进行签名,生成证书文件(.crt 或 .pem) 同时会下发CA链证书(中间证书 bundle,参考下面附录) 下载并安装证书 你把证书、公钥、私钥安装到Web服务器(Nginx/Apache/IIS等) 重启服务,以使证书生效 最常见的自动化签发:Let’s Encrypt(ACME协议) Let’s Encrypt是目前全球使用量最大的免费CA,90天有效期,完全自动化。 ...

December 10, 2025 · 2 min · holdsky

后端登陆模块常见的校验策略

后端登录模块常见的校验策略(以安全为首要目标),从输入校验到风控防御,通常会分多层实现,以下是生产环境中广泛采用的实战校验策略汇总: 1. 基础参数校验(最基本的一道防线) 用户名/手机号/邮箱:非空、格式正则、长度限制、防注入(预编译语句或ORM自动处理) 密码:非空、长度(通常8-32位)、不能包含用户名等 验证码(图片/短信/邮箱):非空、长度固定、是否过期(通常60-300秒) 请求必传字段完整性(deviceId、timestamp、sign等) 2. 账号状态校验 账号是否存在 账号是否被禁用/冻结/注销 账号是否需要强制修改密码(首次登录、密码过期) 账号是否被加入黑名单 3. 密码相关安全校验 密码明文传输 → 必须强制HTTPS 密码加密传输(前端通常用RSA或SM2加密,后端私钥解密) 后端密码存储:bcrypt、scrypt、Argon2(加盐,绝不用MD5/SHA1明盐) 密码输错次数限制(单账号5-10次后锁定30分钟~永久,需要人工解封) 密码复杂度策略(可选:大小写+数字+特殊字符) 4. 验证码多重校验(防止暴力破解) 图片验证码:防自动化脚本 短信/邮箱验证码:限流(同一手机号/IP/设备1分钟1条,1小时5条,24小时10条) 验证码有效期 + 只能使用一次 验证码输错次数限制(3-5次后需要重新获取) 5. 登录风控与异常检测(核心防爆破、防薅羊毛) 同一账号短时间内多次登录失败次数超限 → 锁定 同一IP短时间内大量不同账号登录失败 → IP封禁 同一设备(deviceId/deviceFingerprint)短时大量账号尝试 → 设备封禁 异地登录检测(根据IP归属地,与历史登录地对比,差距>1000km触发二次验证) 短时间内大规模登录请求 → 触发全局限流或人工介入 用户代理(User-Agent)异常或频繁切换 无头浏览器、模拟器、爬虫特征检测 6. 会话安全策略 登录成功后颁发token(推荐无状态JWT,或服务端Session+Redis) JWT需设置合理过期时间(accessToken 15min ~ 2h,refreshToken 7 ~ 30天) 支持单点登录(同账号新登录踢掉旧设备) 支持多端互踢配置(Web端登录踢掉App等) 敏感操作二次验证(修改密码、支付等需重新输入密码或验证码) 7. 常见辅助安全措施 登录接口全局限流(令牌桶/漏桶算法,如QPS>500触发降级) 关键日志记录安全审计日志(登录IP、设备、时间、结果等) 登录成功后下发设备绑定关系(可用于后续风控) 支持可信设备管理(记住设备30天免二次验证) 密码传输前端可使用RSA/SM2非对称加密,或使用SRP协议(密码永不离客户端) 8. 特殊场景校验 第三方登录(微信、QQ、Apple):校验state防CSRF,校验code是否已被使用 无密码登录(短信登录):必须绑定手机号 + 短信验证码 + 设备风控 扫码登录:防钓鱼站点、心跳保活、状态机严格校验 推荐的实战分层校验伪代码结构(Java/SpringBoot示例) @PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { // 1. 基础参数校验 validateParams(dto); // 2. 验证码校验 captchaService.check(dto.getCaptchaId(), dto.getCode()); // 3. 风控预检(IP、设备、全球限流) riskControl.preCheck(dto); // 4. 账号状态与密码校验 User user = userService.checkAccountAndPassword(dto); // 5. 登录风控后置(异地、异常检测) riskControl.afterLogin(user, request); // 6. 生成token并记录登录日志 String token = jwtUtil.createToken(user); loginLogService.asyncSave(user, request, true); return Result.ok(token); } 总结:现代后端登录模块已远不止“用户名+密码正确就放行”,而是一整套“参数→验证码→风控→账号状态→密码→二次验证→会话管理→日志”的立体防御体系。缺任何一层都可能被拖库、撞库、短信轰炸、羊毛党攻破。 ...

December 8, 2025 · 1 min · holdsky

在hugo里集成mermaid图支持

本文环境: hugo v0.152.2+extended+withdeploy darwin/amd64 BuildDate=2025-10-24T15:31:49Z VendorInfo=Homebrew 一般使用了hugo主题的话,默认是集成了mermaid图。 若不使用hugo主题,那么就需要自己手动集成了。 步骤如下: 创建 layouts/_markup/render-codeblock-mermaid.html 负责开启一个mermaid图的html区块,内容如下: <pre class="mermaid"> {{- .Inner | safeHTML }} </pre> {{ .Page.Store.Set "hasMermaid" true }} 修改 layouts/_default/baseof.html 如果没有该文件就创建一个。默认情况下,所有html页面的主体结构和内容由这个文件渲染。和mermaid图相关的内容如下: <!DOCTYPE html> <html lang="zh-CN"> <body> ...... ...... ...... <!-- 必须放到最后在 (.Content 之下),因为这渲染钩子直到执行 .Content 之后才会被处理),否则.Page.Store.Get "hasMermaid"永远为false --> {{ if .Page.Store.Get "hasMermaid" }} <!-- 只有文章里写了 mermaid 才加载下面这些资源,节省性能 --> <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true }); </script> {{ end }} </body> </html> 添加一个mermaid图看看效果 在网站的任意一个markdown文件里添加样例,看下效果 ...

December 1, 2025 · 1 min · holdsky

TLS 1.3前向安全的通俗解释

前向安全 前向安全(Forward Secrecy,简称 PFS)是 TLS(以及其他加密协议)中一个非常重要的安全属性,简单来说: “即使攻击者将来拿到了服务器的长期私钥(即证书私钥),也无法解密之前已经完成的所有 TLS 会话” 这就是前向安全的核心意义:过去的通信记录永远是安全的,即使私钥彻底泄露。 前向安全在TLS 1.3中强制开启的,安全性由 椭圆曲线离散对数问题(ECDLP) 保障 ECDH(椭圆曲线 Diffie-Hellman)最核心的数学原理 一句话就能说清楚: “乘法可以随便做,除法几乎做不出来” 我们用最常用、最直观的曲线 X25519 / Curve25519 来解释(TLS 1.3 里 90% 以上用的就是它)。 1. 公共参数(全世界都一样) 选定一个安全的椭圆曲线:y² = x³ + 486662x² + x(模 2²⁵⁵-19) 选定一个基点(base point)G,一个固定的点,坐标是已知的: G = (9, 447…)(具体数字不重要,只要大家用同一个就行) 2. 双方各自生成临时私钥(就是随机数) 爱丽丝(Alice,客户端)随机选一个 256 bit 的整数作为私钥: a = 随机数(比如 3f1a…c2,绝对保密!) 鲍勃(Bob,服务器)也随机选一个 256 bit 的整数: b = 随机数(比如 7d2b…e9,绝对保密!) 3. 计算并公开“公钥”(其实就是把私钥乘以基点 G) 爱丽丝算出自己的临时公钥: A = a × G 鲍勃算出自己的临时公钥: B = b × G ...

November 26, 2025 · 2 min · holdsky

using bridging headers with module interfaces is unsupported

Xcode创建framework,选择语言Swift。在使用Swift语言编写framework时,如果要引入Objective-C的代码或者其他Objective-C的库,可能会报错“using bridging headers with module interfaces is unsupported”。这通常是因为在模块接口中使用了桥接头文件(bridging-header),而这是不被支持的, 一般有两种解决办法: 一、如果这个framework不需要对外发布,那么将Build Setting里的Build Library For Distrubution 设置为NO,则可以继续使用桥接头文件(bridging-header)。 二、使用module map文件替换桥接头文件(bridging-header),具体操作如下: 1、在framework工程里创建一个头文件,如 Test.h 在Build Setting里设置User Header Search Paths,如 $(SRCROOT)/$(PRODUCT_NAME)。设置的路径需要能找到 Test.h 文件 2、在 Test.h 文件里引入需要引用的Objective-C头文件 #import <MyLib/MyLib.h> 3、创建一个module map文件, module.modulemap,内容如下 module TestModule (名字自定义) { header "Test.h" export * } 4、在Swift文件里,使用import指令,然后就可以调用Objective-C的方法了 import TestModule

September 26, 2025 · 1 min · holdsky

创建Objective-C可使用的Swift Framework

默认创建的使用Swift语言编写的framework是没有头文件的,如果另一个使用Objective-C语言编写的target想用引入swift framework,则需要在swift framework的工程上打开一些配置 同时使用public 和 @objc标记 要给Objective-C暴露swift编写的api,则需要使用public 和 @objc标记 @objc public class MySwiftClass: NSObject { @objc public func doSomething() { print("Doing something in Swift") } } 生成 Objective-C 兼容头文件 Swift Framework 的 Build Settings 中: Defines Module 设置为 YES。 Install Generated Header 设置为 YES。 这会生成一个 FrameworkName-Swift.h 头文件,包含 Swift 代码的 Objective-C 接口。 在 Objective-C 项目中导入导入 Swift Framework 的头文件 #import <FrameworkName/FrameworkName-Swift.h> MySwiftClass *swiftObject = [[MySwiftClass alloc] init]; [swiftObject doSomething];

September 24, 2025 · 1 min · holdsky

移动端环境,Kotlin Multiplatfrom 调用C/C++方法

前言 本文成文环境: macOS 26 Xcode 26 以及 iOS SDK Android Studio MeerKat Feature Drop 以及 Android SDK、NDK JDK 17 Gradle 8.11.1 Kotlin Multiplatfrom 通过Cinterop工具调用C/C++方法,也可以通过JNI规范调用C/C++方法。 本质上,都是调用C方法,C++方法需要通过 extern "C" 修饰为C方法。 本文将介绍Kotlin/Native在Android和iOS环境,如何调用C/C++方法。 Android Studio 推荐使用 Android Studio 开发Kotlin/Native (Kotlin更像是一款Android开发语言) Gradle Android Studio的御用构建工具(相比其构建工具,复杂且难用) CMake 一个事实上的C/C++构建标准工具,在Android和iOS环境调用各自平台的工具链构建C/C++。 创建 Kotlin Multiplatfrom 工程 如果Android Studio没有 Kotlin Multiplatfrom 工程模版,则需要先安装 Kotlin Multiplatfrom插件。 安装完成后,使用 Kotlin Multiplatfrom 工程模版创建一个工程(创建过程中,需要选择iOS的包管理方式,选择cocoapods) 镜像 因为环大陆的网络墙问题,可能需要配置下镜像仓库 1、找到工程根目录下的 settings.gradle.kts,添加下镜像仓库(下面配置的是阿里云的maven仓库) // settings.gradle.kts pluginManagement { repositories { maven{ setUrl("https://maven.aliyun.com/repository/public") } maven{ setUrl("https://maven.aliyun.com/repository/central") } maven{ setUrl("https://maven.aliyun.com/repository/gradle-plugin") } ... ... ... } } dependencyResolutionManagement { repositories { maven{ setUrl("https://maven.aliyun.com/repository/public") } maven{ setUrl("https://maven.aliyun.com/repository/central") } maven{ setUrl("https://maven.aliyun.com/repository/gradle-plugin") } ... ... ... } } 2、找到根目录下 gradle/wrapper/gradle-wrapper.properties,替换为distributionUrl ...

July 18, 2025 · 3 min · holdsky

CocoaPods Repo更新经常失败

先解决的墙的问题,建立高速且稳定的梯子,可以解决更新失败的问题。 还有一种原因是http协议导致的,切换到ssh协议成功率会更高。需要注意,使用ssh协议,需要有github账号,并配置好ssh 公钥信息。 使用ssh协议更新成功后,还要记得再将本地的cocoapods git仓库设置为http协议. cd ~/.cocoapods/repos/cocoapods // ssh git remote set-url origin https://github.com/CocoaPods/Specs.git // http git remote set-url origin git@github.com:CocoaPods/Specs.git 最后,首次初始化cocoapods repo仓库,也可以使用ssh协议 cd 一个临时文件夹 // 克隆到tmpRepo文件夹 git clone git@github.com:CocoaPods/Specs.git tmpRepo // 添加repo pod repo add trunk /xxx/yyy/tmpRepo // 修改远程地址 cd ~/.cocoapods/repos/cocoapods git remote set-url origin https://github.com/CocoaPods/Specs.git

November 1, 2024 · 1 min · holdsky