Spring Security - Token based API auth & user/password authentication

57,202

I believe the error that you mention is just because the AbstractAuthenticationProcessingFilter base class that you are using requires an AuthenticationManager. If you aren't going to use it you can set it to a no-op, or just implement Filter directly. If your Filter can authenticate the request and sets up the SecurityContext then usually the downstream processing will be skipped (it depends on the implementation of the downstream filters, but I don't see anything weird in your app, so they probably all behave that way).

If I were you I might consider putting the API endpoints in a completely separate filter chain (another WebSecurityConfigurerAdapter bean). But that only makes things easier to read, not necessarily crucial.

You might find (as suggested in comments) that you end up reinventing the wheel, but no harm in trying, and you will probably learn more about Spring and Security in the process.

ADDITION: the github approach is quite interesting: users just use the token as a password in basic auth, and the server doesn't need a custom filter (BasicAuthenticationFilter is fine).

Share:
57,202

Related videos on Youtube

rhinds
Author by

rhinds

Co-founder of NerdAbility.com - an online resume building site for tech professionals linking up StackOVerflow, GitHub, GoogleCode, BitBucket, GeekList, LinkedIn, Blogs, apps, and more! You can also check my blog here I'm also a food and cooking nerd, so you can check my food science site here

Updated on July 09, 2022

Comments

  • rhinds
    rhinds almost 2 years

    I am trying to create a webapp that will primarily provide a REST API using Spring, and am trying to configure the security side.

    I am trying to implement this kind of pattern: https://developers.google.com/accounts/docs/MobileApps (Google have totally changed that page, so no longer makes sense - see the page I was referring to here: http://web.archive.org/web/20130822184827/https://developers.google.com/accounts/docs/MobileApps)

    Here is what I need to accompish:

    • Web app has simple sign-in/sign-up forms that work with normal spring user/password authentication (have done this type of thing before with dao/authenticationmanager/userdetailsservice etc)
    • REST api endpoints that are stateless sessions and every request authenticated based ona token provided with the request

    (e.g. user logins/signs up using normal forms, webapp provides secure cookie with token that can then be used in following API requests)

    I had a normal authentication setup as below:

    @Override protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
                .disable()
            .authorizeRequests()
                .antMatchers("/resources/**").permitAll()
                .antMatchers("/mobile/app/sign-up").permitAll()
                .antMatchers("/v1/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/")
                .loginProcessingUrl("/loginprocess")
                .failureUrl("/?loginFailure=true")
                .permitAll();
    }
    

    I was thinking of adding a pre-auth filter, that checks for the token in the request and then sets the security context (would that mean that the normal following authentication would be skipped?), however, beyond the normal user/password I have not done too much with token based security, but based on some other examples I came up with the following:

    Security Config:

    @Override protected void configure(HttpSecurity http) throws Exception {
            http
                .csrf()
                    .disable()
                .addFilter(restAuthenticationFilter())
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()).and()
                    .antMatcher("/v1/**")
                .authorizeRequests()
                    .antMatchers("/resources/**").permitAll()
                    .antMatchers("/mobile/app/sign-up").permitAll()
                    .antMatchers("/v1/**").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/")
                    .loginProcessingUrl("/loginprocess")
                    .failureUrl("/?loginFailure=true")
                    .permitAll();
        }
    

    My custom rest filter:

    public class RestAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
        public RestAuthenticationFilter(String defaultFilterProcessesUrl) {
            super(defaultFilterProcessesUrl);
        }
    
        private final String HEADER_SECURITY_TOKEN = "X-Token"; 
        private String token = "";
    
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
    
            this.token = request.getHeader(HEADER_SECURITY_TOKEN);
    
            //If we have already applied this filter - not sure how that would happen? - then just continue chain
            if (request.getAttribute(FILTER_APPLIED) != null) {
                chain.doFilter(request, response);
                return;
            }
    
            //Now mark request as completing this filter
            request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
    
            //Attempt to authenticate
            Authentication authResult;
            authResult = attemptAuthentication(request, response);
            if (authResult == null) {
                unsuccessfulAuthentication(request, response, new LockedException("Forbidden"));
            } else {
                successfulAuthentication(request, response, chain, authResult);
            }
        }
    
        /**
         * Attempt to authenticate request - basically just pass over to another method to authenticate request headers 
         */
        @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
            AbstractAuthenticationToken userAuthenticationToken = authUserByToken();
            if(userAuthenticationToken == null) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
            return userAuthenticationToken;
        }
    
    
        /**
         * authenticate the user based on token, mobile app secret & user agent
         * @return
         */
        private AbstractAuthenticationToken authUserByToken() {
            AbstractAuthenticationToken authToken = null;
            try {
                // TODO - just return null - always fail auth just to test spring setup ok
                return null;
            } catch (Exception e) {
                logger.error("Authenticate user by token error: ", e);
            }
            return authToken;
        }
    

    The above actually results in an error on app startup saying: authenticationManager must be specified Can anyone tell me how best to do this - is a pre_auth filter the best way to do this?


    EDIT

    I wrote up what I found and how I did it with Spring-security (including the code) implementing a standard token implementation (not OAuth)

    Overview of the problem and approach/solution

    Implementing the solution with Spring-security

    Hope it helps some others..

    • ksokol
      ksokol about 10 years
      I would recommend Spring Security OAuth(2) over a custom implementation. IMHO I would try to avoid implementing a custom solution. Most of the time it is error prone and insecure. Particularly if you are using Spring MVC you could consider Spring Security and Spring Security OAuth(2) as an valid alternative for a token based authentication flow.
    • rhinds
      rhinds about 10 years
      I was originally planning to use OAuth2 for security - but was questioning that as the API is planned only to be used by an app I am building (e.g. no other planned client/consumers etc), and then I saw the above link: developers.google.com/accounts/docs/MobileApps with Google recommending the approach outlined above, plus for a single client I didn't know whether OAuth2 would be overkill. See my previous question regarding security: stackoverflow.com/q/21461223/258813
    • rhinds
      rhinds about 10 years
      I also looked at an implementation like this: thebuzzmedia.com/… - but that is very close to the two-leg OAuth 1 pattern
    • Nalla Srinivas
      Nalla Srinivas over 7 years
    • Nalla Srinivas
      Nalla Srinivas over 7 years
      additionally you need to configure form based authentication also.
  • rhinds
    rhinds about 10 years
    Great - thanks. I didn't really think about having multiple configs, but that is a good idea. Is there any reason I couldn't have the API/webapp config in two seperate files, each defining their own URLs to intercept and their own AuthenticationProvider? Would that be a reasonable approach do you think?
  • Dave Syer
    Dave Syer about 10 years
    It has to be 2 classes (2 instances of WebSecurityConfigurerAdapter) so it might as well be 2 files I guess. AuthenticationProvider isn't necessarily appropriate (YMMV), but each one will need an AuthenticationManager. I suppose with ProviderManager they could share it, but I don't see a lot of value in that since they are different resources with different authentication requirements.