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。於是自定義的Custom UserDetailsService類只需要覆寫這兩個方法即可
@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 
 
嘿嘿,如果覺得以上實例項目中可以借鑒的話,不妨打個賞吧
                     
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM