如何使用 Elixir 或 Erlang 创建一个 Google 签名的 URL 云存储?
How to create a Google Cloud Storage Signed URL using Elixir or Erlang?
我目前正在尝试让我的 elixir 网络服务器为 Google 云存储生成签名 url,以便我可以生成过期的文件 url。
不幸的是,当我尝试使用生成的网址时,出现以下错误:
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
</Message>
我可以通过 gsutil 工具生成签名的 url,尽管它很慢,也可以通过这里给出的 python 示例:
Google Cloud Storage Signed URLs Example
我目前在 Elixir 中的实现基于上面的 Python 示例,看起来像这样:
@default_expiration 1000
def construct_string(http_verb, content_md5, content_type, expiration_timestamp, canonicalized_extension_headers, canonicalized_resource) do
"#{http_verb}\n
#{content_md5}\n
#{content_type}\n
#{expiration_timestamp}\n
#{canonicalized_extension_headers}
#{canonicalized_resource}"
end
def load_secret_pem do
load_local_key_file("/path/to/key")
end
def load_local_key_file(path) do
{ok, pem_bin} = File.read(path)
[rsa_entry] = :public_key.pem_decode(pem_bin)
key = :public_key.pem_entry_decode(rsa_entry)
end
def base64Sign(plaintext) do
key = load_secret_pem()
signature_bytes = :public_key.sign(plaintext, :sha256, key )
Base.url_encode64(signature_bytes)
|> String.replace("-", "%2B")
|> String.replace("_", "%2F")
|> URI.encode_www_form
end
def make_url(verb, path, content_md5 \ "", content_type \ "") do
client_id = GCloud.Credentials.client_email() |> URI.encode_www_form
expiration = :os.system_time(:seconds) + @default_expiration
base_url = GCloud.Storage.base_uri() <> path
signature_string = construct_string(verb, content_md5, content_type, expiration, "", path )
url_encoded_signature = base64Sign(signature_string)
IO.puts "#{base_url}?GoogleAccessId=#{client_id}&Expires=#{expiration}&Signature=#{url_encoded_signature}"
end
如何使用 Elixir 或 Erlang 对已签名的 url 进行正确签名?
您在 construct_string
中的字符串构造可能在做您没有意识到的事情。记得Python语法不一样,对空格有其他看法。
defmodule Test do
def foo(a,b) do
"#{a}\n
#{b}"
end
end
IO.inspect Test.foo(1,2)
# output:
"1\n\n 2"
如果您改用带有 """
的 heredoc,前导空格会消失,但您的换行符仍然会重复。但是,这种方法可能不是一个好主意,因为如果您从 windows 机器上保存文件,您可能会以 \r\n
作为编辑器的结尾行,而删除这些是不必要的麻烦无论如何。
相反,我认为您应该将此处的方法更改为如下所示:
def construct_string(http_verb, content_md5, content_type, expiration_timestamp, canonicalized_extension_headers, canonicalized_resource) do
headers = Enum.join([http_verb, content_md5, content_type, expiration_timestamp], "\n")
"#{headers}\n#{canonicalized_extension_headers}#{canonicalized_resource}"
end
我不确定是否还有其他错误,但我立即注意到了这一点。
我设法让它工作,我通过并排打开 python 和 elixir REPL 来做到这一点,用测试字符串执行两个步骤中的每个步骤并比较输出的差异,那里没有差异在对测试字符串进行哈希或签名之后,但是有base64编码之后,所以我改变了:
def base64Sign(plaintext) do
key = load_secret_pem()
signature_bytes = :public_key.sign(plaintext, :sha256, key )
Base.url_encode64(signature_bytes)
|> String.replace("-", "%2B")
|> String.replace("_", "%2F")
|> URI.encode_www_form
end
至
def base64Sign(plaintext) do
key = GCloud.Credentials.load_secret_pem()
signature_bytes = :public_key.sign(plaintext, :sha256, key )
Base.encode64(signature_bytes)
|> URI.encode_www_form
end
结合 解决了这个问题。
使用 gcs_signer 来自 hex.pm:
Application.get_env(:goth, :json)
|> Poison.decode!
|> GcsSigner.Client.from_keyfile()
|> GcsSigner.sign_url(bucket, object)
在Google Cloud Platform 环境中,例如 Cloud Functions 和 App
引擎,您通常不提供 keyFilename
或 credentials
实例化,
那么你可以使用 signBlob api see this
我目前正在尝试让我的 elixir 网络服务器为 Google 云存储生成签名 url,以便我可以生成过期的文件 url。 不幸的是,当我尝试使用生成的网址时,出现以下错误:
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
</Message>
我可以通过 gsutil 工具生成签名的 url,尽管它很慢,也可以通过这里给出的 python 示例:
Google Cloud Storage Signed URLs Example
我目前在 Elixir 中的实现基于上面的 Python 示例,看起来像这样:
@default_expiration 1000
def construct_string(http_verb, content_md5, content_type, expiration_timestamp, canonicalized_extension_headers, canonicalized_resource) do
"#{http_verb}\n
#{content_md5}\n
#{content_type}\n
#{expiration_timestamp}\n
#{canonicalized_extension_headers}
#{canonicalized_resource}"
end
def load_secret_pem do
load_local_key_file("/path/to/key")
end
def load_local_key_file(path) do
{ok, pem_bin} = File.read(path)
[rsa_entry] = :public_key.pem_decode(pem_bin)
key = :public_key.pem_entry_decode(rsa_entry)
end
def base64Sign(plaintext) do
key = load_secret_pem()
signature_bytes = :public_key.sign(plaintext, :sha256, key )
Base.url_encode64(signature_bytes)
|> String.replace("-", "%2B")
|> String.replace("_", "%2F")
|> URI.encode_www_form
end
def make_url(verb, path, content_md5 \ "", content_type \ "") do
client_id = GCloud.Credentials.client_email() |> URI.encode_www_form
expiration = :os.system_time(:seconds) + @default_expiration
base_url = GCloud.Storage.base_uri() <> path
signature_string = construct_string(verb, content_md5, content_type, expiration, "", path )
url_encoded_signature = base64Sign(signature_string)
IO.puts "#{base_url}?GoogleAccessId=#{client_id}&Expires=#{expiration}&Signature=#{url_encoded_signature}"
end
如何使用 Elixir 或 Erlang 对已签名的 url 进行正确签名?
您在 construct_string
中的字符串构造可能在做您没有意识到的事情。记得Python语法不一样,对空格有其他看法。
defmodule Test do
def foo(a,b) do
"#{a}\n
#{b}"
end
end
IO.inspect Test.foo(1,2)
# output:
"1\n\n 2"
如果您改用带有 """
的 heredoc,前导空格会消失,但您的换行符仍然会重复。但是,这种方法可能不是一个好主意,因为如果您从 windows 机器上保存文件,您可能会以 \r\n
作为编辑器的结尾行,而删除这些是不必要的麻烦无论如何。
相反,我认为您应该将此处的方法更改为如下所示:
def construct_string(http_verb, content_md5, content_type, expiration_timestamp, canonicalized_extension_headers, canonicalized_resource) do
headers = Enum.join([http_verb, content_md5, content_type, expiration_timestamp], "\n")
"#{headers}\n#{canonicalized_extension_headers}#{canonicalized_resource}"
end
我不确定是否还有其他错误,但我立即注意到了这一点。
我设法让它工作,我通过并排打开 python 和 elixir REPL 来做到这一点,用测试字符串执行两个步骤中的每个步骤并比较输出的差异,那里没有差异在对测试字符串进行哈希或签名之后,但是有base64编码之后,所以我改变了:
def base64Sign(plaintext) do
key = load_secret_pem()
signature_bytes = :public_key.sign(plaintext, :sha256, key )
Base.url_encode64(signature_bytes)
|> String.replace("-", "%2B")
|> String.replace("_", "%2F")
|> URI.encode_www_form
end
至
def base64Sign(plaintext) do
key = GCloud.Credentials.load_secret_pem()
signature_bytes = :public_key.sign(plaintext, :sha256, key )
Base.encode64(signature_bytes)
|> URI.encode_www_form
end
结合
使用 gcs_signer 来自 hex.pm:
Application.get_env(:goth, :json)
|> Poison.decode!
|> GcsSigner.Client.from_keyfile()
|> GcsSigner.sign_url(bucket, object)
在Google Cloud Platform 环境中,例如 Cloud Functions 和 App
引擎,您通常不提供 keyFilename
或 credentials
实例化,
那么你可以使用 signBlob api see this