Logout with Rest Template in Spring Security Application - spring

I'm writing a client for my application. Spring stack is Spring 4 and Spring Security 4 (main parts).
I try to logout from my application in the following way:
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> entity = new HttpEntity<>("_csrf=" + csrfToken,
httpHeaders);
restTemplate.postForEntity(appUrl + "/logout", entity, String.class);
A RestTemplate object is created in the following way (before login of course):
new RestTemplate(new HttpComponentsClientHttpRequestFactory())
But I get the following exception on the server:
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported at
org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:207) at
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:374) at
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:314) at
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:61) at
org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:352)
I was getting the following exception when I tried to login in the app. The only
way I managed to do it is getting login page and getting CSRF token from there. I tried to get token from the server in the following way and return it to the client:
#RequestMapping(value = "/api/csrf", method = RequestMethod.GET)
public String csrf(HttpServletRequest httpServletRequest) {
return ((CsrfToken) httpServletRequest.getAttribute(CsrfToken.class.getName())).getToken();
}
But with this token I was getting the same exception all the time.
Now I want to implement logout in any way at least but notes related to proper login with RestTemplate is appreciated too. Thanks!
UPDATE: adding security config
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final DataSource dataSource;
private final UserDetailsService splittingRolesUserDetails;
private final AccessDeniedHandler accessDeniedHandler;
#Autowired
public SecurityConfig(DataSource dataSource, UserDetailsService splittingRolesUserDetails,
AccessDeniedHandler accessDeniedHandler) {
this.dataSource = dataSource;
this.splittingRolesUserDetails = splittingRolesUserDetails;
this.accessDeniedHandler = accessDeniedHandler;
}
// overrides role prefix in case .access() in httpSecurity configuration
// just because it is needed in the task. hasRole() won't work
// as there are used different voters in AffirmativeBased.
// link to the related issue on GitHub:
// https://github.com/spring-projects/spring-security/issues/3701
#Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.authenticationProvider(authenticationProvider())
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select user_name, password, true from user where username=?");
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(splittingRolesUserDetails);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.antMatchers("/login/**").permitAll()
.antMatchers("/api/csrf").permitAll()
.antMatchers("/api/ticket/event**").access("hasRole('" + Role.BOOKING_MANAGER.toString() + "')")
.anyRequest().access("hasRole('" + Role.REGISTERED_USER.toString() + "')")
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/event")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.accessDeniedPage("/403")
.and()
.rememberMe()
.userDetailsService(splittingRolesUserDetails);
}
}

No need to send your token from an endpoint that is not secured, that contradicts the principle for which the token is used in the first place. You can store your token in a cookie with HTTP only access by adding this to your config:
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
You can then retrieve it from a cookie named XSRF-TOKEN.

Related

Request method 'POST' is not supported

I'm trying to upgrade Spring Boot from 2.7.6 to 3.0.1. I have a problem during the login action. The following is my new WebSecurityConfig:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
private final CustomUserDetailsService customUserDetailsService;
private final CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler;
public WebSecurityConfig(CustomUserDetailsService customUserDetailsService, CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler) {
this.customUserDetailsService = customUserDetailsService;
this.customizeAuthenticationSuccessHandler = customizeAuthenticationSuccessHandler;
}
#Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(customUserDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AccessDeniedHandler accessDeniedHandler(){
return new CustomAccessDeniedHandler();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests()
.requestMatchers("/").permitAll()
.requestMatchers("/login").permitAll()
.authenticated()
.and()
.csrf().disable()
.formLogin()
.successHandler(customizeAuthenticationSuccessHandler)
.loginPage("/login")
.failureUrl("/login?error=true")
.usernameParameter("email")
.passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.invalidateHttpSession(true)
.logoutSuccessUrl("/login?logout=true")
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.and()
.authenticationProvider(authenticationProvider());
http
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired=true");
return http.build();
}
// This second filter chain will secure the static resources without reading the SecurityContext from the session.
#Bean
#Order(0)
SecurityFilterChain resources(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**").permitAll()
.anyRequest().permitAll())
.requestCache().disable()
.securityContext().disable()
.sessionManagement().disable();
return http.build();
}
}
Follow my CustomUserDetailService:
#Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findUserByEmail(String email) {
System.out.println(email);
User user = userRepository.findByEmail(email.toLowerCase());
System.out.println(user.getEmail());
return userRepository.findByEmail(email.toLowerCase());
}
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email.toLowerCase());
if (user != null) {
List<GrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority( user.getRole()));;
return buildUserForAuthentication(user, authorities);
} else {
throw new UsernameNotFoundException("username not found");
}
}
private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
}
}
When I run the application I see the login page, but when I enter the credential and press submit I receive the error:
Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' is not supported]
and Tomcat shows:
HTTP Status 405 – Method Not Allowed Type Status Report
Message Method 'POST' is not supported.
I searched for a solution but really I don't understand where is the problem.
To use multiple HttpSecurity instances, you must specify a security matcher, otherwise the first SecurityFilterChain will process all requests, and no requests will reach the second chain.
See this section of the Spring Security reference documentation.
In your case the SecurityFilterChain called resources is matching all requests, because you don't have a security matcher.
Since the resources chain does not configure formLogin then Spring Security does not create the default /login POST endpoint.
You can fix this by changing requests to:
#Bean
#Order(0)
SecurityFilterChain resources(HttpSecurity http) throws Exception {
http
.securityMatchers((matchers) -> matchers
.requestMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**") // the requests that this SecurityFilterChain will process
)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().permitAll())
.requestCache().disable()
.securityContext().disable()
.sessionManagement().disable();
return http.build();
}
If you want more details on the difference between authorizeHttpRequests and requestMatchers you can check out this question.
This error typically occurs when the method in the controller is not mapped to a post request. Should be something like:
#RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView login(...

