Spring Security - Token based API auth & user/password authentication
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).
Related videos on Youtube
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, 2022Comments
-
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 about 10 yearsI 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 about 10 yearsI 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 about 10 yearsI also looked at an implementation like this: thebuzzmedia.com/… - but that is very close to the two-leg OAuth 1 pattern
-
Nalla Srinivas over 7 yearsCheck this it may useful github.com/srinivas1918/spring-rest-security
-
Nalla Srinivas over 7 yearsadditionally you need to configure form based authentication also.
-
rhinds about 10 yearsGreat - 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 about 10 yearsIt 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 anAuthenticationManager
. I suppose withProviderManager
they could share it, but I don't see a lot of value in that since they are different resources with different authentication requirements.