Session Concurrency not working with custom UserDetails - spring-boot

Spring security maximum concurrent session setting is not working with custom UserDetailsService and custom UserDetails implementation. It allows login with same user from different machines.
But when I use custom UserDetailsService with Spring Security's UserDetails implementation User, it terminates first logged in session and logs in with the new session.
Security Configuration:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic().and()
.sessionManagement().maximumSessions(1).and().and().userDetailsService(customUserDetailsService);
}
}
Custom UserDetailsService with Spring Secutiy User implementation(Working):
#Service
public class CustomUserDetailsService
implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
final User user = new User("user", "password", Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
return user;
}
Custom UserDetailsService with Custom User implementation(Not Working):
#Service
public class CustomUserDetailsService
implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
final CustomUser user = new CustomUser();
user.setUsername("user");
user.setPassword("password");
user.setAuthorities(Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
user.setAccountNonLocked(true);
user.setAccountNonExpired(true);
user.setCredentialsNonExpired(true);
user.setEnabled(true);
return user;
}
Any help on this?

Related

where do I get the "username" value from in spring security to pass to the loadUserByUsername(String username) method of UserDetailsService interface

I am trying to get a user from the database by authenticating the user based on username and password. I am using basic authentication to do this.
I am sending username and password in the authorization header of the rest api
In my controller the getUser() method calls the getuser() method of the UserService class
#GetMapping("/user/self")
public ResponseEntity<UserDto> getUser() {
UserDto UserDto = userService.getUser();
return new ResponseEntity<>(UserDto, HttpStatus.OK);
}
#PutMapping("/user/self")
public ResponseEntity<User> updateUser(#Valid #RequestBody Map<String, String> userMap, Principal principal) {
String username = principal.getName();
String firstname = userMap.get("firstName");
String lastName = userMap.get("lastName");
String password = BCrypt.hashpw(userMap.get("password"), BCrypt.gensalt(10));
User user = userService.getUserByUserName(username);
user.setFirstName(firstname);
user.setLastName(lastName);
user.setPassword(password);
userService.save(user);
return new ResponseEntity<>(user, HttpStatus.NO_CONTENT);
}
UserService class implements UserDetailsService and overrides the loadUserByUsername method that requires a username to be passed as an argument. my question is: how do I pass username to loadUserByUsername() method from my UserService class that I am calling from my controller. where does username value reside?
my understanding is - the Authentication Object contains user credentials that are passed to authentication object when a user types their credentials and send their request, how do I retrieve this username value
#Service
public class UserService implements UserDetailsService {
#Autowired
UserRepository userRepository;
public UserDto save(User user) {
String hashedPassword = BCrypt.hashpw(user.getPassword(), BCrypt.gensalt(10));
user.setPassword(hashedPassword);
userRepository.save(user);
UserDto userDto = new UserDto();
userDto.setId(user.getId());
userDto.setFirstName(user.getFirstName());
userDto.setLastName(user.getLastName());
userDto.setUserName(user.getUserName());
userDto.setAccountUpdatedAt(user.getAccountUpdatedAt());
userDto.setAccountCreatedAt(user.getAccountCreatedAt());
return userDto;
}
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userRepository.findByUserName(userName);
if (user == null) {
throw new UsernameNotFoundException(userName + "was not found");
}
return new UserPrincipal(user);
}
here is my repository code:
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByUserName(String userName);
}
here is my authentication code:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Autowired
UserService userService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.authorizeRequests().antMatchers("/v1/user").permitAll()
.antMatchers("/v1/user/self").authenticated().and().httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
}
}
if you dealing with JPA then in your case you have to use userDetailsService instead of jdbcauthentication, therefor your security class would look like this :
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private UserService userService;
public SecurityConfig(UserService userService){
this.userService = userService;
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(10); // Number of rounds
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userService).passwordEncoder(passwordEncoder());
}
}
then you can customize the authentication in the UserService class to satisfy the business need as the below sample :
#Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository){
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> user = userRepository.findByUsername(username);
if(user.isPresent()){
log.info("cretaed under User service : " + user.get());
return user.get();
}
throw new UsernameNotFoundException("empty or invalud user");
}
}
in addition, do not forget to create the findByUsername method in your repository also do not forget to implement org.springframework.security.core.userdetails.UserDetails in your module class:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String name);
}

