【问题标题】:Spring authentication process failsSpring认证过程失败
【发布时间】:2020-08-28 01:47:54
【问题描述】:

早上好,

我正在使用 Spring Security 来保护 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中获取认证记录(实际上是一种用户名);
  • qualifica:用于为每个用户设置权限(例如,如果“qualifica”=“admin”,则用户具有管理员权限)。

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

【问题讨论】:

  • 这里已经有很多信息了,但是如果你添加一些 Spring Security 日志,诊断问题会(甚至)更容易。将“logging.level.org.springframework.security=debug”放入你的application.properties,再次运行应用程序,尝试登录并添加你看到的日志。
  • @neofelis,在此先感谢,我添加了来自 Spring Security 的日志。我使用 log4j2 来获取它们,我还根据新发现相应地编辑了帖子。

标签: java spring authentication spring-security


【解决方案1】:

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

来自MDN reference for the HTML input element

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

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

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

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

编辑:This StackOverflow answer 帮助我确定了问题,Spring Security 文档的相关位是this,特别是示例 53。

【讨论】:

  • 你完全正确,我的错是忘记了输入标签中的“名称”属性。此外,我尝试仅使用 auth.authenticationProvider(authenticationProvider()); 行登录而且它实际上没有问题,所以只需要一种方法,就像你说的那样,再次感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-15
  • 2020-11-14
  • 2021-03-07
  • 2019-09-03
  • 2016-05-30
  • 2014-07-19
相关资源
最近更新 更多