Spring Boot JWT - Adding BCrypt Security - Getting Access Denied

I have an application server which uses Spring boot framework with JWT token. I want to encrypt the user password, but running into login issues. I am able to encrypt user's password using
userModel.setPassword(new BCryptPasswordEncoder().encode(userModel.getPassword()));
but when trying to login I am getting Encoded password does not look like BCrypt. I tried to change my authenticate method and encrypt the password from login but it didn't work.
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
new BCryptPasswordEncoder().encode( authenticationRequest.getPassword())));
I would appreciate your help, if you could point me to the direct direction or give me solution. below is my Security config file.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailService myUserDetailService;
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfig(MyUserDetailService myUserDetailService, JwtRequestFilter jwtRequestFilter) {
this.myUserDetailService = myUserDetailService;
this.jwtRequestFilter = jwtRequestFilter;
}
#Override
// Authentication : User --> Roles
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(authProvider());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(myUserDetailService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Override
// Authorization : Role -> Access
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin().disable()
.headers().frameOptions().disable()
.and()
.authorizeRequests()
.antMatchers("/authenticate").permitAll() .and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
I am not able to authenticate(login) using the encrypted password or the raw password. Please let me know what I can do to fix.
Thank you for your help.
Hey i had the same problem like you, i am just from solving it.
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword())
);
replace that part of your code with mine.
By calling again new BCryptPasswordEncoder().encode you create another salt value different from the one previously created when saving the user in your Database.
And by login you will never get the same value as the one in the Database.
The exception is thrown if the stored password in the database is not encrypted correctly.
Make sure it starts with $2a$ or $2b$ or $2y$ and is exactly 60 chars long.

Spring Security basic auth for REST Api single login needed?

I am using Spring Security basic auth for my REST API.
Initially I get unauthorized HTTP response status for unauthenticated secured routes.
If I provide the right credentials, I get a Ok HTTP response status, but after a single successful login, I can access all the secured routes without providing user credentials.
Here are my questions:
Is it the correct behaviour for basic authentication?
Why it happens?
My security config:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
//J-
http.csrf().disable()
.authorizeRequests()
.antMatchers("/save")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/h2-console/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
//J+
//adding support for h2 console, otherwise crashes
http.headers().frameOptions().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
}
and here is the UserDetailsService's loadByUsername() method:
#Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UserNotFoundException(username);
} else if (UserStatus.Deactivated.equals(user.getStatus())) {
throw new UserDeactivatedException(username);
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), Collections.singleton(new SimpleGrantedAuthority("USER")));
}
https://www.baeldung.com/spring-security-session
Refer mentioned link. For Restful API’s use stateless session policy

successForwardUrl does not work with Spring Social after authenticating successfully

