Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring - RESTful authentication using cookies

I try to implement authentication (for my android client app) using cookies, based on this article -http://automateddeveloper.blogspot.co.uk/2014/03/securing-your-mobile-api-spring-security.html

SecurityConfig:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final static String TOKEN_STRING = "my_token";
    private final static String COOKIE_STRING = "my_cookie";

    @Autowired
    private UserDetailsService userSvc;
    @Autowired
    private MyTokenBasedRememberMeService tokenSvc;
    @Autowired
    private RememberMeAuthenticationProvider rememberMeProvider;
    @Autowired 
    private MyAuthSuccessHandler authSuccess;
    @Autowired
    private MyAuthFailureHandler authFailure;
    @Autowired
    private MyLogoutSuccessHandler logoutSuccess;


    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(userSvc)
            .passwordEncoder(passwordEncoder());

        auth.authenticationProvider(rememberMeProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

         http
            .authorizeRequests()
                .antMatchers("/register").permitAll()
                .anyRequest().authenticated().and()
            .formLogin()
               .loginPage("/")
               .loginProcessingUrl("/loginendpoint")
               .successHandler(authSuccess)
               .failureHandler(authFailure).and()
            .logout()                           
                .logoutUrl("/logout")
                .logoutSuccess(logoutSuccess)
                .deleteCookies(COOKIE_STRING).and()
            .rememberMe()
                .rememberMeServices(tokenSvc).and()
            .csrf()
                .disable()
            .addFilterBefore(rememberMeAuthenticationFilter(), BasicAuthenticationFilter.class)
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public RememberMeAuthenticationFilter rememberMeAuthenticationFilter() throws Exception {
        return new RememberMeAuthenticationFilter(authenticationManager(), tokenBasedRememberMeService());
    }

    @Bean
    public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
        return new RememberMeAuthenticationProvider(TOKEN_STRING);
    }

    @Bean
    public MyTokenBasedRememberMeService tokenBasedRememberMeService() {
        MyTokenBasedRememberMeService service = new MyTokenBasedRememberMeService(TOKEN_STRING,
                userSvc);
        service.setAlwaysRemember(true);
        service.setCookieName(COOKIE_STRING);
        return service;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        return encoder;
    }

}

MyTokenBasedRememberMeService:

public class MyTokenBasedRememberMeService extends TokenBasedRememberMeServices {

    private final static String TOKEN_STRING = "my_token";

    public MyTokenBasedRememberMeService(String key, UserDetailsService userDetailsService) {
        super(key, userDetailsService);
    }

    @Override
    protected String extractRememberMeCookie(HttpServletRequest request) {
        String token = request.getHeader(TOKEN_STRING);
        if ((token == null) || (token.length() == 0)) {
            return "";
        }
        return token;
    }
}

Unfortunately after a successful login my cookie is empty on the client side:

Set-Cookie: my_cookie=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/

What's wrong?

-------EDIT 1-------

If you login directly in the browser you get no cookie (in dev tools for example)?

I tested it using postman and I received only JSESSIONID cookie (no my_cookie).

Also, are you using a custom login controller method? (e.g. Is your usercontroller explicitly authenticating users?)

Yes, I'm using a custom login controller method, but I'm new in spring security and if can be done without a custom controller I will be grateful for any explanations. My controller is responsible for authentication of the user.

If you are not using spring-security to handle authentication then I suspect you may have to explicitly set cookies etc yourself

No, I'm using spring security only. At least I think so ... :)

What is UserController login method doing?

I updated my code.

-------EDIT 2-------

According to @rhinds advices and the spring documentation I corrected a few things (above code is updated). Now I can login to loginendpoint and after login I get my_cookie. But I have related questions:

  1. After a successful login I receive a cookie in response. To further requests I have to manually add the token (client side) if it's automatically added on the server side?
  2. What about logout? How "Spring" will know which user has to be logged out?
  3. What about the token expiration date? The default is 2 weeks, then what? Can I set so that the token never expires?

For people who will be do something similar I recommend a look at this great article also - https://dzone.com/articles/secure-rest-services-using :)

like image 332
Bakus123 Avatar asked Aug 19 '15 16:08

Bakus123


1 Answers

Ok, so the best place to start is to move away from the custom spring controller for logging in and just delegate that to spring security - the docs will give you a pretty good overview of how to get started with that - see here to get started

From the linked article, if you look at the config code:

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

The section under the .formLogin() call is telling spring-security which endpoint to listen on for login attempts - e.g. if I have this config and POST to the endpoint /loginprocess then Spring-security will intercept it and use the authentication manager to process the submitted form (expecting the username and password fields etc).

The next important bit is the wiring together of your userDetailsService and the authentication manger:

 @Override protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
     auth
        .userDetailsService(userDetailsServiceImpl)
        .passwordEncoder(bCryptPasswordEncoder());
     auth.authenticationProvider(rememberMeAuthenticationProvider);
 }

This gives spring-security the means to attempt to load a user object given the provided login attempt - as long as you class implements UserDetailsService then spring security should have all it needs.

Assuming all is correct, then you should be able to remove the custom login controller method, define a loginProcessingUrl and then POST to it and spring-security should kick in and (attempt) to handle it.

It might be worth just spending some time getting spring-security config working and handling the simple login case, once that is all been delegated to spring-security machinery it should be easier to update the config to wire in the RememberMe side of things.


Response to Edit 2

I am assuming you are following the implementation details in the linked article based on the general approach here: http://automateddeveloper.blogspot.co.uk/2014/03/securing-your-api-for-mobile-access.html (the explanation of the implementation details that you have linked to in your op)

  1. After a successful login I receive a cookie in response. To further requests I have to manually add the token (client side) if it's automatically added on the server side?

So assuming you are logging in and then making API requests from your mobile app - as per the article, you need a webview in your app to allow users to login, once they have done that the WebView will recieve the response from the login which will include the cookie. At this point, all you care about is extracting the token from the cookie -after that, the cookie isn't needed. In your app you can persist the token however you like, and just add that make sure you provide that in every API request you make from the app - The RememberMe implementation in the article extracts the token from the API request headers and authenticates the user.

  1. What about logout? How "Spring" will know which user has to be logged out?

It won't - the config has set spring up to be stateless, e.g. it doesn't track logged in users, that is logged by the presence of a valid cookie (or in the case of the API requests, presence of the token we extracted) - e.g. in stateless mode, every single request made to the app is checked to see if it is authenticated

  1. What about the token expiration date? The default is 2 weeks, then what? Can I set so that the token never expires?

Again, assuming you are following the pattern described in the link above, this does not matter, as we just use that cookie on that first login, after that your app has a remember me token that will be used for authentication so the cookie is essentially discarded from that point.

like image 112
rhinds Avatar answered Oct 06 '22 04:10

rhinds