What is a good way to validate that the public key claimed to belong to a particular user really belongs to him without revealing the associated private key? Can this be done with zero-knowledge proofs? or is there a better approach.

This can be done by Signing and Validating. If someone signs a message with his private key, you can check that the public key belongs to that person by validating the signed message with it.

What is Signing?

Signing is the act of a user A “signing” data that anyone can validate came from user A. This is used in transactions to check if they are real.

[...]

Instead of real-world signatures, which can be faked, the digital ones can not. If you want to know user A did something, make them sign it before moving forward. Then if a dispute arises, check the signature.

You can sign using web3.eth.sign and validate signatures in solidity using ecrecover, as said by Rob Hitchens in this question. This is also explained in depht in the linked blog post.