PasswordEncoder如何在Spring Security中调用?

时间:2014-04-12 18:41:14

标签: spring spring-mvc spring-security

我尝试使用spring boot(1.0.0,使用启动Web和安全性),并拥有一个可用的Web应用程序,其安全端点通过这样的用户进行身份验证。此代码有效(为简洁起见,省略了HttpSecurity),要求对某些端点的http请求使用用户名/密码:

@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}

为了准备将spring安全性与数据库中的用户一起使用,我将安全部分放在一起,我觉得应该可以工作,但是根本没有任何数据库。这纯粹是为了试验安全相关的类。

因此上面用于配置AuthenticationManagerBuilder的代码更改为:

// private test implementations so we can explore security without a database
// here all usernames and passwords are valid
// org.springframework.security.crypto.password.PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
    return new PasswordEncoder() {
        @Override public String encode(CharSequence cs) {
            return cs.toString();
        }
        @Override public boolean matches(CharSequence cs, String string) {
            return true;
        }
    };
}

@Bean
public UserDetailsService createUserDetailsService() {
    return new UserDetailsService() {
        @Override
        public UserDetails loadUserByUsername(String string) throws UsernameNotFoundException {
            return new User(); // a trivial implementation of UserDetails
        }
    };
}

@Bean
@Inject
public DaoAuthenticationProvider createDaoAuthenticationProvider(UserDetailsService service, PasswordEncoder encoder) {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(service);
    provider.setPasswordEncoder(encoder);
    return provider;
}

@Bean
@Inject
public AuthenticationManager authenticationManager(AuthenticationProvider provider) throws Exception {
    // includes a trivial implementation of ObjectPostProcessor
    return new AuthenticationManagerBuilder(new NopPostProcessor())
            .authenticationProvider(provider)
            .build();
}

我的自定义用户详细信息服务被调用但我的自定义密码编码器从未被调用过,我通过在适当的行设置断点来验证这一点。身份验证总是失败。

如何调用密码编码器?不应该通过每个http请求的身份验证来调用它吗?我是否应该期望使用上述配置调用它?

4 个答案:

答案 0 :(得分:3)

AbstractUserDetailsAuthenticationProvider的authenticate方法(由DaoAuthenticationProvider扩展) 就像这样

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
            messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
                "Only UsernamePasswordAuthenticationToken is supported"));

        // Determine username
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;

            try {
                user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
            } catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                } else {
                    throw notFound;
                }
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
        } catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                // There was a problem, so try again after checking
                // we're using latest data (i.e. not from the cache)
                cacheWasUsed = false;
                user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
            } else {
                throw exception;
            }
        }

        postAuthenticationChecks.check(user);

        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

正如您在加载并确认存在于存储库中的用户所看到的那样,可以调用additionalAuthenticationChecks。如果找不到该用户,则不会进行附加检查。在附加检查方法中使用密码编码器。这是DaAuthenticationProvider中的实现

 @SuppressWarnings("deprecation")
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        Object salt = null;

        if (this.saltSource != null) {
            salt = this.saltSource.getSalt(userDetails);
        }

        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
        }

        String presentedPassword = authentication.getCredentials().toString();

        if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
        }
    }

正如您所看到的,密码编码器用于匹配存储在存储库中的密码。因此,如果没有调用密码编码器,一个可能的原因可能是找不到用户。

答案 1 :(得分:1)

最终的答案是“下载弹簧安全源并调试它以找出正在发生的事情。”

我这样做并立即偶然发现了一个AccountStatusException ...我的简单UserDetails实现默认将其enabled标志设置为false,因此在用户检查失败后,密码永远不会被验证。以下是我所有问题的实际答案:

问:如何调用密码编码器?

A:逐步完成AbstractUserDetailsAuthenticationProvider

中的源代码

问:不应该通过每个http请求的身份验证来调用它吗?

答:不,根据需要调用它(在检索并验证用户启用后)

问:我是否希望使用上述配置调用它?

答:就编码器和用户服务的布线而言,配置是令人满意的,但配置并不能确保它被调用。

答案 2 :(得分:0)

我将其更改为:

@Bean
public PasswordEncoder passwordEncoder() { ... }

@Bean
public UserDetailsService userDetailsService() { ... }

@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(this.userDetailsService());
    provider.setPasswordEncoder(this.passwordEncoder());
    return provider;
}

@Bean
public AuthenticationManager authenticationManager() throws Exception {
    return new AuthenticationManagerBuilder(new NopPostProcessor())
            .authenticationProvider(this.daoAuthenticationProvider())
            .build();
}

无论如何,您应该使用自定义AuthenticationManagerBuilder配置userDetailsService

auth.userDetailsService(this.userDetailsService())

答案 3 :(得分:0)

我遇到了同样的问题。 我意识到UsernamePasswordAuthenticationFilter.obtainPassword(请求)返回null。这是后来验证失败的原因。

我的错误是我的登录表单中没有“密码”字段。 解决方案:在表单的密码字段中添加id和name属性,并将其值设置为'passwordParameter'值,默认为'password'。

首先检查一下这个基本问题。 希望它有所帮助!