Spring 身份验证过程失败



早上好,

我正在使用 Spring 安全性来保护 Web 应用程序,问题出在身份验证过程中。流程从登录页面(正确显示(开始,当我输入"2"作为用户名和"ciao"作为密码时(我解释我稍后定义它(,我被重定向到登录失败页面,似乎没有用户保存在数据库中(尽管日志中没有显示错误或异常(。

我有一个数据库表(使用MySQL(,我想使用它对用户进行身份验证,表构造脚本是

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;

哪里:

  • id_utente:用于从DB中检索认证记录(实际上是一种用户名(;
  • 限定:用于为每个用户设置权限(例如,如果"限定"="管理员",则用户具有管理员权限(。

我手动添加了一个使用 BCryptEncoder 加密密码的记录:

  • id_utente = 2
  • nome = 名称
  • 同名 = 姓氏
  • 密码 = $2y$04$atmkYeXsf8f7R6c7LU5fZ.lTuae5Pem3c303mG3P6UVbWMLxtgrSK(它是散列词"ciao"(
  • 资格 = 管理员

我发现 spring security 为此目的提供了 JDBC 身份验证,按照互联网上的教程,我想出了以下配置和有用的类。

安全配置类

@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();
}
}

自定义用户详细信息服务

@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;
}
}

我不知道问题出在哪里,但我认为它可能在全局配置中,我可能缺少有关此方法的一些内容,因为我是 Spring 安全性的新手:

@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception
{
auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder);
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}

如何更改这些配置以使用 JDBC 和 MySQL 表对用户进行身份验证? 并且有插入第一个(通常是管理员(用户的首选方法吗?

编辑

来自 Spring 安全状态的日志:

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 

特别是行:

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/:?]

声明错误来自该行

int id = Integer.parseInt(s);

loadUserByUsername方法中,现在我不明白此方法如何获得空字符串"而不是我在表单中键入的"2"。

表单页面

<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>

您的主要问题在于表单页面。

从 HTML 输入元素的 MDN 引用:

将名称视为必需属性(即使它不是(。如果输入未指定名称,或者名称为空,则输入的值不会随表单一起提交!

您的<input>元素都没有名称,因此不会提交任何内容。(您可以在浏览器中打开开发人员工具并检查它发送的请求,以确保这确实是问题所在。

在您的配置(特别是SecurityConfiguration中的configure(HttpSecurity http)方法(中,您使用的是usernameParameterpasswordParameter方法。因此,用户名的输入字段应具有name="id_utente",密码的输入字段应具有name="password"。(无论如何,如果我了解这些方法的作用;我自己没有用过它们。

您的配置还有一个问题,可能与您当前的问题无关。(也许还有其他问题 - 我自己对Spring Security相当陌生 - 但这是一个。您可以使用jdbcAuthenticationauthenticationProvideruserDetailsService的方法AuthenticationManagerBuilder。您只需要其中之一。删除其他两个。这将有助于提高可读性,但这不是唯一的原因。当您配置多个身份验证提供程序时,Spring 将按顺序尝试它们。在您的特定情况下,我认为这意味着您每次失败的登录尝试都会多次访问数据库。

编辑:这个StackOverflow答案帮助我确定了问题,Spring Security文档的相关部分是这样的,特别是示例53。

最新更新