使用MetAmask的授权 - 签名和验证 - 您可以在这种方法中找到任何弱点吗?

发布于 2025-02-14 00:48:11 字数 3563 浏览 0 评论 0 原文

我想开发一个WebApp(FrontEnd:reactJ,后端:Spring Boot),在该WebApp中,用户可以在无需登录单词的含义(用户名 +密码)的情况下进行交互,而是使用MetAmask来证明其身份。

你们能否在以下方法中找到任何弱点:

  1. 客户端请求服务器/nonce?publicAddress = {metAmaskAddress}
  2. 服务器为特定的metAmask地址生成一个UUID,并使用简短的ttl和ttl和将其返回到客户端
  3. 客户端通过metAmask客户端符号符号,
  4. 提出了一个请求,将签名的nonce和MetAmask地址放在标题
  5. 服务器 中签署nonce的metAmask地址与标题中发送的一个相同,并且nonce不是过期的

客户端代码(使用ethers库)

        if (!window.ethereum) {
            throw new Error("No crypto wallet found. Please install it.");
        }
        await window.ethereum.send("eth_requestAccounts");
        const provider = new ethers.providers.Web3Provider(window.ethereum);

        const signer = provider.getSigner();
        const address = await signer.getAddress();

        const nonceResponse = await fetch(`/nonce?userId=${address}`, {
            method: "get",
            headers: {
                "Content-Type": "application/json",
            },
        });

        const nonce = await nonceResponse.text();

        const signature = await signer.signMessage(nonce);

        await fetch("/doSomething", {
            method: "post",
            headers: {
                "Content-Type": "application/json",
                "Authorization": signature,
                "Public-Address": address,
            },
            body: JSON.stringify("data"),
        });

服务器端 - 从

    @PostMapping("/doSomething")
    public void doSomething(
            @RequestHeader(value = HttpHeaders.AUTHORIZATION) String signedNonce,
            @RequestHeader(value = "Public-Address") String publicAddress,
            @RequestBody String body) {

        String originalNonce = nonceService.getNonceForAddress(publicAddress)
                .orElseThrow(() -> new HttpClientErrorException(HttpStatus.UNAUTHORIZED));

        boolean verified = verifySignature(
                publicAddress,
                signedNonce,
                originalNonce
        );

        if (verified) {
            // do something with the body
        } else {
            throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED);
        }
    }

    
    public static boolean verifySignature(String address, String signedMessage, String originalMessage) {
        String decryptedAddress = getAddressUsedToSignHashedPrefixedMessage(signedMessage, originalMessage);
        decryptedAddress = "0x" + decryptedAddress;

        return address.equalsIgnoreCase(decryptedAddress);
    }

    @SneakyThrows
    private static String getAddressUsedToSignHashedPrefixedMessage(String signedHash, String originalMessage) {
        String r = signedHash.substring(0, 66);
        String s = "0x" + signedHash.substring(66, 130);
        String v = "0x" + signedHash.substring(130, 132);

        String publicKey = Sign.signedPrefixedMessageToKey(
                        originalMessage.getBytes(),
                        new Sign.SignatureData(
                                Numeric.hexStringToByteArray(v)[0],
                                Numeric.hexStringToByteArray(r),
                                Numeric.hexStringToByteArray(s))
                )
                .toString(16);

        return Keys.getAddress(publicKey);
    }

(请忽略Sneakythrows)

感谢您阅读这篇文章 - 任何评论/建议/批评都将受到赞赏:)

I would like to develop a webapp (frontend: ReactJs, backend: Spring Boot) in which users can interact without logging in in the traditional sense of the word (username + password) but instead using Metamask for proving their identity.

Can you guys find any weaknesses in the following approach:

  1. Client requests a nonce from the server /nonce?publicAddress={metamaskAddress}
  2. Server generates a UUID for a specific Metamask address and stores it with a short TTL and returns it to the client
  3. Client signs the nonce via Metamask
  4. Client makes a request putting the signed nonce and the Metamask address in the headers
  5. Server verifies that the Metamask address that signed the nonce is the same of the one sent in the headers and that the nonce is not expired

Client code (using ethers library)

        if (!window.ethereum) {
            throw new Error("No crypto wallet found. Please install it.");
        }
        await window.ethereum.send("eth_requestAccounts");
        const provider = new ethers.providers.Web3Provider(window.ethereum);

        const signer = provider.getSigner();
        const address = await signer.getAddress();

        const nonceResponse = await fetch(`/nonce?userId=${address}`, {
            method: "get",
            headers: {
                "Content-Type": "application/json",
            },
        });

        const nonce = await nonceResponse.text();

        const signature = await signer.signMessage(nonce);

        await fetch("/doSomething", {
            method: "post",
            headers: {
                "Content-Type": "application/json",
                "Authorization": signature,
                "Public-Address": address,
            },
            body: JSON.stringify("data"),
        });

Server side - validation code taken from sridharreddyu/web3-java-snippets

    @PostMapping("/doSomething")
    public void doSomething(
            @RequestHeader(value = HttpHeaders.AUTHORIZATION) String signedNonce,
            @RequestHeader(value = "Public-Address") String publicAddress,
            @RequestBody String body) {

        String originalNonce = nonceService.getNonceForAddress(publicAddress)
                .orElseThrow(() -> new HttpClientErrorException(HttpStatus.UNAUTHORIZED));

        boolean verified = verifySignature(
                publicAddress,
                signedNonce,
                originalNonce
        );

        if (verified) {
            // do something with the body
        } else {
            throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED);
        }
    }

    
    public static boolean verifySignature(String address, String signedMessage, String originalMessage) {
        String decryptedAddress = getAddressUsedToSignHashedPrefixedMessage(signedMessage, originalMessage);
        decryptedAddress = "0x" + decryptedAddress;

        return address.equalsIgnoreCase(decryptedAddress);
    }

    @SneakyThrows
    private static String getAddressUsedToSignHashedPrefixedMessage(String signedHash, String originalMessage) {
        String r = signedHash.substring(0, 66);
        String s = "0x" + signedHash.substring(66, 130);
        String v = "0x" + signedHash.substring(130, 132);

        String publicKey = Sign.signedPrefixedMessageToKey(
                        originalMessage.getBytes(),
                        new Sign.SignatureData(
                                Numeric.hexStringToByteArray(v)[0],
                                Numeric.hexStringToByteArray(r),
                                Numeric.hexStringToByteArray(s))
                )
                .toString(16);

        return Keys.getAddress(publicKey);
    }

(please ignore the SneakyThrows)

Thank you for reading this far - any comment/suggestion/criticism is appreciated :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文