UserNotFoundException was not thrown by spring security in springboot - spring

I am working on spring boot app with spring security enabled. I am using custom jdbc authentication for the same. In my user detail service impl i am throwing usernamenotfound exception but it is not getting logged anywhere. I am not seeing anything on console either.
Below is my UserDetailService impl
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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;
#Service
public class ApplicationUserDetailService implements UserDetailsService{
private ApplicationUserDao applicationUserDao;
#Autowired
public ApplicationUserDetailService(#Qualifier("db") ApplicationUserDao applicationUserDao) {
this.applicationUserDao=applicationUserDao;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return applicationUserDao.loadUserByUsername(username)
.orElseThrow(()->{
System.out.println("here...");
throw new UsernameNotFoundException("dsdsds");
});
}
}
Below is security configuration for jdbc auth
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider());
}
#Bean
public DaoAuthenticationProvider provider() {
DaoAuthenticationProvider pr=new DaoAuthenticationProvider();
pr.setUserDetailsService(applicationUserDetailService);
pr.setPasswordEncoder(encoder);
return pr;
}
UserDetailDaoImpl
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Repository;
#Repository("db")
public class DBUserDaoImpl implements ApplicationUserDao{
private final JdbcTemplate jdbcTemplate;
#Autowired
public DBUserDaoImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate=jdbcTemplate;
}
#Override
public Optional<ApplicationUser> loadUserByUsername(String username) throws UsernameNotFoundException {
return Optional.ofNullable(jdbcTemplate.query("select * from users where username='"+username+"'"
,new UserExtractor(jdbcTemplate)));
}
}
When i am sending wrong details i should get exception but it is not coming. Please help me.Spring boot 2.4.2

UsernameNotFoundException is not typically something that someone running a server really needs to know about, so Spring Security does not print a message for it. It's an error that is the fault of the user (they entered an account that doesn't exist).
Regardless, if you'd like to print the message out, you can do something like this:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return applicationUserDao.loadUserByUsername(username).orElseThrow(() -> {
// Create an exception, but don't throw it yet
var exception = new UsernameNotFoundException("No account found with name " + username);
// Print the exception
exception.printStackTrace();
// Return the exception (throwing works too, but everything I can find says to return it)
return exception;
});
}

Related

How to override SecurityFilterChain in Spring Boot context?

