spring security使用数据库验证的逻辑处理

前面做了多个示例,包括使用jdbc和hibernate两种方式访问数据库获取用户信息和权限信息,其中一些关键步骤如下:
 
我们在SecurityConfig中配置覆盖configure方法时候,可以指定authenticationProvider,也可以不需要指定,直接指定userDetailsService。例如:
@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        auth.authenticationProvider(authenticationProvider);
        //auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

 

如果没有指定authenticationProvider,则security使用的是实现类DaoAuthenticationProvider。
如果指定自定义的authenticationProvider,为了方便,我们自定义的authenticationProvider也是继承自DaoAuthenticationProvider,只需要重写指定userDetailsService,authenticate方法,例如:
@Autowired
    @Qualifier("userDetailsService")
    @Override
    public void setUserDetailsService(UserDetailsService userDetailsService) {
        super.setUserDetailsService(userDetailsService);
    }

@Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        try {
            //调用上层验证逻辑
            Authentication auth = super.authenticate(authentication);
            //如果验证通过登录成功则重置尝试次数, 否则抛出异常
            userDetailsDao.resetFailAttempts(authentication.getName());
            return auth;
        } catch (BadCredentialsException e) {
            //如果验证不通过,则更新尝试次数,当超过次数以后抛出账号锁定异常
            userDetailsDao.updateFailAttempts(authentication.getName());
            throw e;
        } catch (LockedException e){
            //该用户已经被锁定,则进入这个异常
            String error;
            UserAttempts userAttempts =
                    userDetailsDao.getUserAttempts(authentication.getName());
            if(userAttempts != null){
                Date lastAttempts = userAttempts.getLastModified();
                error = "用户已经被锁定,用户名 : "
                        + authentication.getName() + "最后尝试登陆时间 : " + lastAttempts;
            }else{
                error = e.getMessage();
            }
            throw new LockedException(error);
        }
    }

在此方法中,仍然调用的是上层验证方法super.authenticate();在这里可以根据不同的验证异常抛出不同的异常,从而显示不同的用户账号状态,例如用户被锁定、用户失效、账号或者密码过期等,这里例子是多次登录失败锁定了用户。
 
下面我们看看security是如何验证账号的:
验证逻辑实现是在类AbstractUserDetailsAuthenticationProvider,此类实现了接口AuthenticationProvider的接口方法
Authentication authenticate(Authentication authentication)  throws AuthenticationException;

实现方法中首先获取security定义的接口UserDetails,先从缓存userCache中获取,如果不存在,则调用方法retrieveUser。

retrieveUser的方法实现是在类DaoAuthenticationProvider,这个方法中可以看到
UserDetails loadedUser;
        try {
            loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        }
        catch (UsernameNotFoundException notFound) {
            if (authentication.getCredentials() != null) {
                String presentedPassword = authentication.getCredentials().toString();
                passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
                        presentedPassword, null);
            }
            throw notFound;
        }
.....

此处调用的是自定义的UserDetailsService中loadUserByUsername方法。于是可以看出自定义的UserDetailsService实现类关键是实现loadUserByUsername方法。

 
下面就两种方式的实现进行剖解:
1、使用jdbc方式时,我们自定义的UserDetailsService是继承了类JdbcDaoImpl,可以发现JdbcDaoImpl已经实现了接口UserDetailsService,实现了方法loadUserByUsername。
在实现方法中,关键是调用自己定义的两个方法loadUsersByUsername和createUserDetails。于是自定义的CustomUserDetailsService类只需要覆写这两个方法即可
@Override
    protected List<UserDetails> loadUsersByUsername(String username) {
        return getJdbcTemplate().query(super.getUsersByUsernameQuery(), new Object[]{username},
                (rs, rowNum) -> {
                    String username1 = rs.getString("username");
                    String password = rs.getString("password");
                    boolean enabled = rs.getBoolean("enabled");
                    boolean accountNonExpired = rs.getBoolean("accountNonExpired");
                    boolean credentialsNonExpired = rs.getBoolean("credentialsNonExpired");
                    boolean accountNonLocked = rs.getBoolean("accountNonLocked");
                    return new User(username1, password, enabled, accountNonExpired, credentialsNonExpired,
                            accountNonLocked, AuthorityUtils.NO_AUTHORITIES);
                });
    }
    @Override
    protected UserDetails createUserDetails(String username, UserDetails userFromUserQuery, List<GrantedAuthority> combinedAuthorities) {
        String returnUsername = userFromUserQuery.getUsername();
        if (!super.isUsernameBasedPrimaryKey()) {
            returnUsername = username;
        }
        return new User(returnUsername, userFromUserQuery.getPassword(),
                userFromUserQuery.isEnabled(),
                userFromUserQuery.isAccountNonExpired(),
                userFromUserQuery.isCredentialsNonExpired(),
                userFromUserQuery.isAccountNonLocked(), combinedAuthorities);
    }

在这里我们根据需要指定各个值,例如用户名,密码,是否可用,账号和密码是否过期,是否账号被锁等,所以如果已经设计完成的数据表中字段名称不一致也没有关系,只要含义相同,获取值指定即可。

2、使用hibernate方式时,需要自己实现UserDetailsService接口中的方法loadUserByUsername:
@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userDao.findByUserName(username);
        if (user == null) {
            throw new UsernameNotFoundException("该用户不存在:" + username);
        }
        List<GrantedAuthority> authorities =  buildUserAuthority(user.getUserRole());
        return buildUserForAuthentication(user, authorities);
    }
    // 把自定义的User转换成org.springframework.security.core.userdetails.User
    private org.springframework.security.core.userdetails.User buildUserForAuthentication(
            User user,
            List<GrantedAuthority> authorities) {
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
                user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), authorities);
    }
    private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
        Set<GrantedAuthority> setAuths = new HashSet<>();
        // Build user's authorities
        for (UserRole userRole : userRoles) {
            setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
        }
        return new ArrayList<>(setAuths);
    }

在方法中使用hibernate的方式获取自定义的User实例,然后转换成security中的org.springframework.security.core.userdetails.User即可,org.springframework.security.core.userdetails.User是接口UserDetails的实现类。

 
附上所有示例的代码的github地址: https://github.com/hongxf1990/spring-security-learning 
 
嘿嘿,如果觉得以上实例项目中可以借鉴的话,不妨打个赏吧
                     
 
 
posted @ 2017-04-24 13:50  飞天0407  阅读(3301)  评论(0编辑  收藏  举报