Reputation: 858
I've been probably trying harder than I should to get this to work, but now I give up. I need a simple HttpSession based authentication on my Spring Boot project which should be rather straightforward based on the docs.
Here is my 'WebSecurityConfigurerAdapter' implementation:
package com.collective.foundation ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.context.annotation.Configuration ;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder ;
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.UserDetails ;
import org.springframework.security.core.userdetails.UserDetailsService ;
import org.springframework.security.core.userdetails.UsernameNotFoundException ;
import org.springframework.security.config.http.SessionCreationPolicy ;
import com.collective.foundation.usermanagement.UserDetailsServiceImpl ;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService ;
@Override
protected UserDetailsService userDetailsService () {
return this.userDetailsService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/market/**").access("hasRole('CUSTOMER')")
.antMatchers("/scm/staff/**").access("hasRole('SCM_STAFF')")
.antMatchers("/scm/manager/**").access("hasRole('SCM_MANAGER')")
.antMatchers("/warehouse/staff/**").access("hasRole('WAREHOUSE_STAFF')")
.antMatchers("/warehouse/manager/**").access("hasRole('WAREHOUSE_MANAGER')")
.antMatchers("/warehouse/supplier/**").access("hasRole('SUPPLIER')")
.antMatchers("/user/**").permitAll()
.antMatchers("/**").permitAll()
// .and()
// .formLogin()
// .loginPage("/user/login/")
// .permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/err/403/")
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) ;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService) ;
}
}
I have a User model with a role
field to encapsulate principal data, so here is my UserDetailsService
implementation:
package com.collective.foundation.usermanagement ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.security.core.GrantedAuthority ;
import org.springframework.security.core.authority.SimpleGrantedAuthority ;
import org.springframework.security.core.userdetails.UserDetails ;
import org.springframework.security.core.userdetails.UserDetailsService ;
import org.springframework.security.core.userdetails.UsernameNotFoundException ;
import org.springframework.stereotype.Service ;
import org.springframework.transaction.annotation.Transactional ;
import java.util.HashSet ;
import java.util.Set ;
import com.collective.foundation.entities.* ;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository ;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findOne(username) ;
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
grantedAuthorities.add(new SimpleGrantedAuthority(user.getRole()));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities);
}
}
I'm relying on my own controller to handle the login page, here is the method:
@PostMapping(value="login/")
public String submitLoginView(@ModelAttribute LoginDto loginDto) {
//
System.out.println(Constants.LOG_PREFIX + "submitLoginView") ;
System.out.println(loginDto.toString()) ;
//
User user = userRepository.findOne(loginDto.getUsername()) ;
if (user != null && user.getPassword().equals(loginDto.getPassword())) {
System.out.println(Constants.LOG_PREFIX + "successful login") ;
securityService.autologin(loginDto.getUsername(), loginDto.getPassword()) ;
return "view_home_page" ;
} else {
System.out.println(Constants.LOG_PREFIX + "no matching login found") ;
return "redirect:/user/login/err/" ;
}
}
The autoLogin method in the above controller simply puts a USerDetails
instance obtained from the userDetailsService
into the SecurityContext
:
public void autologin(String username, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username) ;
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities()) ;
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken) ;
}
Yet the isn't authenticated automatically upon their next request, so I'm wondering what is wrong here.
Upvotes: 1
Views: 922
Reputation: 3561
The problem is in autologin
method.
Please do not use userDetailsService
directly. Inject AuthenticationManager
instead and pass to it authenticate
method UsernamePasswordAuthenticationToken
with raw username
and password
.
It will call userDetailsService
under the hood, perform all checks and if credentials are valid, account not locked, etc - return to you Authentication
which can be set into SecurityContext
.
Here is a simplified version of your example:
@SpringBootApplication
public class So44739147Application {
public static void main(String[] args) {
SpringApplication.run(So44739147Application.class, args);
}
@EnableWebSecurity
static class Security extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/protected/*").fullyAuthenticated()
.and().logout().permitAll().and().exceptionHandling().accessDeniedPage("/err/403/").and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}
}
@Service
public static class SecurityService {
private final AuthenticationManager authenticationManager;
@Autowired
public SecurityService(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
void autologin(String username, String password) {
final Authentication usernamePasswordAuthenticationToken = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public static class CredentialsDTO {
String username;
String password;
}
@RestController
@RequestMapping("/login")
public static class LoginApi {
private final SecurityService securityService;
@Autowired
public LoginApi(SecurityService securityService) {
this.securityService = securityService;
}
@PostMapping
public String login(@RequestBody CredentialsDTO credentials) {
securityService.autologin(credentials.username, credentials.password);
return "ok";
}
}
@RestController
@RequestMapping("/protected/me")
public static class MeApi {
@GetMapping
public String me(Principal principal) {
return principal.getName();
}
}
}
=>
$ curl -i -XPOST 'localhost:8080/login' -H'Content-Type: application/json' -d'{"username":"user","password":"123"}'
HTTP/1.1 200
...
Set-Cookie: JSESSIONID=1FECEF3B13065A8938A4B8DA951ED96F; Path=/; HttpOnly
...
ok%
$ curl -XGET 'localhost:8080/protected/me' -H'Cookie: JSESSIONID=1FECEF3B13065A8938A4B8DA951ED96F'
user%
Upvotes: 1