跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Java大前端java算法

无密码登录安全对比与 WebAuthn 实战实现

综述由AI生成本文深入分析了无密码登录的安全性,对比了传统密码、FingerprintJS 设备指纹与 WebAuthn(Passkey/FIDO2)三种方案的优劣。重点阐述了 WebAuthn 如何通过公钥密码学、Origin 绑定及 Challenge 机制有效抵御钓鱼、重放及中间人攻击。文章提供了基于 Spring Boot 与 webauthn4j 的后端实现及前端 HTML 交互代码,详细解析了后端在何种条件下确认认证成功,并论证了即使数据包被截获也无法伪造有效签名的原理。

开源信徒发布于 2026/3/16更新于 2026/5/45 浏览
无密码登录安全对比与 WebAuthn 实战实现

无密码登录安全性对比:FingerprintJS、WebAuthn 与传统密码

在当前的威胁环境下,单纯依赖用户名 + 密码的认证方式已显疲态。我们对比了三种主流方案:用户名 + FingerprintJS(设备指纹)、用户名 + 密码以及用户名 + WebAuthn(Passkey / FIDO2)。

这三种模式都支持'用户名 + 凭证'的组合,实际场景中用户可自由选择。下表基于真实世界的攻击场景(如钓鱼、中间人、重放等)进行维度评估。

维度用户名 + 密码登录用户名 + FingerprintJS 无密码登录用户名 + WebAuthn 无密码登录(Passkey)安全性排名
凭证窃取风险高(易被键盘记录或钓鱼页窃取)中低(需访问真实站点,但 JS 数据可被读取)极低(私钥永不离开设备)WebAuthn > FingerprintJS > 密码
钓鱼抵抗力差(用户易误入假站输入密码)差(假站同样能采集指纹并重放)极强(RP ID/Origin 绑定,假站无效)WebAuthn >> 其他
中间人攻击抵抗差(HTTPS 下仍可能后端弱加密)中(数据传输无强加密绑定)极强(公钥加密 + Origin 校验)WebAuthn >> 其他
凭证重放攻击高(密码泄露后可无限使用)中(可加 nonce 缓解,但非原生防重放)极低(Challenge 随机 + Counter 机制)WebAuthn > FingerprintJS > 密码
克隆/复制设备高(知道密码即可在任何设备登录)中高(高级攻击者可伪造指纹)低(私钥绑定硬件,极难克隆)WebAuthn > FingerprintJS > 密码
XSS 攻击下泄露高(密码框易被脚本窃取)高(localStorage 中的密钥易读)低(私钥不暴露给 JS 存储)WebAuthn > 其他
肩窥/键盘记录高(需手动输入)低(无需输入)低(仅需生物识别/PIN)FingerprintJS ≈ WebAuthn > 密码
用户体验中(需记忆复杂密码)低(几乎无缝)低–中(首次注册稍繁琐,后续一键)FingerprintJS ≈ WebAuthn > 密码
隐私影响低(仅存 Hash)高(跨站追踪风险大,GDPR 敏感)中(有限设备信息泄露)密码 > WebAuthn > FingerprintJS
NIST/OWASP 推荐AAL1–AAL2AAL2(勉强)AAL3(最高,抗钓鱼 MFA)WebAuthn >> 其他

场景建议

  • 高价值目标(金融、企业内网):首选 WebAuthn。这是目前唯一真正抗钓鱼的方案,符合 NIST AAL3 标准。
  • 中价值目标(电商、SaaS):推荐 WebAuthn,FingerprintJS 仅作为辅助风控手段,不建议单独作为唯一认证方式。
  • 低价值/高便利需求(论坛、内容站):可使用 FingerprintJS 或传统密码,但需知晓其安全性明显弱于 WebAuthn。
  • 最差组合:仅依赖 用户名 + FingerprintJS。极易受到高级钓鱼和 XSS 组合攻击,且存在严重隐私隐患。

为什么选择 WebAuthn?

WebAuthn 是浏览器原生 API,允许网站使用公钥凭证来安全认证用户身份。它支持无密码登录、生物识别(指纹/面部)、设备 PIN 或硬件安全密钥(如 YubiKey),彻底取代了传统的密码 + 短信验证码模式。

它是 FIDO2 框架的核心组成部分(FIDO2 = WebAuthn + CTAP2)。

解决传统密码痛点

传统用户名 + 密码面临以下致命问题:

  • 易被钓鱼:用户在假冒网站输入密码。
  • 易被泄露:数据库拖库、键盘记录器、肩窥。
  • 易被重放:密码一旦泄露,攻击者可在任意设备无限使用。
  • 体验差:用户需要记住复杂密码,重置成本高。

WebAuthn 通过公钥密码学 + 设备绑定解决了这些问题:

  • 私钥永不离开设备:服务器只存储公钥。
  • Origin/RP ID 绑定:假网站无法使用你在真实站点注册的凭证(强防钓鱼)。
  • Challenge + 签名:每次认证使用随机挑战,防止重放。
  • Counter 机制:防止同一凭证被克隆使用。
  • 生物识别支持:无需输入密码,提升体验。

必要环境配置

环境开发阶段可接受值生产环境必须值备注
协议http://localhosthttps://浏览器强制要求
域名(RP ID)localhost你的真实域名(example.com)不能是 IP 或 127.0.0.1
端口任意(5500、8080 等)443(标准 HTTPS)端口不影响,但必须 HTTPS

WebAuthn 流程图

下图展示了详细的序列图,包含所有技术细节:Challenge 生成、浏览器 API 调用、后端具体验证点(Challenge 匹配、签名验证、Counter 检查等)。假设使用 webauthn4j 后端库。

详细前后端交互逻辑

代码实现

这是一个前后端分离的完整示例,后端使用 Spring Boot,前端采用单文件 HTML 风格。项目结构如下:

webauthn-demo/
├── backend/ # Spring Boot 项目
│   ├── src/main/java/...
│   ├── pom.xml
│   └── ...
└── frontend/
    └── index.html # 单文件前端

后端:Spring Boot + webauthn4j

核心依赖 (pom.xml)
<?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">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>webauthn-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>webauthn-demo</name>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version>
        <relativePath/>
    </parent>
    <properties>
        <java.version>21</java.version>
        <webauthn4j.version>0.28.0.RELEASE</webauthn4j.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.webauthn4j</groupId>
            <artifactId>webauthn4j-core</artifactId>
            <version>${webauthn4j.version}</version>
        </dependency>
        <dependency>
            <groupId>com.webauthn4j</groupId>
            <artifactId>webauthn4j-util</artifactId>
            <version>${webauthn4j.version}</version>
        </dependency>
        <!-- 生产环境建议使用 Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
配置类:WebAuthnConfig.java
package com.example.webauthndemo.config;

import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.converter.jackson.ObjectConverter;
import com.webauthn4j.data.RelyingPartyIdentity;
import com.webauthn4j.validator.WebAuthnValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WebAuthnConfig {

    @Bean
    public RelyingPartyIdentity relyingPartyIdentity() {
        return RelyingPartyIdentity.builder()
                .id("localhost") // 开发时用 localhost,生产换成你的域名
                .name("WebAuthn Demo")
                .build();
    }

    @Bean
    public WebAuthnManager webAuthnManager() {
        return new WebAuthnManager();
    }

    @Bean
    public WebAuthnValidator webAuthnValidator() {
        return new WebAuthnValidator();
    }

    @Bean
    public ObjectConverter objectConverter() {
        return new ObjectConverter();
    }
}
简易内存存储:InMemoryCredentialRepository.java
package com.example.webauthndemo.repository;

import com.webauthn4j.credential.CredentialRecord;
import com.webauthn4j.data.ByteArray;
import com.webauthn4j.data.PublicKeyCredentialDescriptor;
import com.webauthn4j.data.PublicKeyCredentialType;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Component
public class InMemoryCredentialRepository {

    private final Map<ByteArray, List<CredentialRecord>> credentials = new ConcurrentHashMap<>();

    public void save(CredentialRecord credentialRecord) {
        ByteArray userHandle = credentialRecord.getUserHandle();
        credentials.computeIfAbsent(userHandle, k -> new ArrayList<>()).add(credentialRecord);
    }

    public List<CredentialRecord> findByUserHandle(ByteArray userHandle) {
        return credentials.getOrDefault(userHandle, Collections.emptyList());
    }

    public Optional<CredentialRecord> findByCredentialId(ByteArray credentialId) {
        return credentials.values().stream()
                .flatMap(List::stream)
                .filter(c -> c.getCredentialId().equals(credentialId))
                .findFirst();
    }

    public Set<PublicKeyCredentialDescriptor> getCredentialDescriptors(ByteArray userHandle) {
        List<CredentialRecord> records = findByUserHandle(userHandle);
        return records.stream()
                .map(r -> new PublicKeyCredentialDescriptor(
                        PublicKeyCredentialType.PUBLIC_KEY,
                        r.getCredentialId(),
                        r.getTransports()))
                .collect(Collectors.toSet());
    }
}
Controller:WebAuthnController.java
package com.example.webauthndemo.controller;

import com.example.webauthndemo.repository.InMemoryCredentialRepository;
import com.webauthn4j.WebAuthnManager;
import com.webauthn4j.converter.jackson.ObjectConverter;
import com.webauthn4j.credential.CredentialRecord;
import com.webauthn4j.data.*;
import com.webauthn4j.data.client.challenge.DefaultChallenge;
import com.webauthn4j.server.ServerProperty;
import com.webauthn4j.validator.WebAuthnValidator;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.*;

@RestController
@RequestMapping("/api/webauthn")
@RequiredArgsConstructor
public class WebAuthnController {

    private final InMemoryCredentialRepository credentialRepository;
    private final WebAuthnManager webAuthnManager;
    private final WebAuthnValidator webAuthnValidator;
    private final ObjectConverter objectConverter;

    private static final String RP_ID = "localhost";
    private static final String ORIGIN = "http://localhost:5500";

    // 1. 开始注册
    @PostMapping("/register/start")
    public ResponseEntity<Map<String, Object>> startRegistration() {
        Challenge challenge = new DefaultChallenge();
        PublicKeyCredentialCreationOptions options = new PublicKeyCredentialCreationOptions(
                new RelyingPartyIdentity(RP_ID, "WebAuthn Demo"),
                new UserIdentity(new ByteArray(UUID.randomUUID().toString().getBytes()), "testuser", "测试用户"),
                challenge,
                List.of(new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256)),
                null,
                new AuthenticatorSelectionCriteria(null, true, UserVerificationRequirement.PREFERRED),
                null, null, null
        );
        Map<String, Object> json = objectConverter.getJsonConverter().writeValueAsMap(options);
        return ResponseEntity.ok(json);
    }

    // 2. 完成注册
    @PostMapping("/register/finish")
    public ResponseEntity<String> finishRegistration(@RequestBody Map<String, Object> request) {
        try {
            PublicKeyCredential<AuthenticatorAttestationResponse, CollectedClientData> pkc = 
                    objectConverter.getJsonConverter().readValue(request, PublicKeyCredential.class);
            CredentialRecord credentialRecord = webAuthnManager.parseRegistrationResponse(pkc);
            credentialRepository.save(credentialRecord);
            return ResponseEntity.ok("注册成功");
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("注册失败:" + e.getMessage());
        }
    }

    // 3. 开始认证
    @PostMapping("/authenticate/start")
    public ResponseEntity<Map<String, Object>> startAuthentication() {
        Challenge challenge = new DefaultChallenge();
        PublicKeyCredentialRequestOptions options = new PublicKeyCredentialRequestOptions(
                challenge,
                60000L,
                RP_ID,
                credentialRepository.getCredentialDescriptors(new ByteArray("testuser".getBytes())),
                UserVerificationRequirement.PREFERRED,
                null
        );
        Map<String, Object> json = objectConverter.getJsonConverter().writeValueAsMap(options);
        return ResponseEntity.ok(json);
    }

    // 4. 完成认证
    @PostMapping("/authenticate/finish")
    public ResponseEntity<String> finishAuthentication(@RequestBody Map<String, Object> request) {
        try {
            PublicKeyCredential<AuthenticatorAssertionResponse, CollectedClientData> pkc = 
                    objectConverter.getJsonConverter().readValue(request, PublicKeyCredential.class);

            // 构建 ServerProperty,包含当前 challenge、origin、rpId
            ServerProperty serverProperty = new ServerProperty(
                    ORIGIN,
                    RP_ID,
                    new DefaultChallenge()
            );

            // 获取对应的凭证记录用于验签
            CredentialRecord credentialRecord = credentialRepository.findByCredentialId(
                    pkc.getId()
            ).orElseThrow(() -> new RuntimeException("凭证不存在"));

            AuthenticationData authenticationData = webAuthnManager.parseAuthenticationResponse(
                    pkc,
                    serverProperty,
                    credentialRecord,
                    null,
                    false
            );

            // 更新 counter 防止克隆
            credentialRecord.setCounter(authenticationData.getAuthenticatorData().getSignCount());
            credentialRepository.save(credentialRecord);

            return ResponseEntity.ok("登录成功");
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("认证失败:" + e.getMessage());
        }
    }
}
前端:index.html

