272

Problem:
We have a Spring MVC-based RESTful API which contains sensitive information. The API should be secured, however sending the user's credentials (user/pass combo) with each request is not desirable. Per REST guidelines (and internal business requirements), the server must remain stateless. The API will be consumed by another server in a mashup-style approach.

Requirements:

  • Client makes a request to .../authenticate (unprotected URL) with credentials; server returns a secure token which contains enough information for the server to validate future requests and remain stateless. This would likely consist of the same information as Spring Security's Remember-Me Token.

  • Client makes subsequent requests to various (protected) URLs, appending the previously obtained token as a query parameter (or, less desirably, an HTTP request header).

  • Client cannot be expected to store cookies.

  • Since we use Spring already, the solution should make use of Spring Security.

We've been banging our heads against the wall trying to make this work, so hopefully someone out there has already solved this problem.

Given the above scenario, how might you solve this particular need?

PraveenKumar Lalasangi
  • 3,255
  • 1
  • 23
  • 47
Chris Cashwell
  • 22,308
  • 13
  • 63
  • 94
  • 50
    Hi Chris, I'm not sure passing that token in the query parameter is the best idea. That will show up in logs, regardless of HTTPS or HTTP. The headers are probably safer. Just FYI. Great question though. +1 – jamesmortensen May 31 '12 at 01:19
  • 1
    What is your understanding of stateless? Your token requirement collides with my understanding of stateless. The Http authentication answer seems to me the only stateless implementation. – Markus Malkusch Jan 03 '14 at 17:33
  • 9
    @MarkusMalkusch stateless refers to the server's knowledge of prior communications with a given client. HTTP is stateless by definition, and session cookies make it stateful. The lifetime (and source, for that matter) of the token are irrelevant; the server only cares that it's valid and can be tied back to a user (NOT a session). Passing an identifying token, therefore, does not interfere with statefulness. – Chris Cashwell Jan 03 '14 at 17:40
  • 1
    @ChrisCashwell How do you ensure that the token is not being spoofed/generated by the client? Do you use a private key on the server-side to encrypt the token, provide it to the client, and then use the same key to decrypt it during future requests? Obviously Base64 or some other obfuscation would not be enough. Can you elaborate on techniques for the "validation" of these tokens? – Craig Otis Oct 01 '14 at 18:06
  • @CraigOtis one part is plaintext, and one is an md5 hash of that data and a private key on the server. (Something like `email` + delimiter + `expiration_in_ms` + delimiter + `md5(email+expiration+private_key)`) The plaintext portion of the token can be modified by the user, however the token would be invalid because hashing the modified data will result in a different signature than the second half of the token. The trick is to *not* encrypt the token; you don't need to be able to reverse it, you just need to verify the integrity. MD5 the plaintext data and compare the signature for equality. – Chris Cashwell Dec 12 '14 at 20:18
  • 6
    Although this is dated and I haven't touched or updated the code in over 2 years, I have created a Gist to further expand on these concepts. https://gist.github.com/ccashwell/dfc05dd8bd1a75d189d1 – Chris Cashwell Dec 12 '14 at 20:48
  • 1
    @ChrisCashwell - Would really help me if you could add the web.xml, root-context.xml and servlet-context.xml files to the gist too. – kapad Jul 20 '15 at 14:46
  • @ChrisCashwell I've also same use case as yours, I tried to implement on springboot but its not working. So if you provide the spring boot base configuration then it would be very useful. – Dhiren Hamal Dec 25 '16 at 18:48

4 Answers4

195

We managed to get this working exactly as described in the OP, and hopefully someone else can make use of the solution. Here's what we did:

Set up the security context like so:

<security:http realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="CustomAuthenticationEntryPoint">
    <security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
    <security:intercept-url pattern="/authenticate" access="permitAll"/>
    <security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>

<bean id="CustomAuthenticationEntryPoint"
    class="com.demo.api.support.spring.CustomAuthenticationEntryPoint" />

