Reputation: 325
Good morning,
I am using Spring Security in order to secure a web application, the problem comes in the authentication process. The flow start from the login page (displayed correctly), when I type in "2" as a username and "ciao" as a password (I explain ho I defined it later), I am redirected to the login failed page and it seems no users where saved in the Database (although no errors or exception are shown in logs).
I have a database table (MySQL is used) and I want to authenticate users using it, the table construction script is
CREATE TABLE IF NOT EXISTS `SR002`.`utenti` (
`id_utente` INT NOT NULL AUTO_INCREMENT,
`nome` VARCHAR(45) NOT NULL,
`cognome` VARCHAR(45) NOT NULL,
`password` VARCHAR(60) NOT NULL,
`qualifica` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id_utente`))
ENGINE = InnoDB;
where:
I manually added a record encrypting password with BCryptEncoder:
I found out that spring security provides JDBC authentication for this purpose and following tutorials on the internet I come up with the following configuration and userful classes.
Security configuration class
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
@Qualifier("securityUserDetailService")
UserDetailsService userDetailsService;
@Autowired
DataSource dataSource;
/**
* Manages the encoder to save passwords in the DB
* not in plain text
*
* @return PasswordEncoder object
*/
@Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
/**
* Manages the configuration of the Authentication manager with
* user credentials and roles.
* <p>
* The AuthenticationManager processes any authentication request.
*
* @param auth AuthenticationManagerBuilder object
* @throws Exception exception
*/
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception
{
auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder);
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
/**
* Manages the configuration for specific http request.
*
* @param http HttpSecurity request
* @throws Exception exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests()
//configure security for pages
.antMatchers(new String[]{"/login", "/accessDenied"}).permitAll()
.antMatchers("/**").access("hasRole('admin')")
.anyRequest().authenticated()
//creates login form
.and().formLogin().loginPage("/login").loginProcessingUrl("/login")
.defaultSuccessUrl("/home").failureUrl("/accessDenied")
.usernameParameter("id_utente").passwordParameter("password")
//catches exceptions http 403 response
.and().exceptionHandling().accessDeniedPage("/accessDenied");
}
/**
* Manages the storage of user credentials inside database
*
* @return The authenticationProvider Object
*/
@Bean
public DaoAuthenticationProvider authenticationProvider()
{
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder);
return authenticationProvider;
}
@Bean
public AuthenticationTrustResolver getAuthenticationTrustResolver()
{
return new AuthenticationTrustResolverImpl();
}
}
Custom user detail service
@Service("securityUserDetailService")
public class SecurityUserDetailService implements UserDetailsService
{
@Autowired
UtentiService utentiService;
/**
* Manages the load of a user along with
* ID and password.
*
* @param s user id
* @return UserDetail object with ID and password
* @throws UsernameNotFoundException If user is not found in the system
*/
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException
{
int id = Integer.parseInt(s);
Utenti user = utentiService.findUserById(id);
if(user == null)
{
throw new UsernameNotFoundException("User with id " + s + " not found");
}
return new User(Integer.toString(user.getId_utente()), user.getPassword(), true, true, true, true, getGrantedAuthorities(user));
}
/**
* Determines which roles a particular user has
*
* @param user The user for which to find roles
* @return List containing all roles related to the user
*/
private List<GrantedAuthority> getGrantedAuthorities(Utenti user)
{
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getQualifica()));
return authorities;
}
}
I do not know where the problem is, but I think it might be in the global configuration, I may be missing something about this method as I am new to Spring security:
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception
{
auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder);
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
How these configurations hav to be changed in order to authenticate user with JDBC and MySQL table? And There is a preferred way to insert the first (normally admin) user?
EDIT
Logs from spring security state:
15:27:38.660 [http-nio-8080-exec-8] ERROR org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter - An internal error occurred while trying to authenticate the user.
org.springframework.security.authentication.InternalAuthenticationServiceException: For input string: ""
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:123) ~[spring-security-core-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:144) ~[spring-security-core-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:199) ~[spring-security-core-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:219) ~[spring-security-core-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:95) ~[spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:141) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) [spring-security-web-5.3.1.RELEASE.jar:5.3.1.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) [spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) [spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [catalina.jar:9.0.33]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [catalina.jar:9.0.33]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) [catalina.jar:9.0.33]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [catalina.jar:9.0.33]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [catalina.jar:9.0.33]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [catalina.jar:9.0.33]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [catalina.jar:9.0.33]
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:688) [catalina.jar:9.0.33]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [catalina.jar:9.0.33]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [catalina.jar:9.0.33]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) [tomcat-coyote.jar:9.0.33]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-coyote.jar:9.0.33]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-coyote.jar:9.0.33]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1594) [tomcat-coyote.jar:9.0.33]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-coyote.jar:9.0.33]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) [?:?]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) [?:?]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-util.jar:9.0.33]
at java.lang.Thread.run(Thread.java:844) [?:?]
Caused by: java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[?:?]
at java.lang.Integer.parseInt(Integer.java:662) ~[?:?]
at java.lang.Integer.parseInt(Integer.java:770) ~[?:?]
at com.entsorgafin.service.impl.security.SecurityUserDetailService.loadUserByUsername(SecurityUserDetailService.java:36) ~[classes/:?]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
at java.lang.reflect.Method.invoke(Method.java:564) ~[?:?]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:366) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) ~[spring-tx-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at com.sun.proxy.$Proxy71.loadUserByUsername(Unknown Source) ~[?:?]
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:108) ~[spring-security-core-5.3.1.RELEASE.jar:5.3.1.RELEASE]
... 43 more
In particular, lines:
Caused by: java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[?:?]
at java.lang.Integer.parseInt(Integer.java:662) ~[?:?]
at java.lang.Integer.parseInt(Integer.java:770) ~[?:?]
at com.entsorgafin.service.impl.security.SecurityUserDetailService.loadUserByUsername(SecurityUserDetailService.java:36) ~[classes/:?]
state that the error comes from the line
int id = Integer.parseInt(s);
In the loadUserByUsername
method, now i do not understand how this method is getting an empty string "" instead of the "2" I type in the form.
Form Page
<c:url var="loginUrl" value="/login" />
<div class="leftBar">
<h2>Login</h2>
<p>testo esempio</p>
</div>
<div class="rightBar">
<div class="col-md-7 col-sm-12">
<form:form method="post" action="${loginUrl}">
<div class="form-group">
<label for="userId">ID utente</label>
<input type="text" class="form-control" id="userId" placeholder="ID utente">
</div>
<div class="form-group">
<label for="userPwd">Password</label>
<input type="password" class="form-control" id="userPwd" placeholder="Password">
</div>
<button type="submit" name="submit" value="submit" class="btn">Accedi</button>
</form:form>
</div>
</div>
Upvotes: 1
Views: 1097
Reputation: 670
Your main problem is with your form page.
From the MDN reference for the HTML input element:
Consider the name a required attribute (even though it's not). If an input has no name specified, or name is empty, the input's value is not submitted with the form!
None of your <input>
elements have names, so nothing is getting submitted. (You can turn on the developer tools in your browser and inspect the request that it's sending to make sure that this is, in fact, the problem.)
In your configuration (specifically the configure(HttpSecurity http)
method in SecurityConfiguration
) you are using the usernameParameter
and passwordParameter
methods. So the input field for the username should have name="id_utente"
and the input field for the password should have name="password"
. (If I understand what those methods do, anyway; I haven't used them myself.)
There is one further issue with your configuration which is probably not related to your current problem. (Maybe there are also other issues -- I'm fairly new to Spring Security myself -- but this is one.) You use the methods jdbcAuthentication
, authenticationProvider
and userDetailsService
on your AuthenticationManagerBuilder
. You only need one of them. Remove the other two. This will help with readability, but that's not the only reason. When you configure multiple authentication providers Spring will try them in sequence. In your specific case, I think this means that you're hitting the database multiple times per failed login attempt.
Edit: This StackOverflow answer helped me identify the issue, and the relevant bit of Spring Security documentation is this, specifically Example 53.
Upvotes: 1