spring security,UserDetailsS​​ervice,authenticationProvider,pass word encoder ..我迷路了

时间:2016-08-12 18:15:51

标签: spring spring-security

首先,我已经阅读/重新阅读(重复10次),至少有6本关于春季和春季安全的书籍,并且搜索了我的大脑,试图弄明白这一切。

在使用Spring工作了10年之后,我仍然发现有太多的注释定义,注入,组件,配置注释魔术正在进行,我有信心,我理解我应用程序应该如此。 在线示例是xml-config,不完整,完成n diff。方式,过于简单化,使用较旧的弹簧,冲突,只是没有建立来处理基本的现实用例。

作为示例,以下代码尝试处理简单登录,使用密码编码器对db表进行身份验证。 表单帖子包括一个“客户端”,其中一个进行身份验证,一个持久的IP地址和一些URL链接后登录的URL链接信息。 (今天的单页网页应用程序的所有基本内容) 我最初使用xml配置工作,但javaConfig让我卡住了。

我不知道userDetailsS​​ervice,AuthenticationManagerBuilder和PasswordEncoder如何在SecurityConfiguration中进行交互。我获得了服务的登录数据,但我不确定应用spring authenticationProvider的地点或时间,或者我甚至需要一个。

我的用户实现UserDetails并保存必填字段。 我填充那些并在我的CustomUserDetailsS​​ervice中授予权限。 我如何/何时/为什么需要auth.authenticationProvider(authenticationProvider()),如果我在服务中使用登录/密码检查数据库?

我的UserDetailsS​​ervice现在似乎正在执行两次。

spring如何获取提交的密码,对其进行编码并与存储在db中的密码进行比较? 如何使用与创建用户时创建/保留p / w时使用的盐相同的盐?

当authenticationProvider()也设置userDetailsS​​ervice时,为什么configureGlobal()定义了auth.userDetailsS​​ervice和auth.authenticationProvider?

为什么我的大脑如此之小以至于我无法理解这一点? :)

@Service
public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private ClientDAO clientDAO;
@Autowired
private UserDAO userDAO;


public UserDetails loadUserByUsername(String multipartLogon) throws UsernameNotFoundException, DataAccessException {

    Boolean canAccess = false;
    Long clientId = null;
    String userLogon = null;
    String password = null;
    String id = null;
    String entryUrl = null;
    String ipAddress = null;
    String urlParam = null;
    String[] strParts = multipartLogon.split(":");
    try {
        userLogon = strParts[0];
        password = strParts[1];
        id = strParts[2];
        entryUrl = strParts[3];
        ipAddress = strParts[4];
        urlParam = strParts[5];
    } catch(IndexOutOfBoundsException ioob) { }
    Client client = new Client();
    if (!"".equals(id)) {
        clientId = IdUtil.toLong(id);
        client = clientDAO.getClient(clientId);
    }

    //BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    //String encodedPassword = passwordEncoder.encode(password);

    //String encodedPassword = "$2a$22$6UiVlDEOv6IQWjKkLm.04uN1yZEtkepVqYQ00JxaqPCtjzwIkXDjy";


    User user = userDAO.getUserByUserLogonPassword(userLogon, password); //encodedPassword?
    user.isCredentialsNonExpired = false;
    Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
    for (UserRole userRole : userDAO.getUserRolesForUser(user)) {
        if (userRole.getRole().getActiveStatus()) {
            authorities.add(new SimpleGrantedAuthority(userRole.getRole().getRoleName()));
            user.isCredentialsNonExpired = true;
        }
    }       
    user.setAuthorities(authorities);
    user.setPassword(password); //encodedPassword?
    user.setUsername(user.getUserLogon());
    user.isAccountNonExpired = false;
    user.isAccountNonLocked = false;

    List<ClientUser> clientUsers = clientDAO.getClientUsersForUser(user);
    for (ClientUser clientUser : clientUsers) {
        if (clientUser.getClient().getClientId().equals(client.getClientId())) {
            canAccess = true;
            break;
        }
    }

    user.isEnabled = false;
    if (user.getActiveStatus() && canAccess) {
        user.isAccountNonExpired = true;
        user.isAccountNonLocked = true;
        user.isEnabled = true;

        Session session = userDAO.getSessionForUser(user);
        if (session == null) { session = new Session(); }
        session.setUser(user);
        session.setDateLogon(Calendar.getInstance().getTime());
        session.setClient(client);
        session.setEntryUrl(entryUrl);
        session.setUrlParam(urlParam);
        session.setIPAddress(ipAddress);
        session.setActive(true);
        userDAO.persistOrMergeSession(session);
    }
    return user;
}

}
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
CustomUserDetailsService customUserDetailsService;


@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(customUserDetailsService);
    auth.authenticationProvider(authenticationProvider());
}


@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}