<bean id="authenticationTokenProcessingFilter"
    class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter" >
    <constructor-arg ref="authenticationManager" />
</bean>

As you can see, we've created a custom AuthenticationEntryPoint, which basically just returns a 401 Unauthorized if the request wasn't authenticated in the filter chain by our AuthenticationTokenProcessingFilter.

CustomAuthenticationEntryPoint:

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Authentication token was either missing or invalid." );
    }
}

AuthenticationTokenProcessingFilter:

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {

    @Autowired UserService userService;
    @Autowired TokenUtils tokenUtils;
    AuthenticationManager authManager;
    
    public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
        this.authManager = authManager;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        @SuppressWarnings("unchecked")
        Map<String, String[]> parms = request.getParameterMap();

        if(parms.containsKey("token")) {
            String token = parms.get("token")[0]; // grab the first "token" parameter
            
            // validate the token
            if (tokenUtils.validate(token)) {
                // determine the user based on the (already validated) token
                UserDetails userDetails = tokenUtils.getUserFromToken(token);
                // build an Authentication object with the user's info
                UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
                // set the authentication into the SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));         
            }
        }
        // continue thru the filter chain
        chain.doFilter(request, response);
    }
}

Obviously, TokenUtils contains some privy (and very case-specific) code and can't be readily shared. Here's its interface:

public interface TokenUtils {
    String getToken(UserDetails userDetails);
    String getToken(UserDetails userDetails, Long expiration);
    boolean validate(String token);
    UserDetails getUserFromToken(String token);
}

