zoukankan      html  css  js  c++  java
  • 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 
     
    嘿嘿,如果觉得以上实例项目中可以借鉴的话,不妨打个赏吧
                         
     
     
  • 相关阅读:
    数学归纳法证明等值多项式
    整值多项式
    同余式
    欧拉定理&费马定理
    与模互质的剩余组
    欧拉函数的性质
    欧拉函数计数定理
    完全剩余组高阶定理
    51nod 1488 帕斯卡小三角 斜率优化
    51nod 1577 异或凑数 线性基的妙用
  • 原文地址:https://www.cnblogs.com/hongxf1990/p/6756520.html
Copyright © 2011-2022 走看看