REST 身份验证和公开 API 密钥

我一直在阅读关于 REST 的文章,有很多关于它的问题,还有很多其他的网站和博客。虽然我从来没有见过这个具体的问题问... ... 出于某种原因,我不能把我的头脑,这个概念..。

如果我正在构建一个 RESTful API,并且希望保护它,那么我看到的一种方法是使用安全令牌。当我使用其他 API 时,会有一个令牌和一个共享的秘密... 有意义。我不明白的是,对休息服务操作的请求是通过 javascript (XHR/Ajax)发出的,有什么能阻止人们通过简单的东西如 FireBug (或浏览器中的“查看源代码”)来嗅出这一点,然后复制 API 密钥,然后使用密钥和机密来模拟那个人呢?

76752 次浏览

I supose you mean session key not API key. That problem is inherited from the http protocol and known as Session hijacking. The normal "workaround" is, as on any web site, to change to https.

To run the REST service secure you must enable https, and probably client authentification. But after all, this is beyond the REST idea. REST never talks about security.

api secret is not passed explicitly, secret is used to generate a sign of current request, at the server side, the server generate the sign following the same process, if the two sign matches, then the request is authenticated successfully -- so only the sign is passed through the request, not the secret.

We're exposing an API that partners can only use on domains that they have registered with us. Its content is partly public (but preferably only to be shown on the domains we know), but is mostly private to our users. So:

  • To determine what is shown, our user must be logged in with us, but this is handled separately.

  • To determine where the data is shown, a public API key is used to limit access to domains we know, and above all to ensure the private user data is not vulnerable to CSRF.

This API key is indeed visible to anyone, we do not authenticate our partner in any other way, and we don't need REFERER. Still, it is secure:

  1. When our get-csrf-token.js?apiKey=abc123 is requested:

  2. Look up the key abc123 in the database and get a list of valid domains for that key.

  3. Look for the CSRF validation cookie. If it does not exist, generate a secure random value and put it in a HTTP-only session cookie. If the cookie did exist, get the existing random value.

  4. Create a CSRF token from the API key and the random value from the cookie, and sign it. (Rather than keeping a list of tokens on the server, we're signing the values. Both values will be readable in the signed token, that's fine.)

  5. Set the response to not be cached, add the cookie, and return a script like:

    var apiConfig = apiConfig || {};
    if(document.domain === 'example.com'
    || document.domain === 'www.example.com') {
    
    
    apiConfig.csrfToken = 'API key, random value, signature';
    
    
    // Invoke a callback if the partner wants us to
    if(typeof apiConfig.fnInit !== 'undefined') {
    apiConfig.fnInit();
    }
    } else {
    alert('This site is not authorised for this API key.');
    }
    

    Notes:

    • The above does not prevent a server side script from faking a request, but only ensures that the domain matches if requested by a browser.

    • The same origin policy for JavaScript ensures that a browser cannot use XHR (Ajax) to load and then inspect the JavaScript source. Instead, a regular browser can only load it using <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123"> (or a dynamic equivalent), and will then run the code. Of course, your server should not support Cross-Origin Resource Sharing nor JSONP for the generated JavaScript.

    • A browser script can change the value of document.domain before loading the above script. But the same origin policy only allows for shortening the domain by removing prefixes, like rewriting subdomain.example.com to just example.com, or myblog.wordpress.com to wordpress.com, or in some browsers even bbc.co.uk to co.uk.

    • If the JavaScript file is fetched using some server side script then the server will also get the cookie. However, a third party server cannot make a user’s browser associate that cookie to our domain. Hence, a CSRF token and validation cookie that have been fetched using a server side script, can only be used by subsequent server side calls, not in a browser. However, such server side calls will never include the user cookie, and hence can only fetch public data. This is the same data a server side script could scrape from the partner's website directly.

  6. When a user logs in, set some user cookie in whatever way you like. (The user might already have logged in before the JavaScript was requested.)

  7. All subsequent API requests to the server (including GET and JSONP requests) must include the CSRF token, the CSRF validation cookie, and (if logged on) the user cookie. The server can now determine if the request is to be trusted:

    1. The presence of a valid CSRF token ensures the JavaScript was loaded from the expected domain, if loaded by a browser.

    2. The presence of the CSRF token without the validation cookie indicates forgery.

    3. The presence of both the CSRF token and the CSRF validation cookie does not ensure anything: this could either be a forged server side request, or a valid request from a browser. (It could not be a request from a browser made from an unsupported domain.)

    4. The presence of the user cookie ensures the user is logged on, but does not ensure the user is a member of the given partner, nor that the user is viewing the correct website.

    5. The presence of the user cookie without the CSRF validation cookie indicates forgery.

    6. The presence of the user cookie ensures the current request is made through a browser. (Assuming a user would not enter their credentials on an unknown website, and assuming we don’t care about users using their own credentials to make some server side request.) If we also have the CSRF validation cookie, then that CSRF validation cookie was also received using a browser. Next, if we also have a CSRF token with a valid signature, and the random number in the CSRF validation cookie matches the one in that CSRF token, then the JavaScript for that token was also received during that very same earlier request during which the CSRF cookie was set, hence also using a browser. This then also implies the above JavaScript code was executed before the token was set, and that at that time the domain was valid for the given API key.

      So: the server can now safely use the API key from the signed token.

    7. If at any point the server does not trust the request, then a 403 Forbidden is returned. The widget can respond to that by showing a warning to the user.