That ought to get you off to a good start.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Chris Cashwell
  • 22,308
  • 13
  • 63
  • 94
  • Is it necessary to authenticate the token when the token is sending with the request. How about get the username info directly and set in the current context/request? – Fisher Sep 26 '12 at 10:27
  • My feeling is that it's probably best to do the authentication as it enables you to use the other spring security features throughout the rest of your app when making REST requests. The @Sercure annotation is one example that comes to mind. – threejeez Dec 17 '12 at 05:55
  • @Miles we were using Spring's security features throughout the app, just not with the `@Secured` annotation. Instead, we had a security context that set up the role permissions. – Chris Cashwell Dec 18 '12 at 16:33
  • Right, either way, you're taking advantage of Spring Security features which is why it's beneficial to use this approach. I was pointing that out in response to @Fisher. – threejeez Dec 18 '12 at 22:35
  • @Chris Cashwell Hi Chris, when you create a token in server where do you keep them, in a database or in a bean? also I use Jee6 but not spring, can I still use your approach? tnx – Spring Dec 20 '12 at 23:34
  • 1
    @Spring I don't store them anywhere... the whole idea of the token is that it needs to be passed with every request, and it can be deconstructed (partially) to determine its validity (hence the `validate(...)` method). This is important because I want the server to remain stateless. I would imagine you could use this approach without needing to use Spring. – Chris Cashwell Jan 02 '13 at 17:02
  • @ChrisCashwell `SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));` the subsequent request will be `Authenticated` into Spring Security -- if I add `GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole());` and than use the `@Secured("ROLE_USER")` annotation on a controller method, shouldn't spring security detect current authorization and apply it and enforce the `@Secured` rule? Or is the that context discarded after? – jordan.baucke Jan 11 '13 at 21:02
  • NM figured it out: You can use `@Secured` the subsequent request will be `Authenticated` into Spring Security - I just did `UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), *userDetails.getAuthorities()*);` and assigned and authority role to my userdetails. Cheers I appreciate the solution! – jordan.baucke Jan 11 '13 at 21:52
  • Also which I am surprised @ChrisCashwell did not mention or need is the need to switch entry points based on the request: `org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint` . You'll need this if you have other complex authentication schemes like OAuth. – Adam Gent Jan 23 '13 at 20:01
  • @ChrisCashwell - I'm looking to implement something very similar to this. Excellent thought put into the answer. Did you read any tutorials or docs to get this info? What do you mean by the "OP"? I'm basically looking for links to help me get a full understanding of how it all hangs together. – Thomas Buckley Feb 19 '13 at 20:44
  • @ThomasBuckley - "OP" means "Original Post", so I was referring to the question. I can't say exactly what I read, just that I did a lot of looking at filter chain stuff for custom authentication methods. Hope this helps you out one way or another. – Chris Cashwell Feb 20 '13 at 13:57
  • 1
    If the client is a browser, how can the token be stored? or do you have to redo authentication for each request? – beginner_ Mar 12 '13 at 13:55
  • Could you please read my question it's common with your solution http://stackoverflow.com/questions/18507816/spring-secutity-and-multipart – Sergey Aug 29 '13 at 12:55
  • Hi chriss, I stdied you solution but I think its wrong (Hope Im mistaken). Because the passwords have to be stored at least in MD5 and when you do new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword()); The password obtained is in MD5 so will never pass the security filter. how do you solve this??? – IturPablo Sep 30 '13 at 22:43
  • @IturPablo I'm really agree with you. The approach is OK but it doesn't work with md5 encoded passwords. If you have the passwords stored in MD5 (obiously) and in appContext-security.xml you have your with password encoded md5 is not possible to authenticate users. The only thing that it's possible to do is to implement your own auth manager because in the xml is not possible to define two (one md5 and one not). – mannuk Nov 22 '13 at 12:38
  • @IturPablo @mannuk — I disagree... Our passwords are also MD5 encoded, but we don't actually *care* about their password when doing token-based authentications. We're simply creating a `UserAuthentication` object and setting it on the `SecurityContext`, effectively bypassing the password authentication. That's the whole purpose of having an `AuthenticationTokenProcessingFilter`. `TokenUtils` is responsible for validating tokens and determining the right `User`. – Chris Cashwell Jan 03 '14 at 17:51
  • 2
    great tips. @ChrisCashwell - the part that I can't find is where do you validate the user credentials and send back a token ? I would guess it should be somewhere in the impl of the /authenticate end point. am I right ? If not what is the goal of /authenticate ? – Yonatan Maman Jan 14 '14 at 14:21
  • Hi ChrisCashwell and @YonatanMaman , can you please help provide me the part where you validate the user credentials and send back the token. what is the related entry in secutiry.xml file and in java code for that ? – sunny_dev May 06 '14 at 11:05
  • I'm trying this solution next to our normal session based security for end users. Getting No unique bean of type [org.springframework.security.web.context.SecurityContextRepository] is defined: expected single matching bean but found 2: [org.springframework.security.web.context.NullSecurityContextRepository#0, org.springframework.security.web.context.HttpSessionSecurityContextRepository#0] – Marc Sep 18 '14 at 15:33
  • god, @ChrisCashwell your answer helped me so much I cant express that.. THANKS! HUGE beer (or sprite if you are not used to dringing) for you :P – azalut Dec 10 '14 at 20:46
  • @ChrisCashwell, You are AWESOME. I searched all over the internet to figure out how to handle this and no luck. Thanks to your solution, I finally got mine working. – Bashir Jan 09 '15 at 05:22
  • Hi @ChrisCashwell, is that a right assumption that we need to implement a service mapped to something like /authenticate which is just receiving the username/pass combo and generating the token? Or do I need to do the authentication cycle manually at this point? It is not clear where is the authentication cycle executed? – balas Jan 28 '15 at 10:17
  • Hi @ChrisCashwell, I can't see how to implement the tokenUtils.getUserFromToken(...) as I expect the password to be hashed so it is not recoverable? – balas Jan 28 '15 at 12:41
  • @balas that's correct, you're not going to recover any form of password. The `getUserFromToken` function validates the signature, then uses the user ID to retrieve the user. Since the ID is found in plaintext in the token and secured from manipulation by the signature, this is all we need. We know that if we run the plaintext portion of the user-provided token through our server-side hashing algorithm and compare to the full token we should get an exact match. If so, we've successfully authenticated the user. If not, the token was manipulated and is therefore invalid. Make sense? – Chris Cashwell Jan 28 '15 at 16:23
  • I'd also like to point out that this answer was posted three years ago and there are undoubtedly better and/or smarter ways to do this now. – Chris Cashwell Jan 28 '15 at 16:25
  • @ChrisCashwell - so password is not recoverable from the token. This was my intention as well, thanks for the confirmation. – balas Jan 28 '15 at 22:00
  • @ChrisCashwell - Can you please elaborate on how token is sent back to the user in response to authenticate api call? As I understand we implement CustomAuthenticationManager and User Details Service to ensure user credentials are correct initially but in what component we should generate the Json response which contains token for the user – Shailesh Vaishampayan Feb 25 '15 at 17:27
  • @ChrisCashwell If you are not storing tokens for each user how do you identify a user who has logged out before his token expires? – Shailesh Vaishampayan Mar 02 '15 at 16:40
  • Hi @ChrisCashwell ..Thanks for this solution. Can you point to other better/smarter solutions which you mentioned in one of your comments? – Anmol Gupta Aug 14 '15 at 17:03
  • after you read the answer above of @Chris Cashwell check out http://www.javacodegeeks.com/2014/10/stateless-spring-security-part-2-stateless-authentication.html – oak Sep 11 '15 at 08:05
  • 3
    what is inside the AuthenticationManager ? – MoienGK Sep 25 '15 at 08:17
  • please please pleeassee share more about how TokenUtils works – secondbreakfast Aug 18 '17 at 17:05