@Bean
public DaoAuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(customUserDetailsService);
    authenticationProvider.setPasswordEncoder(passwordEncoder());
    return authenticationProvider;
}


@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()

        .authorizeRequests()
            .antMatchers("/conv/a/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_COURT_ADMIN')")
            .antMatchers("/conv/u/**").access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN') or hasRole('ROLE_COURT_ADMIN')")
            .antMatchers("/**").permitAll()
            .and()

        .formLogin()
            .loginPage("/conv/common/logon")
            .usernameParameter("multipartLogon")
            .loginProcessingUrl("/conv/common/logon")
            .defaultSuccessUrl("/conv/")
            .failureUrl("/conv/common/logon?error=1")
            .and()

        .logout()
            .logoutUrl("/conv/common/logout")
            .logoutSuccessUrl("/conv/")
            .permitAll()
            .and()

        .rememberMe()
            .key("conv_key")
            .rememberMeServices(rememberMeServices())
            .useSecureCookie(true);
}


@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring()
        .antMatchers("/common/**")
        .antMatchers("/favicon.ico");
}


@Bean
public RememberMeServices rememberMeServices() {
    TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("conv_key", customUserDetailsService);
    rememberMeServices.setCookieName("remember_me_cookie");
    rememberMeServices.setParameter("remember_me_checkbox");
    rememberMeServices.setTokenValiditySeconds(2678400); //1month
    return rememberMeServices;
}

}

2 个答案:

答案 0 :(得分:3)

  

我的用户实现UserDetails并保存必填字段。一世   在我的CustomUserDetailsS​​ervice中填充那些并授予权限。   怎么/何时/为什么我需要一个   auth.authenticationProvider(authenticationProvider()),如果我检查db   在我的服务中使用登录/密码?

我认为你想要的是:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}

userDetailsS​​ervice 方法是创建DaoAuthenticationProvider bean的快捷方式!您不应该同时使用两者,只需两种不同的方式来配置相同的东西。 authenticationProvider 方法用于更多自定义设置。

  

spring如何获取提交的密码,对其进行编码并进行比较   存储在db中的?怎么知道使用相同的盐   在创建用户时创建/保留p / w时使用?

如果您使用BCrypt,则盐将存储在编码的密码值中。盐是第三个$(美元)符号后的前22个字符。 matches方法负责检查密码。

  

为什么configureGlobal()定义了auth.userDetailsS​​ervice和   当authenticationProvider()也设置时,auth.authenticationProvider   userDetailsS​​ervice?

见上文。这可能是用户详细信息加载两次的原因。

更新:您在UserDetailsS​​ervice中获取密码和其他详细信息很奇怪。这应该只根据用户名加载用户,例如:

User user = userDAO.getUserByUserLogonPassword(userLogon);

返回的User对象应包含已编码(存储)的密码,而不是输入的密码。 Spring Security会为您进行密码检查。您不应该修改UserDetailsS​​ervice中的User对象。

答案 1 :(得分:1)

哇,好的,那是很多问题。我会跟这个说话:

“我不知道userDetailsS​​ervice,AuthenticationManagerBuilder和PasswordEncoder是如何”

UserDetailsS​​ervice设置您可以从Spring访问的User。如果您希望在用户的上下文中存储更多用户信息,则需要实现自己的用户并使用自定义用户详细信息服务进行设置。 e.g。

public class CustomUser extends User implements UserDetails, CredentialsContainer {
private Long id;
private String firstName;
private String lastName;
private String emailAddress;
....

然后,在您的自定义UserDetailsS​​ervice中,设置属性:

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DatabaseEntity databaseUser = this.userRepository.findByUsernameIgnoreCase(username);

customUser customUser = databaseUser.getUserDetails();
customUser.setId(databaseUser.getId());
customUser.setFirstName(databaseUser.getFirstname());
.....

密码编码器是Spring用于将纯文本密码与数据库中的加密哈希进行比较的机制。您可以使用BCryptPasswordEncoder:

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

除了将其传递给您的身份验证提供程序之外,您还需要执行更多操作。

最后,configureGlobal是您连接的地方。您定义了Spring要使用的用户详细信息服务和身份验证提供程序。

就我而言,我使用自定义身份验证提供程序来限制登录尝试失败:

@Component("authenticationProvider")
public class LimitLoginAuthenticationProvider extends DaoAuthenticationProvider {

然后我连线了所有内容:

@Autowired
@Qualifier("authenticationProvider")
AuthenticationProvider authenticationProvider;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    LimitLoginAuthenticationProvider provider = (LimitLoginAuthenticationProvider)authenticationProvider;
    provider.setPasswordEncoder(passwordEncoder);
    auth.userDetailsService(customUserDetailsService()).passwordEncoder(passwordEncoder);
    auth.authenticationProvider(authenticationProvider);
}