注意:确保按钮 ID 与 JavaScript 事件监听器匹配。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>WebAuthn 前端 - Spring Boot Demo</title>
    <style>
        body { font-family: system-ui, sans-serif; padding: 20px; max-width: 800px; margin: auto; line-height: 1.6; }
        button { margin: 10px 0; padding: 12px 24px; font-size: 16px; cursor: pointer; background: #007bff; color: white; border: none; border-radius: 6px; }
        button:hover { background: #0056b3; }
        pre { background: #f8f9fa; padding: 16px; border-radius: 6px; overflow: auto; max-height: 500px; font-size: 14px; }
        #status { margin: 20px 0; font-weight: bold; font-size: 1.1em; color: #333; }
    </style>
</head>
<body>
    <h1>WebAuthn 测试(连接 Spring Boot 后端)</h1>
    <p>请确保后端运行在 http://localhost:8080,前端通过 http://localhost:5500 访问</p>
    <button id="registerStartBtn">开始注册 Passkey</button>
    <button id="loginStartBtn">开始无密码登录</button>
    <div id="status">等待操作...</div>
    <pre id="output"></pre>
    <script type="module">
        const status = document.getElementById('status');
        const output = document.getElementById('output');
        const API_BASE = 'http://localhost:8080/api/webauthn';

        async function fetchJson(url, method = 'POST', body = null) {
            const res = await fetch(url, {
                method,
                headers: { 'Content-Type': 'application/json' },
                body: body ? JSON.stringify(body) : null
            });
            if (!res.ok) throw new Error(await res.text());
            return res.json();
        }

        async function register() {
            status.textContent = "正在请求注册选项...";
            try {
                const optionsJSON = await fetchJson(`${API_BASE}/register/start`);
                const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJSON);
                status.textContent = "请按照浏览器提示创建 Passkey";
                const credential = await navigator.credentials.create({ publicKey: options });
                status.textContent = "正在提交注册结果...";
                const responseJSON = credential.toJSON();
                await fetchJson(`${API_BASE}/register/finish`, 'POST', responseJSON);
                status.textContent = "注册成功!";
                output.textContent = JSON.stringify(responseJSON, null, 2);
            } catch (err) {
                status.textContent = "注册失败";
                output.textContent = `${err.name}\n${err.message}`;
                console.error(err);
            }
        }

        async function login() {
            status.textContent = "正在请求认证选项...";
            try {
                const optionsJSON = await fetchJson(`${API_BASE}/authenticate/start`);
                const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON);
                status.textContent = "请按照浏览器提示验证身份";
                const assertion = await navigator.credentials.get({ publicKey: options });
                status.textContent = "正在提交认证结果...";
                const responseJSON = assertion.toJSON();
                await fetchJson(`${API_BASE}/authenticate/finish`, 'POST', responseJSON);
                status.textContent = "登录成功!";
                output.textContent = JSON.stringify(responseJSON, null, 2);
            } catch (err) {
                status.textContent = "登录失败";
                output.textContent = `${err.name}\n${err.message}`;
                console.error(err);
            }
        }

        document.getElementById('registerStartBtn').addEventListener('click', register);
        document.getElementById('loginStartBtn').addEventListener('click', login);
    </script>
</body>
</html>

运行步骤

  1. 启动 Spring Boot 后端服务。
  2. 将前端 index.html 放在本地服务器(如 VS Code Live Server),确保端口为 5500。
  3. 点击按钮即可体验完整的 WebAuthn 流程。

注意事项

  • 当前示例使用了内存存储,生产环境必须使用 Redis 或数据库持久化 Challenge 和凭证。
  • 生产环境必须启用 HTTPS,并将 RP ID 配置为真实域名。
  • 务必在 finishAuthentication 接口中执行完整的验证逻辑,包括 Origin、RP ID、Challenge 匹配及签名验证。

后端何时确认登录成功?

在 WebAuthn 的完整认证流程里,后端(Relying Party)在 /authenticate/finish 这一步验证通过后,才真正知道'这次登录是没问题的'。

认证流程回顾

  1. 前端发起登录 → 调用 /authenticate/start
    • 后端生成随机 Challenge + Options。
    • 返回给前端(PublicKeyCredentialRequestOptions JSON)。
  2. 前端调用 navigator.credentials.get()
    • 浏览器弹出 PIN/指纹/面部提示。
    • 用户验证成功后,生成签名(Assertion)。
    • 前端拿到 PublicKeyCredential(包含 authenticatorData、signature 等)。
    • 前端把 .toJSON() 结果 POST 到 /authenticate/finish。
  3. 后端收到 /authenticate/finish 请求(关键时刻)
    • 后端执行核心验证(webauthn4j 帮你完成大部分):
      • Challenge 是否匹配(防重放)。
      • Origin / RP ID 是否正确(防钓鱼)。
      • authenticatorData 中的标志位是否符合要求(UP、UV)。
      • 签名验证:用之前注册时存储的公钥验证 signature 是否有效。
      • Counter 是否递增(防克隆/重放)。
    • 如果以上全部通过 → webauthn4j 的验证方法会返回成功。
    • 此时后端才知道:这次认证是合法的,用户身份可信。
    • 后端可以签发会话(JWT、cookie、session ID 等),完成登录。

关键结论

  • 后端不是在收到前端请求时就认为登录成功。
  • 而是在 finishAuthentication 接口里,完成所有密码学验证后,才确认'登录没问题'。
  • 如果任何一项校验失败(签名不对、counter 没增长、origin 被篡改等),webauthn4j 会抛异常,后端返回失败。
  • 只有验证全部通过,后端才会签发会话 token 或设置登录状态。

前后端交互数据可以被伪造吗?

可以伪造'数据包'发送给后端,但你无法伪造'有效的签名数据'。

在 WebAuthn 的流程中,前后端交互的数据确实可以被截获、查看,甚至有人可以手动模拟一个 POST 请求。但由于密码学签名的存在,后端能瞬间识别出这些数据是'真货'还是'伪造品'。

1. 数据包的结构:看得见,改不动

当指纹识别成功后,前端发给后端的数据主要包含三部分:

  1. ClientDataJSON:包含 Challenge 和当前的域名(Origin)。
  2. AuthenticatorData:包含设备状态和签名计数器。
  3. Signature(签名):最关键的部分。

为什么不能伪造:签名是硬件使用私钥对前两部分内容的哈希值进行加密的结果。如果你篡改了 ClientDataJSON 里的任何一个字节(比如想把域名从 fake.com 改成 real.com),后端使用公钥解密签名时,计算出的哈希值就对不上了。

2. Challenge(挑战值):无法'预制'数据包

黑客可能会想:我先录制一段你登录成功的完整数据包,下次直接发给后端。

  • 后端的防御:后端在每次登录前给出的 Challenge 都是随机且唯一的。
  • 结果:就像银行柜台问你'今天的暗号是什么?',你必须用私钥对'今天的暗号'签名。黑客拿着'昨天的暗号'签名数据过来,后端会发现 Challenge 不匹配,直接丢弃。

3. 浏览器的'强制诚信':拦截域名伪造

这是 WebAuthn 最特殊的地方。在前后端交互中,Origin(域名)不是由前端 JavaScript 定义的,而是由浏览器内核强制写入的。

  • 伪造场景:黑客写了一个恶意脚本,试图在数据包里把 Origin 字段填成你的官网域名。
  • 浏览器的拦截:浏览器在调用底层硬件 API 时,会核实当前真实的访问地址。如果黑客在 evil.com 上运行,浏览器传给硬件进行签名的域名永远只能是 evil.com。
  • 后端的最后把关:当后端收到数据,发现签名里锁定的域名是 evil.com 而不是自己的域名,校验失败。

4. 模拟请求(脚本攻击)为什么行不通?

如果黑客跳过前端页面,直接用 Postman 或 Python 脚本往你的 Spring Boot 接口发一段伪造的 JSON:

  1. 没有私钥:脚本无法生成合法的 Signature。
  2. 验签失败:后端库会执行以下逻辑:
    • 检查 Challenge 是否存在于 Session 中。
    • 使用数据库里的公钥对 Signature 进行 RSA/EC 验签。
    • 检查 Origin 是否合法。

只要其中一项不符,后端就不会给这个请求签发 JWT 或 Session。

总结

在前后端交互中,黑客唯一能做的就是'观察'。他能看到你发了什么,但他改不了,也无法在没有你设备硬件的情况下生成一份新的、能通过后端校验的数据。

这就好比黑客截获了你的一张'亲笔签名信':

  • 他可以复印这张信(重放攻击),但因为信上的日期(Challenge)不对,收信人不认。
  • 他想改信的内容(篡改数据),但由于他没法模仿你的笔迹(私钥签名),收信人一眼就能看出签名被破坏了。

目录

  1. 无密码登录安全性对比:FingerprintJS、WebAuthn 与传统密码
  2. 场景建议
  3. 为什么选择 WebAuthn?
  4. 解决传统密码痛点
  5. 必要环境配置
  6. 代码实现
  7. 后端:Spring Boot + webauthn4j
  8. 核心依赖 (pom.xml)
  9. 配置类:WebAuthnConfig.java
  10. 简易内存存储:InMemoryCredentialRepository.java
  11. Controller:WebAuthnController.java
  12. 前端:index.html
  13. 运行步骤
  14. 注意事项
  15. 后端何时确认登录成功?
  16. 认证流程回顾
  17. 关键结论
  18. 前后端交互数据可以被伪造吗?
  19. 1. 数据包的结构:看得见,改不动
  20. 2. Challenge(挑战值):无法“预制”数据包
  21. 3. 浏览器的“强制诚信”:拦截域名伪造
  22. 4. 模拟请求(脚本攻击)为什么行不通?
  23. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 联想小新 Air14 R5 5500U 通过 M.2 转 OCuLink 外接 RX580 显卡方案
  • Langchain-Chatchat 项目搭建指南
  • OpenClaw 核心架构解析:本地优先与确定性执行
  • 无人机影像像素坐标转大地坐标
  • Java 桥接模式实战:解耦数据库与文件格式转换
  • DeepSeek 降 AI 指令组合使用指南:三步降低检测率
  • Whisper 语音识别快速入门:从安装到使用
  • C 语言实现 A*算法路径规划全流程
  • 声源定位算法基础:CBF(延时求和波束形成)
  • WebStorm 编码辅助 AI 插件使用指南
  • Python 网络爬虫实战:从基础请求到数据可视化
  • 大模型 AI Agent 在企业应用中的 6 种基础类型与落地场景
  • Java 虚拟机核心机制:类加载与垃圾回收详解
  • OpenClaw Linux 本地 AI 智能体部署指南
  • Windows 本地部署 Ollama+OpenClaw 构建 AI 生产力系统
  • macOS 终端 iTerm2 配置指南:SSH、Zsh 与 Powerlevel10k
  • 二分算法实战:查找元素范围及区间统计
  • LeetCode 49. 字母异位词分组:两种高效 C++ 解法
  • AI Prompt 工程指南:提示词设计原则与实战技巧
  • 闲置小米 9 打造复古掌机:天马 G 前端实战指南

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online