spring boot oauth2 Can I get client info in the UserDetailsService

public class UserService implements UserDetailsService {
#Autowired
private UserMapper userMapper;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SecurityContext context = SecurityContextHolder.getContext();
Authentication a = SecurityContextHolder.getContext().getAuthentication();
return userMapper.findUserByUsername(username);
}
public void create(User user) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
user.setPassword(encoder.encode(user.getPassword()));
userMapper.insert(user);
}
}
I want to get the Client type When I loadUserInfo, I find in the SecurityContextHolder, But ,I still found the Client type, How can I write this code?
Thanks for your review

Spring boot basic authentication

I'm using spring boot security to help me to make authentication...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated().and().httpBasic();
}
}
I have a rest service to make login (on my controller) thats a post request that i send email and password and i like to use this service to make the authentication...
But i'm new on spring-boot / java... Can some one help me to make that right way?
Thanks.
You need to permit access to the login endpoint (at least). E.g.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login", "/error").permitAll()
.antMatchers("/**").authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"));
}
If I were you I would remove the #EnableWebSecurity (and let Spring Boot do it's job) as well. And then in the login endpoint you need to set the security context, e.g.
#PostMapping
public void authenticate(#RequestParam Map<String, String> map,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Authentication result = authService.authenticate(map.get("username"), map.get("password"));
SecurityContextHolder.getContext().setAuthentication(result);
handler.onAuthenticationSuccess(request, response, result);
}
The authService should throw BadCredentialsException if the user cannot be authenticated. Here's a sample app that I used in a blog once: https://github.com/dsyer/mustache-sample/blob/7be8459173d0b65b6d44d05f86e581d358ea9b2e/src/main/java/com/example/DemoApplication.java#L177
Change add method in SpringSecurityConfig.java like Below
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserAuthenticationService userAuthenticationService;
#Autowired
private CustomAuthenticationProvider authenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.authenticationProvider).userDetailsService(this.userAuthenticationService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated().and().httpBasic();
}}
Create CustomAuthenticationProvider.
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserAuthenticationService userAuthenticationService;
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String emailId = authentication.getName();
String password = (String) authentication.getCredentials();
UserDetails user = this.userAuthenticationService.loadUserByUsername(emailId);
if (user == null) {
throw new UsernameNotFoundException("Username not found.");
}
//Your password encoder here
if (!password.equals(user.getPassword())) {
throw new UsernameNotFoundException("Wrong password.");
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}}
Create Custom UserService
#Service
public class UserAuthenticationService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmailAddressWithRole(email);
if (user == null) {
throw new UsernameNotFoundException("Username not found for " + email);
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
for (Role roles : user.getRoles()) {
grantedAuthorities.add(new SimpleGrantedAuthority(roles.getRoleName()));
}
return new UserAuthenticationWrapperDto(user.getId(), user.getEmailAddress(), user.getPassword(),
user.getUserType(), user.getCompany().getId(), grantedAuthorities,user.getName());
}}

Spring Security avoid create a table for roles