28

You might consider Digest Access Authentication. Essentially the protocol is as follows:

  1. Request is made from client
  2. Server responds with a unique nonce string
  3. Client supplies a username and password (and some other values) md5 hashed with the nonce; this hash is known as HA1
  4. Server is then able to verify client's identity and serve up the requested materials
  5. Communication with the nonce can continue until the server supplies a new nonce (a counter is used to eliminate replay attacks)

All of this communication is made through headers, which, as jmort253 points out, is generally more secure than communicating sensitive material in the url parameters.

Digest Access Authentication is supported by Spring Security. Notice that, although the docs say that you must have access to your client's plain-text password, you can successfully authenticate if you have the HA1 hash for your client.

Community
  • 1
  • 1
Tim Pote
  • 27,191
  • 6
  • 63
  • 65
  • 1
    While this is a possible approach, the several round trips that must be made in order to retrieve a token makes it a little undesirable. – Chris Cashwell May 31 '12 at 13:33
  • If your client follows the HTTP Authentication specification those round trips happen only upon the first call and when 5. happens. – Markus Malkusch Jan 03 '14 at 17:27
7

Regarding tokens carrying information, JSON Web Tokens (http://jwt.io) is a brilliant technology. The main concept is to embed information elements (claims) into the token, and then signing the whole token so that the validating end can verify that the claims are indeed trustworthy.

I use this Java implementation: https://bitbucket.org/b_c/jose4j/wiki/Home

There is also a Spring module (spring-security-jwt), but I haven't looked into what it supports.

Leif John
  • 778
  • 6
  • 21
3

Why don't you start using OAuth with JSON WebTokens

http://projects.spring.io/spring-security-oauth/

OAuth2 is an standardized authorization protocol/framework. As per Official OAuth2 Specification:

You can find more info here

Community
  • 1
  • 1
vaquar khan
  • 10,864
  • 5
  • 72
  • 96
  • 2
    The Spring Security OAuth project is deprecated. The latest OAuth 2.0 support is provided by Spring Security. See the OAuth 2.0 Migration Guide for further details. – ACV Sep 04 '20 at 12:26