I am facing the issue which is not obvious to resolve just by reading the documentation. While migrating to Spring Boot v2.7.4 / Spring Security v5.7.3 I have refactored the configuration not to extend WebSecurityConfigurerAdapter and to look like below:
#Configuration
#EnableWebSecurity
public class CustomSecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.
csrf().disable().
logout().disable().
authorizeRequests().anyRequest().permitAll();
return http.build();
}
}
The above method is called, however has no effect as SecurityFilterChain instance created by OAuth2SecurityFilterChainConfiguration is used instead (I see that from debug by inspecting the list of filter in the stack that has e.g. LogoutFilter which should be disabled by above configuration). Debug log:
2022-10-20 15:49:48.790 [main] o.s.b.a.s.DefaultWebSecurityCondition : Condition DefaultWebSecurityCondition on org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration$OAuth2SecurityFilterChainConfiguration matched due to AllNestedConditions 2 matched 0 did not; NestedCondition on DefaultWebSecurityCondition.Beans #ConditionalOnMissingBean (types: org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter,org.springframework.security.web.SecurityFilterChain; SearchStrategy: all) did not find any beans; NestedCondition on DefaultWebSecurityCondition.Classes #ConditionalOnClass found required classes 'org.springframework.security.web.SecurityFilterChain', 'org.springframework.security.config.annotation.web.builders.HttpSecurity'
2022-10-20 15:49:48.791 [main] a.ConfigurationClassBeanDefinitionReader : Registered bean definition for imported class 'org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration$OAuth2SecurityFilterChainConfiguration'
2022-10-20 15:49:48.792 [main] o.s.b.a.condition.OnBeanCondition : Condition OnBeanCondition on org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration$OAuth2SecurityFilterChainConfiguration#jwtSecurityFilterChain matched due to #ConditionalOnBean (types: org.springframework.security.oauth2.jwt.JwtDecoder; SearchStrategy: all) found bean 'jwtDecoderByJwkKeySetUri'
...
2022-10-20 15:49:49.082 [main] a.ConfigurationClassBeanDefinitionReader : Registering bean definition for #Bean method com.mycompany.CustomSecurityConfig.filterChain()
...
2022-10-20 15:49:52.276 [main] edFilterInvocationSecurityMetadataSource : Adding web access control expression [authenticated] for any request
2022-10-20 15:50:13.348 [main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter#33502cfe, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#729d1428, org.springframework.security.web.context.SecurityContextPersistenceFilter#7d0312a, org.springframework.security.web.header.HeaderWriterFilter#6ca97ddf, org.springframework.security.web.csrf.CsrfFilter#38f569d, org.springframework.security.web.authentication.logout.LogoutFilter#1104ad6a, org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter#74ab8610, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#7833407, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#66acaa54, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#115924ba, org.springframework.security.web.session.SessionManagementFilter#6a905513, org.springframework.security.web.access.ExceptionTranslationFilter#5749e633, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#49741e80]
...
2022-10-20 15:50:13.384 [main] edFilterInvocationSecurityMetadataSource : Adding web access control expression [permitAll] for any request
2022-10-20 15:50:17.641 [main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter#4a0f4282, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#19d3f4fb, org.springframework.security.web.context.SecurityContextPersistenceFilter#99f75e4, org.springframework.security.web.header.HeaderWriterFilter#118c1faa, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#2b6ff016, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#5aefdb9e, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#43cf97a8, org.springframework.security.web.session.SessionManagementFilter#da5b46f, org.springframework.security.web.access.ExceptionTranslationFilter#11267e87, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#7827cdfc]
Is it expected that the bean CustomSecurityConfig.filterChain participates in DefaultWebSecurityCondition evaluation and OAuth2SecurityFilterChainConfiguration.jwtSecurityFilterChain is not created. Or the issue with DefaultWebSecurityCondition is that the instance of WebSecurityConfigurerAdapter is not in the context anymore (as to issue #10822 it is deprecated)?
The suggestion to add #Order() annotation didn't work:
#Configuration
#EnableWebSecurity
public class CustomSecurityConfig {
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ...
as well as further attempts to exclude the autoconfiguration class like this:
#SpringBootApplication(excludeName = "org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration.OAuth2SecurityFilterChainConfiguration")
public class Application extends SpringBootServletInitializer { ...
failed probably due to issue #5427 with the following error
java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes:
- org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration.OAuth2SecurityFilterChainConfiguration
at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.handleInvalidExcludes(AutoConfigurationImportSelector.java:222) ~[spring-boot-autoconfigure-2.7.4.jar!/:2.7.4]
This way also does not work:
#ComponentScan(excludeFilters = {#ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*OAuth2ResourceServerJwtConfiguration.*")})
public class Application extends SpringBootServletInitializer { ...
The documentation I read before posting:
Spring Security without the WebSecurityConfigurerAdapter
Spring Security: Upgrading the Deprecated WebSecurityConfigurerAdapter
Spring Security - How to Fix WebSecurityConfigurerAdapter Deprecated
Update
I have created a small Maven project that demonstrates the issue. After project is started, request the controller like this:
$ wget -nv -O - 'http://localhost:8080/spring/test'
Username/Password Authentication Failed.
As one can see, the custom configured SecurityFilterChain is not active because otherwise the access would be granted (as to antMatchers( "/**/test").permitAll()). ContextRefreshedEvent listener dumps two SecurityFilterChain instances (jwtSecurityFilterChain and filterChain), the priority of them is not possible to configure reliably.
As follows from issue #33103, the beans imported from XML via #ImportResource do not participate in Spring Boot autoconfiguration, hence beans scanning should be performed using annotation i.e. #SpringBootApplication(scanBasePackages = "some.package") – this basically solves the issue. –
dma_k
Nov 17, 2022 at 9:21
Thanks to this reply I found the solution.
But since we are using different auth method so it might not work for you. I am using DaoAuthenticationProvider from this tutorial. Then rewrite to a new version without using "WebSecurityConfigurerAdapter".
Hope this solution helps. I got tortured 3-4 hours finding solution.
I created a subpackage called "config", on the same level with "models, controller, service, etc"(or whatever you name them).
Then, manully import this package by "scanBasePackages" along with others.
#SpringBootApplication(scanBasePackages = {"com.example.test.controller", "com.example.test.model", "com.example.test.repo", "com.example.test.service","com.example.test.config"})
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
Here are 3 rewritten files in my "config" folder. All rewrote from the tutorial mentioned above.
a.CustomUserDetails
package com.example.test.config;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.example.test.model.UserModel;
public class CustomUserDetails implements UserDetails {
//#Autowired no need cuz this is not a bean
private UserModel user;
public CustomUserDetails(UserModel user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getEmail();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public String getFullName() {
return user.getFirstName() + " " + user.getLastName();
}
}
b. CustomUserDetailsService
package com.example.test.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.example.test.repo.UserRepo;
import com.example.test.model.UserModel;
import org.springframework.stereotype.Service;
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepo userRepo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserModel user = userRepo.findByEmail(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return new CustomUserDetails(user);
}
}
c. SecurityConfiguration
package com.example.test.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
public class SecurityConfiguration{
#Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests ((requests)->requests
.requestMatchers("/", "/register", "/try2Register").permitAll()
.anyRequest().authenticated()
)
.formLogin((form)->form
.usernameParameter("email")
.defaultSuccessUrl("/users")
.permitAll()
)
.logout((logout) -> logout
.logoutSuccessUrl("/").permitAll());
http.authenticationProvider(authenticationProvider());
return http.build();
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/images/**", "/js/**", "/webjars/**");
}
}

Why is an InMemoryUserDetailsManager injected into this controller even though I've configured a JdbcUserDetailsManager elsewhere?

I'm trying to use Spring Security JDBC Authentication in a Spring Boot web app.
Here's the (much simplified, relevant) configuration:
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.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import javax.sql.DataSource;
#Configuration
public class SecurityConfiguration {
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
}
}
Here's a controller:
import org.adventure.inbound.UserFormData;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/users")
public class UserController {
private final UserDetailsManager userDetailsManager;
public UserController(UserDetailsManager userDetailsManager) {
this.userDetailsManager = userDetailsManager;
}
#PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<Void> registerUser(UserFormData userFormData) {
userDetailsManager.createUser(
User
.withUsername(userFormData.getUsername())
.password(userFormData.getPassword())
.authorities(new SimpleGrantedAuthority("ROLE_USER"))
.build());
return ResponseEntity.created(null).build();
}
}
When I start the app, I can see that:
The AuthenticationManagerBuilder gets a JDBCUDM configured:
but perhaps some step is missing?
The InMemoryUserDetailsManager is instantiated with Spring Security's default user:
The Controller, when instantiated, receives an InMemoryUserDetailsManager:
This means that when I try to create a new user, it uses the InMemoryUDM instead of the JDBCUDM that I'd like to use. Why is that?
Working solution
We already configure the UserDetailsService through JdbcUserDetailsManagerConfigurer, but didn't expose it as a bean.
#Autowired
#Bean
public UserDetailsManager configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
return jdbcUserDetailsManagerConfigurer.getUserDetailsService();
}
In your configuration override the userDetailsServiceBean(), and let it call the super method and add the #Bean annotation to make the configured service available. This is also explained here in the Javadocs.
#Configuration
public class SecurityConfiguration extends WebSecutiryConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
}
#Bean
public UserDetailsService userDetailsServiceBean()
throws java.lang.Exception {
return super.userDetailsServiceBean();
}
}
Also because you want to use your configuration instead of the Spring Boot one, you might need to add #EnableWebSecurity to disable the Spring Boot defaults.
Make your SecurityConfiguration extend from WebSecurityConfigurerAdapter, add #EnableWebSecurity and expose your JdbcUserDetailsManager as #Bean.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter {
#Bean
public JdbcUserDetailsManager userDetailsManager() {
return yourJdbcUserDetailsManager;
}
/// etc.
}

Spring-boot Digest authentication failure using Digest filter

I am new to this technology . I have trying to implement Digest Authentication for my Springboot application . I am getting below error while I am trying to call my application :There is no PasswordEncoder mapped for the id \"null\"","path":"/countryId/"}* Closing connection 0
curl command I am using to invoke : curl -iv --digest -u test:5f4dcc3b5aa765d61d8327deb882cf99 -d {"CountryCode": "INDIA"} http://localhost:9090/countryId/
Classes Details :
package com.sg.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.stereotype.Component;
#Component
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomDigestAuthenticationEntryPoint customDigestAuthenticationEntryPoint;
/*#Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}*/
#Bean
public UserDetailsService userDetailsServiceBean()
{
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("test").password("{noop}password").roles("USER").build());
return manager;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/hello/**").permitAll().anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(customDigestAuthenticationEntryPoint).and()
.addFilter(digestAuthenticationFilter());
}
//#Bean
DigestAuthenticationFilter digestAuthenticationFilter() throws Exception {
DigestAuthenticationFilter digestAuthenticationFilter = new DigestAuthenticationFilter();
digestAuthenticationFilter.setUserDetailsService(userDetailsServiceBean());
digestAuthenticationFilter.setAuthenticationEntryPoint(customDigestAuthenticationEntryPoint);
return digestAuthenticationFilter;
}
}
package com.sg.config;
import org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
#Component
public class CustomDigestAuthenticationEntryPoint extends DigestAuthenticationEntryPoint {
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("Digest-Realm");
setKey("MySecureKey");
setNonceValiditySeconds(300);
super.afterPropertiesSet();
}
}
I have resolved the issue. Let me explain what went wrong first, in current Spring security, you can not use a plain text password, so have to keep some encrypting logic. But unfortunately Digest doesn't work with a encrypted password.
I have found a work around, instead using a Bean (Bycrypt), I have directly implemented PasswordEncoder interface, in a way, it should able to hold plain text password.
#Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equals(encodedPassword);
}
};
}

Authentication issue in Spring Security (checking only username not password?)

this is my first project with Spring and I have just started to create the login with Spring Security. I want some pages to be accessible only for the admin and not for the players. I've found some examples on the web and this mechanism works pretty well, I have this secured page that is protected by the login and it's forbidden when the user has no ROLE_ADMIN.
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
#GetMapping("/secured/all")
public String securedHello() {
return "Secured Hello";
}
The problem is that testing my code I found out that Spring authenticates the admin (and the user as well) only checking the username. If I put the wrong password it allows me to enter anyway. I don't understand how this is possible, shouldn't Spring Security do all the authentication work by itself? I've seen somebody suggested to implement an authentication manager or something like that, but I don't understand why and how to insert it in my code. I'm stuck on this since two days, please any advice wuold be really appreciated.
These are my classes:
package model;
import java.io.IOException;
import javax.naming.AuthenticationException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.HttpStatus;
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.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.fasterxml.jackson.databind.ObjectMapper;
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#EnableJpaRepositories(basePackageClasses = PlayersRepository.class)
#ComponentScan(basePackageClasses= CustomUserDetailsService.class)
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(getPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
//http.csrf().disable();
http.authorizeRequests()
.antMatchers("**/secured/**").access("hasAuthority('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin().permitAll();
}
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;
}
};
}
}
package model;
import java.util.ArrayList;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
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;
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private PlayersRepository usersRepository;
#Autowired
private RoleRepository rolesRepository;
public CustomUserDetailsService(PlayersRepository usersRepository, RoleRepository rolesRepository) {
this.usersRepository=usersRepository;
this.rolesRepository=rolesRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Player> optionalUser = usersRepository.findByUsername(username);
optionalUser
.orElseThrow(() -> new UsernameNotFoundException("Username not found"));
Player user= optionalUser.get();
System.out.println(user);
return toUserDetails(new UserObject(user.getUsername(),user.getPassword(),user.getRole()));
}
private UserDetails toUserDetails(UserObject userObject) {
return User.withUsername(userObject.name)
.password(userObject.password)
.roles(userObject.role).build();
}
private static class UserObject {
private String name;
private String password;
private String role;
public UserObject(String name, String password, String role) {
this.name = name;
this.password = password;
this.role = role;
}
}
}
package model;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class CustomUserDetails extends Player implements UserDetails {
String role;
public CustomUserDetails(final Player user) {
super(user);
}
public CustomUserDetails(Optional<Player> user, String role) {
super(user);
this.role=role;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
list.add(new SimpleGrantedAuthority("ROLE_"+ role));
System.out.println(list);
return list;
}
#Override
public String getPassword() {
return super.getPassword();
}
#Override
public String getUsername() {
return super.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
Shouldn't Spring Security do all the authentication work by itself?
Yes, Spring Security does that for you using an AuthenticationManager.
I've seen somebody suggested to implement an authentication manager or something like that, but I don't understand why and how to insert it in my code.
You actually already have an AuthenticationManager, since you built one within the configure() method:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
}
So, what is exactly the reason this isn't working you may ask. Well, the AuthenticationManager you provided contains two parts:
A part that fetches the user information (CustomUserDetailsService)
Another part that checks the password (the getPasswordEncoder()).
What happens behind the screens is that Spring calls your CustomUserDetailsService to fetch your user information, including your (hashed) password. After fetching that information, it calls your PasswordEncoder.matches() function to verify if the raw entered password matches your hashed password provided by the CustomUserDetailsService.
In your case, your PasswordEncoder.matches() function looks like this:
#Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
This means that regardless of what password you provide, it will return true. This is exactly what you're experiencing since any password will work.
So, how do you solve this? Well, your PasswordEncoder should actually hash your raw password and compare it to the hashed password that is being passed, for example:
#Override
public boolean matches(CharSequence rawPassword, String hashedPassword) {
String hashedPassword2 = null; // hash your rawPassword here
return hashedPassword2.equals(hashedPassword);
}
The implementation of this method depends on how you store your password in your database. Spring Security already comes with a few implementation including BcryptPasswordEncoder, StandardPasswordEncoder, MessageDigestPasswordEncoder, ... . Some of these implementations are deprecated, mostly to indicate that the hashing mechanisms used by those encoders are considered unsafe. There are no plans at the moment of writing to remove those encoders, as mentioned by the Javadoc:
Digest based password encoding is not considered secure. Instead use an adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or SCryptPasswordEncoder. Even better use DelegatingPasswordEncoder which supports password upgrades. There are no plans to remove this support. It is deprecated to indicate that this is a legacy implementation and using it is considered insecure.
(Emphasis is my own)
If you are free to choose which implementation you pick, then Spring recommends using BCryptPasswordEncoder as mentioned by the Javadoc:
Service interface for encoding passwords. The preferred implementation is BCryptPasswordEncoder.
I just had a Quick scan I found this
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;
}
};
}
In your matches you are returning always true.
I guess here you should put logic for checking password for equality something like this
#Override
public boolean matches(CharSequence charSequence, String s) {
return charSequence.toString.equals(s);
}
I would suggest you use something like this
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

Update users informations during login against LDAP AD using Spring

I'm getting the users of my web-app from the Acive Directory.
So I created a custom UserDetailsContextMapper to save some data of the user to the web-app's MySql Database.
And this is my security configuration about Ldap:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth
.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("myDomain.local", "ldap://LDAP_IP:389/");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper());
return provider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new LdapUserDetailsContextMapper();
}
I would like to know when and if the data on the AD are changed from last login.
For example if today at 10:00AM I was member of group A inside the AD and now I'm member of group A and B, I would like to update the authorities on MySql.
Is there a field or something inside AD to know that?
EDIT:
I would like to check if something change for a particulare user during the login phase, in this way I can update the information on MySql.
To find when a user was last modified, you can use the "whenchanged" attribute.
if you extend LdapUserDetailsMapper, and override the mapUserFromContext, it might look like this:
package example.active.directory.authentication;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collection;
public class CustomUserMapper extends LdapUserDetailsMapper{
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities){
UserDetails details = super.mapUserFromContext(ctx, username, authorities);
String[] changedValues = ctx.getStringAttributes("whenchanged");
if(changedValues != null && changedValues.length > 0){
LocalDateTime lastChangedTime = Arrays.stream(changedValues)
.map(input ->
OffsetDateTime.parse(
input,
DateTimeFormatter.ofPattern("uuuuMMddHHmmss[,S][.S]X")
).toLocalDateTime()
)
.sorted((a, b) -> a.compareTo(b) * -1)
.findFirst()
.orElse(null);
System.out.println(lastChangedTime);
//Do something with value?
}
return details;
}
}

Resources