Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring security login not working, no JSESSIONID cookie returned and redirect fails

Tags:

I'm trying to write an application with separated Backend (written using Spring Boot, and Spring Security for login) and Frontend (ReactJS). Right now I'm struggling with accessing a secured endpoint after a successful login.

What I want to achieve: Make a GET to secured endpoint e.g. '/books/all'. If user not logged in, return 401. If 401 received on front-end, make a POST to '/login'. Then I want to have a successful login and to be able to make a successful GET to '/books/all'.

What doesn't work: The last part. I'm making a POST to '/login' and receive 200 GET. Then I make a second call to '/books/all' and receive GET 401. Also I no longer receive JSESSIONID cookie which worries me.

My question: how to fix this behavior? I believe it's connected to JSESSIONID (server doesn't send information about user being successfully logged in?).

On front-end I'm using axios.

  axios.get('http://localhost:8080/rest/book/anna/all')
        .then(response => {
            console.log('response rebuild');
            console.log(response);
            if (response.status === 401 && response.request.responseURL === 'http://localhost:8080/login') {
                axios.post('http://localhost:8080/login', 'username=c&password=d')
                    .then(response => {
                        console.log('response 2');
                        console.log(response);
                    })
                    .catch(error => {
                        console.log('error');
                        console.log(error);
                    })
            }
        })
        .catch(error => {
            console.log('error 2');
            console.log(error);
            axios.post('http://localhost:8080/login', 'username=c&password=d')
                .then(response => {
                    console.log('response 2');
                    console.log(response);
                    axios.get('http://localhost:8080/rest/book/anna/all')
                        .then(response => {
                            console.log('response 3');
                            console.log(response);
                        })
                        .catch(error => {
                            console.log('error 3');
                            console.log(error);
                        })
                })
                .catch(error => {
                    console.log('error');
                    console.log(error);
                })
        });

Please mind that I'm aware that this code it's low quality; it's just temporary to check if redirection after login is working.

SecurityConfig.java

package com.shareabook.security;

import com.shareabook.repository.UsersRepository;
import com.shareabook.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@EnableJpaRepositories(basePackageClasses = UsersRepository.class)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;
    @Autowired
    private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;
    @Autowired
    private RESTAuthenticationSuccessHandler restAuthenticationSuccessHandler;
    @Autowired
    private RESTAuthenticationFailureHandler restAuthenticationFailureHandler;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(getPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("**/anna/**").authenticated()
                .anyRequest().permitAll();
        http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);
        http.formLogin().successHandler(restAuthenticationSuccessHandler);
        http.formLogin().failureHandler(restAuthenticationFailureHandler);
//                .and()
//                .formLogin().permitAll();

        http
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/rest/author/all");
    }

    private PasswordEncoder getPasswordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return true;
            }
        };
    }
}

RESTAuthenticationEntryPoint.java

@Component
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException {

        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    }
}

RESTAuthenticationFailureHandler.java

@Component
public class RESTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException,
            ServletException {

        super.onAuthenticationFailure(request, response, exception);
    }
}

RESTAuthenticationSuccessfulHandler.java

@Component
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

//        clearAuthenticationAttributes(request);
        HttpSession session = request.getSession();
        session.setAttribute("username", "c");
        response.setStatus(HttpServletResponse.SC_OK);
    }
}

BooksController.java

@RestController
@RequestMapping("/rest/book")
public class BookController {

    @CrossOrigin(origins = "http://localhost:8888")
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    @RequestMapping(value = "/anna/all", method = RequestMethod.GET)
    public List<String> securedHello() {
        List<String> word = new ArrayList<>();
        word.add("all");
        System.out.print(word);
        return word;
    }
}
like image 329
werw werw Avatar asked Oct 01 '17 21:10

werw werw


1 Answers

You have enabled forms authentication on your application. At the moment you send the post to login page Spring authenticates your request and by default caches the authnetication on user session.

Later you can send requests to server bound to the same session without additional authentication information. But you need to provide session information on your request. Usually this is done by supplying the JSESSIONID cookie on your next requests. Browser does this for you automatically but only after page reload. If you stay of the same page only cookies loaded for that page initially will be sent back to server.

For SPA applications I would suggest using token-based authentication instead of forms. You will have first to login and receive a token in response. Next you will have to supply Authorization header with each your request providing token as authentication information (usually in form Bearer <token>)

like image 98
Aleh Maksimovich Avatar answered Oct 04 '22 15:10

Aleh Maksimovich