I'm working on a Spring Boot project integrating with Spring Social. After authenticating with google successfully, I want to redirect to the end point /userInfo, but it seems to redirect to the previous page where I make a request to authenticate to Google: http://localhost:8080/auth/google
I've also tried to create a bean CustomAuthenticationSuccessHandler, which implements the AuthenticationSuccessHandler and add that to the configuration file, but it didn't work either
My WebSecurityConfiguration:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
// This bean is load the user specific data when form login is used.
#Override
public UserDetailsService userDetailsService() {
return userDetailsService;
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// Enable jdbc authentication
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(passwordEncoder())
.usersByUsernameQuery("select user_name, encryted_password"
+ " from app_user where user_name=?");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// Pages do not require login
http.authorizeRequests()
.antMatchers("/", "/signup", "/login", "/logout")
.permitAll();
http.authorizeRequests()
.antMatchers("/user/**")
.access("hasRole('" + AppRole.ROLE_USER + "')");
// For ADMIN only.
http.authorizeRequests()
.antMatchers("/admin/**")
.access("hasRole('" + AppRole.ROLE_ADMIN + "')");
// When the user has logged in as XX.
// But access a page that requires role YY,
// AccessDeniedException will be thrown.
http.authorizeRequests()
.and()
.exceptionHandling()
.accessDeniedPage("/403");
// Form Login config
http.authorizeRequests()
.and()
.formLogin()
.loginProcessingUrl("/j_spring_security_check") // the url to submit the username and password to
.loginPage("/login") // the custom login page
.successForwardUrl("/userInfo") // the landing page after a successful login
.failureUrl("/login?error=true") // the landing page after an unsuccessful login
.usernameParameter("username")
.passwordParameter("password");
// Logout Config
http.authorizeRequests()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/logoutSuccessful");
http.apply(new SpringSocialConfigurer())
.signupUrl("/signup");
}
}
MyController:
#RestController
#Transactional
public class MainController {
#Autowired
private AppUserDAO appUserDAO;
#Autowired
private ConnectionFactoryLocator connectionFactoryLocator;
#Autowired
private UsersConnectionRepository userConnectionRepository;
#Autowired
private AppUserValidator appUserValidator;
#RequestMapping(value = {"/", "/welcome"}, method = RequestMethod.GET)
public String welcomePage(Model model) {
model.addAttribute("title", "Welcome");
model.addAttribute("message", "This is welcome page!");
return "welcomePage";
}
#RequestMapping(value = "/logoutSuccessful", method = RequestMethod.GET)
public String logoutSuccessfulPage(Model model) {
model.addAttribute("title", "Logout");
return "logoutSuccessfulPage";
}
#RequestMapping(value = "/userInfo", method = RequestMethod.GET)
public String userInfo(Model model, Principal principal) {
// After user login successfully.
String userName = principal.getName();
System.out.println("User Name: " + userName);
UserDetails loginedUser = (UserDetails) ((Authentication) principal).getPrincipal();
String userInfo = WebUtils.toString(loginedUser);
model.addAttribute("userInfo", userInfo);
return "userInfoPage";
}
Are there any ways to forward to /userInfo url after logging in with Spring Social ?
Try in POST "/userInfo" return "redirect:/userInfoPage".

Spring Security SessionRegistry returning empty list

Friends I have search a lot and try every solution available on the internet, but my problem not solved.
I want check (in Spring Boot web application) currently user logged in with the credentials specify in the login page, if there is a session with username currently, then invalidate that first a login again for the request.
I want to ensure there will be one session for the user, if session exist then invalidate and login forcefully.
I am trying to get all the principle from the SessionRegistry, but it always returning [] empty list even after multiple user logged in the system.
Here is my spring security config
...
#Autowired private CustomUserDetailsService customUserDetailsService;
#Autowired private CustomUrlAuthenticationSuccessHandler customUrlAuthenticationSuccessHandler;
#Autowired private PasswordEncoder passwordEncoder;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
#Bean
JCaptchaAuthenticationFilter jCaptchaAuthenticationFilter() throws Exception {
JCaptchaAuthenticationFilter jCaptchaAuthenticationFilter = new JCaptchaAuthenticationFilter();
jCaptchaAuthenticationFilter.setAuthenticationManager(authenticationManager());
jCaptchaAuthenticationFilter.setAuthenticationFailureHandler(customLoginFailureHandler());
jCaptchaAuthenticationFilter.setAuthenticationSuccessHandler(customUrlAuthenticationSuccessHandler);
return jCaptchaAuthenticationFilter;
}
#Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(customUserDetailsService);
authProvider.setPasswordEncoder(passwordEncoder);
return authProvider;
}
#Bean
public CustomLoginFailureHandler customLoginFailureHandler() {
CustomLoginFailureHandler customLoginFailureHandler = new CustomLoginFailureHandler("/login");
return customLoginFailureHandler;
}
#Override
protected void configure(HttpSecurity http) throws Exception {// #formatter:off
http.authorizeRequests().antMatchers("/js/**", "/fonts/**", "/css/**", "/images/**", "/favicon.ico").permitAll()
.antMatchers("/", "/register/**", "/email/**", "/captcha.png/**").permitAll().antMatchers("/login/**")
.permitAll()// Basically I'm allowing parameters for login so
// .antMatchers("/services/**").permitAll()
.antMatchers("/forgot/password/**", "/user/verify/**").permitAll().antMatchers("/user/resetPassword*")
.hasAuthority("CHANGE_PASSWORD_PRIVILEGE").anyRequest().authenticated().and()
.addFilterBefore(jCaptchaAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).formLogin()
.loginPage("/login").permitAll().and()
.csrf().disable()
.sessionManagement().invalidSessionUrl("/invalidSession")
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry()).and()
.sessionFixation().newSession().and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
.invalidateHttpSession(false).deleteCookies("JSESSIONID").permitAll();
http.headers().frameOptions().sameOrigin();
}
....
#Bean
SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
Here is i am getting sessions list
List<String> userList = sessionRegistry.getAllPrincipals().stream()
.filter(u -> !sessionRegistry.getAllSessions(u, true).isEmpty())
.map(Object::toString)
.collect(Collectors.toList());
But above code always return empty list. I have checked is there any double sessionRegistry loading by disabling sessionRepositry, but spring throw exception that bean not found.
Please help friends.

Resources