It's not required to sign the CSRF validation cookie, as we're comparing it to the signed CSRF token. Not signing the cookie makes each HTTP request shorter, and the server validation a bit faster.

The generated CSRF token is valid indefinitely, but only in combination with the validation cookie, so effectively until the browser is closed.

We could limit the lifetime of the token's signature. We could delete the CSRF validation cookie when the user logs out, to meet the OWASP recommendation. And to not share the per-user random number between multiple partners, one could add the API key to the cookie name. But even then one cannot easily refresh the CSRF validation cookie when a new token is requested, as users might be browsing the same site in multiple windows, sharing a single cookie (which, when refreshing, would be updated in all windows, after which the JavaScript token in the other windows would no longer match that single cookie).

For those who use OAuth, see also OAuth and Client-Side Widgets, from which I got the JavaScript idea. For server side use of the API, in which we cannot rely on the JavaScript code to limit the domain, we're using secret keys instead of the public API keys.

What you want to do on the server side is generate an expiring session id that is sent back to the client on login or signup. The client can then use that session id as a shared secret to sign subsequent requests.

The session id is only passed once and this MUST be over SSL.

See example here

Use a nonce and timestamp when signing the request to prevent session hijacking.

This question has an accepted answer but just to clarify, shared secret authentication works like this:

  1. Client has public key, this can be shared with anyone, doesn't matter, so you can embed it in javascript. This is used to identify the user on the server.
  2. Server has secret key and this secret MUST be protected. Therefore, shared key authentication requires that you can protect your secret key. So a public javascript client that connects directly to another service is not possible because you need a server middleman to protect the secret.
  3. Server signs request using some algorithm that includes the secret key (the secret key is sort of like a salt) and preferably a timestamp then sends the request to the service. The timestamp is to prevent "replay" attacks. A signature of a request is only valid for around n seconds. You can check that on the server by getting the timestamp header that should contain the value of the timestamp that was included in the signature. If that timestamp is expired, the request fails.
  4. The service gets the request which contains not only the signature but also all the fields that were signed in plain text.
  5. The service then signs the request in the same way using the shared secret key and compares the signatures.

I will try to answer the the question in it's original context. So question is "Is the secret (API) key safe to be placed with in JavaScript.

In my opinion it is very unsafe as it defeats the purpose of authentication between the systems. Since the key will be exposed to the user, user may retrieve information he/she is not authorized to. Because in a typical rest communication authentication is only based on the API Key.

A solution in my opinion is that the JavaScript call essentially pass the request to an internal server component who is responsible from making a rest call. The internal server component let's say a Servlet will read the API key from a secured source such as permission based file system, insert into the HTTP header and make the external rest call.

I hope this helps.