问题
I'm implementing a set of RESTful services for some developments and one of these is an authentication service.
This authentication service authenticates two kinds of identities:
- Applications. AppKey-based authentication so clients must register for a key in order to access to the rest of the services.
- Users. Well-known credentials (user+password)-based user authentication so humans and machines can work with these RESTful services through client applications.
These RESTful services are stateless.
When a client application authenticates against the authentication service, or when a human or machine authenticates as an identity using credentials, both operations generates an AppToken and UserToken respectively.
These tokens are a salted hash so subsequent requests to the RESTful infrastructure will be authenticated without sharing AppKeys and credentials.
Form the point of view of a fully stateless approach, these tokens should be stored no where in the service layer but in some kind of client-side state (f.e., a Web client would store it using HTTP cookies). This is how my current implementations are working right now.
Because re-authenticating each request using these tokens and let the service layer receive the token coming from the client so it can compare what token comes from the client and check if it's a valid token re-generating it in the service layer and compare with the one owned by the client is too expensive, I've implemented a service layer AppToken and UserToken, both having an expiration date and an owner (the application or user for which the token have been created for), in order to check if the token coming from the client exists in the token store.
How does clients interactively unauthenticate? Just dropping client-side security state. If it's a Web client, it drops the authentication cookie and just refreshing the page, client detects no authentication cookie and user is redirected to the login page.
From the point of view of RESTful services, this is a stateless unauthentication: clients aren't aware about the trick of having a service layer pseudo-authentication state. It's just a service implementation detail - a performance optimization -.
I'm not going to list the pros of stateless services because I'm absolutely sure that this approach is the way to go, but I find a problem: stateless authentication/unauthentication means that clients don't notify server that they close their session, so the security store ends with a lot of useless records.
This isn't a great problem if service clients are ones that would have limited time sessions (f.e., 1 hour, 3 hours, a day...), but what happens if an user must be authenticated forever (8 months, a year)?. How do you distinguish what's an an expired token?
There're some approaches in order to solve this situation:
Whenever the service layer receives a request, it updates token expiration date, so an automated process may drop those tokens that have expired defining an arbitrary expiration of tokens (f.e. 24 hours).
Compromise stateless nature of the architecture and let clients notify service layer that they don't want to be authenticated anymore, so service can drop the associated token to the client session (But wait... what happens if client closes a Web client? User will never actively notify service that the token must be dropped... So... Zombie tokens are there yet, so an automated process should drop them, but... what's a zombie token? I don't like this approach).
Completely stateless authentication, no store, per-request authentication.
This is the question! What's your suggested approach - even if it's not 1., 2. or 3. - and why?
Thanks for this long reading - I honestly believe that question's conclusions are going to be extremely useful to anyone -!
回答1:
Number three
Stateless authentication, token-based. Assuming transport-level encryption.
[X]SS
-- X signed by S's public key[X|Y]
-- X and Y in same envelopeY [M]SY -> S
-- Y sends signed message M to S.
Aim: Client C wishes to speak to service S.
Client C sends its shared secret or public key
PKC
for authentication to service A, for which C knows the endpoint and public key (PKA
).A [now + interval | user-id or PKC]SA -> C
Explained:
Service A adds an interval to the current date/time as an expiration. In the buffer-to-be-sent is now the expiry date and the user id,
PKC
(assuming you have a valid Identity Provider).[now + interval | user-id or PKC] = T
A signs it;
[T]SA
Client C wishes to talk to backend service S.
C [[M|[T]SA]SC -> S
C sends message M plus the token it got signed from A, to service S.
S cares that C really did send it and verifies C's signature
SC
that it reads from the envelope.S verifies signature
SA
of token. Failure means request is denied.S verifies token
[T]SA
: user-id/PKC
correct and token date >= now. Expired token means to send 'token expired' message to client C. Permission denied if token has faulty signature.(Optional; S authorizes C, digression)
S performs work and sends back
[M2]SS
to client C.
This wouldn't be too much overhead; verifying the signature is a pretty fast operation.
Certificates
The question 'C# Sign Data with RSA using Bouncy Castle' shows how you sign and verify a piece of string, the message that you are sending.
You'll need the certificates; if you are using a configuration manager (YOU SHOULD BE DOING THAT! ;)), like puppet, then you create a certificate signing request (CSR) and then sign it using puppet.
Post Scriptum on unauthentication in particular
There's something called a certificate revocation request, which basically is a laundry list of public keys that have been withdrawn and are not to be trusted/used. Place PKC
there and broadcast the revocation, and it basically acts by requiring the client to do another certificate signing request round.
Also, if you want the ability to expire specific tokens, add a unique ID (UUID/GUID) to the token T
when you create it, and have a token revocation list, similarly broadcasted when changed, that you purge the token UUIDs from when they expire. So a service would then also check the token revocation list if the received T is in it.
Hash-based tokens
Have a look at the software giants are doing. E.g. Amazon's REST interface, that uses shared secret keys:
The Amazon S3 REST API uses a custom HTTP scheme based on a keyed-HMAC (Hash Message Authentication Code) for authentication. To authenticate a request, you first concatenate selected elements of the request to form a string. You then use your AWS Secret Access Key to calculate the HMAC of that string. Informally, we call this process "signing the request," and we call the output of the HMAC algorithm the "signature" because it simulates the security properties of a real signature. Finally, you add this signature as a parameter of the request, using the syntax described in this section.
Read more on Amazon's scheme.
Subversion/Attack Vectors on the Above
- The scheme I described initially requires SSL, which is vulnerable to the numerous certificate authorities out there, as well as a number of other things.
- You are left vulnerable to replay attacks, i.e. a man in the middle re-sending a message. If your REST interface is idempotent you are safe. You are also safe if you add a server-known cryptographic nonce to the requests.
回答2:
Chosen approach: COMPLETELY STATELESS AUTHENTICATION AND UNAUTHENTICATION
Finally, I got a conclusion and a protocol in order to switch to the whole completely stateless token-based authentication and unauthentication.
How to achieve it?
First of all, this is what you need to have stateless token-based authentication for applications (but user authentication would work in the same way, excluding this inventory):
- An application registration system. An application is an access to your services. It's "your application accessing some services on the net (intranet, internet, cloud...). This is creating application keys (skip this for user authentication).
- A server certificate so client to service connections are encrypted by using HTTPS/SSL.
This is the flow of authenticating an application:
Client sends an authentication request to the authentication service. This request must include the Application Key (AppKey).
Authentication service receives the previously sent request.
Now authentication service creates an application token (AppToken), which is a self-describing concatenation of the necessary information to track a concrete authenticated client to the services relying on authentication service.
AppToken is a compound string (this composition can be an object serialized using JSON) of:
- An application hash (*a SHA - or other - which is the result of concatenate some application info. This is info will be a service secret + Expiration date (which is part of the token itself). Why Expiration date?. Imagine that a man in the middle or something can break security and modify token's expiration? When encrypted token gets decrypted in order to authenticate a request, the result of hashing again the expiration date + AppKey will no longer produce the same hash, so token gets invalidated.
- Issued date. Current UTC Date+Time when creating the token.
- Expiration date. An UTC DateT+Time on which the token will be no longer valid.
Authentication service encrypts step #4 result (the JSON-serialized object). **Use AppKey as the key or password for a symmetric cipher. In my case, I'll use Rijndael for that.
Subsequent request will include this token in order to avoid sending plain text credentials. Those request will always include the AppKey too, so authentication service will be able of identify what application is trying to authenticate the request.
After some time, a token becomes expired or invalid, and client requests for a new AppToken. Or the client was closed by the user and there's no persistent storage that would save security tokens, so next client session will request new ones when needed.
Some hints and details about .NET implementation of such authentication method:
I've used
System.Security.Cryptography.RijndaelManaged
class for symmetric encryption. Both AppKey and AppToken (and in case of token-based user authentication it's almost the same solution) are generated usingRijndaelManaged
class.Encrypted text is converted to an HEX string. This is sent with the authentication response. In our case (a RESTFul API), the HEX string representing the AppToken will be sent as a response header. Whenever a request includes this HEX string, authentication process will reconvert it to the original encrypted text, and later it'll get decrypted in order to evaluate if the token is valid.
Thanks Henrik for your effort. I've taken some of concepts in your own answer and I've mixed them with my own conclusions.
来源:https://stackoverflow.com/questions/12174393/restful-authentication-client-side-stateless-unauthentication