I'm learning Spring Security. I have my login system ready and I want to add roles. I've seen many tutorials and docs about it and I couldn't find what I'm looking for.
I don't want to create an extra table for Roles, because my table user has a column named "type" and I want to use it for authorization. The value of that column can be "person", "teacher" or "organization". So, I want to based the role system on that column, not in a OneToMany o ManyToMany relationship with a table named "role".
How can I configure that?
Thanks
UPDATED
I forgot, I'm using Spring Data. This is the code I'm using
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private AuthenticationProvider authenticationProvider;
#Autowired
#Qualifier("daoAuthenticationProvider")
public void setAuthenticationProvider(AuthenticationProvider authenticationProvider) {
this.authenticationProvider = authenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder(BCryptPasswordEncoder passwordEncoder){
return passwordEncoder;
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider(BCryptPasswordEncoder passwordEncoder,
UserDetailsService userDetailsService){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
return daoAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/h2-console").disable()
.authorizeRequests().antMatchers("/").authenticated()
.antMatchers("/console/**").permitAll()
.antMatchers("/static/**").permitAll()
.antMatchers("/profile").hasAuthority("PERSON")
.and().formLogin().loginPage("/login").permitAll()
.and().exceptionHandling().accessDeniedPage("/login")
.and().logout().permitAll()
http.headers().frameOptions().disable();
}
#Autowired
public void configureAuthManager(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
authenticationManagerBuilder
.jdbcAuthentication().authoritiesByUsernameQuery("select type from users where username = ?").and()
.authenticationProvider(authenticationProvider);
}
}
You can define a UserDetailsService with a PasswordEncoder in Java Config like following:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired private PersonRepository personRepository;
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(username -> {
Person person = personRepository.findByUsername(username);
if (person == null) throw new UsernameNotFoundException("Invalid user");
return new User(person.getUsername(),
person.getPassword(),
Collections.singleton(new SimpleGrantedAuthority(person.getType())));
})
.passwordEncoder(passwordEncoder())
}
// Rest of the configuration
}
In the above example, i supposed you have a PersonRespository that has access to your user information. With this UserDetailsService you won't need your AuthenticationProvider. Also, User resides in org.springframework.security.core.userdetails package.

Spring Security Basic Auth not using CustomUserDetailsService

my project is in newest Spring Boot + Jersey and I have a problem with login validation.
My security config is:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Autowired
private DataSource datasource;
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable().authorizeRequests()
.antMatchers("/api/user/**").permitAll()
.antMatchers("/api/**").authenticated().and().httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
auth.jdbcAuthentication().dataSource(datasource);
}
#Bean
public UserDetailsService userDetailsService()
{
return new CustomUserDetailsService(datasource);
}
}
but when I do this:
#POST
#Path("authenticate")
#Produces(MediaType.APPLICATION_JSON)
public Response authenticate(#HeaderParam("username") String username, #HeaderParam("password") String password)
{
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = this.authManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = this.userService.loadUserByUsername(username);
return createOkResponse(userDetails.getUsername());
}
authenticate method is using inside InMemoryUserDetailsManager not CustomUserDetailsService which I need for login validation.
How do I change that?
if needed:
public class CustomUserDetailsService extends JdbcUserDetailsManager implements UserDetailsService
{
#Autowired
private UserRepository userRepository;
public CustomUserDetailsService(DataSource datasource)
{
setDataSource(datasource);
}
#Override
public CurrentUserInfo loadUserByUsername(String email)
throws UsernameNotFoundException
{
User user = userRepository.findByPrimaryEmailAndEnabledTrue(email);
handleUserNotFound(email, user);
return new CurrentUserInfo(user);
}
private void handleUserNotFound(String email, User user)
{
if (user == null)
{
throw new UsernameNotFoundException("No user found with email: " + email);
}
}
}
Starter dependencies:
compile("org.springframework.boot:spring-boot-starter-web") {
exclude module: 'spring-boot-starter-tomcat'
}
compile "org.springframework.boot:spring-boot-starter-jetty"
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.boot:spring-boot-starter-aop"
compile "org.springframework.boot:spring-boot-starter-data-jpa"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-jersey"
compile("org.springframework.boot:spring-boot-starter-actuator") { exclude module: 'hsqldb' }
The problem is that you are "registering" a AuthenticationManager only usable for the /api URIs and it isn't exposed as a bean to the ApplicationContext. When using Spring Boot that will add a global one and inject a default user with a generated password in it.
Spring Boot autoconfig will detect the presence of an already availabe AuthenticationManager. To register a global one just create a method which takes a AuthenticationManagerBuilder as an argument and annotate if with #Autowired. Just make sure it isn't named configure as that will not work.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
auth.jdbcAuthentication().dataSource(datasource);
}

Resources