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;
}
}
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>
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With