【權限管理系統】Spring security(三)---認證過程(原理解析,demo)

在前面兩節Spring security (一)架構框架-Component、Service、Filter分析Spring Security(二)--WebSecurityConfigurer配置以及filter順序爲Spring Security認證做好了準備,可讓咱們更好的理解認證過程以及項目代碼編寫。html

1.認證過程工做流程

認證工做流程:  算法

 AbstractAuthenticationProcessingFilter doFilter()(attemptAuthentication()獲取Authentication實體) ->UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter的子類) attemptAuthentication() (在UsernamePasswordAuthenticationToken()中將username 和 password 生成 UsernamePasswordAuthenticationToken對象,getAuthenticationManager().authenticate進行認證以及返回獲取Authentication實體) ->AuthenticationManager ->ProviderManager()(AuthenticationManager接口實現) authenticate()(AuthenticationProvider.authenticate()進行認證並獲取Authentication實體) ->AbstractUserDetailsAuthenticationProvider(內置緩存機制,若是緩存中沒有用戶信息就調用retrieveUser()獲取用戶) authenticate() (獲取Authentication實體須要userDetails,在緩存中或者retrieveUser()獲取userDetails;驗證additionalAuthenticationChecks(); createSuccessAuthentication()生成Authentication實體) ->DaoAuthenticationProvider retrieveUser() (調用自定義UserDetailsService中loadUserByUsername()加載userDetails) ->UserDetailsService loadUserByUsername()(獲取userDetails)

具體流程請看下面小節。數據庫

1.1:請求首先通過過濾器AbstractAuthenticationProcessingFilter以及UsernamePasswordAuthenticationFilter進行處理

當請求來臨時,在默認狀況下,請求先通過AbstractAuthenticationProcessingFilter的子類UsernamePasswordAuthenticationFilter過濾器。在UsernamePasswordAuthenticationFilter過濾器調用attemptAuthentication()方法現實主要的兩步過程:緩存

  1. 建立擁有用戶的詳情信息的Authentication對象,在默認的UsernamePasswordAuthenticationFilter中將建立UsernamePasswordAuthenticationToken的Authentication對象;架構

  2. AuthenticationManager調用authenticate()方法進行認證過程,在默認狀況,使用ProviderManager類進行認證。 app

UsernamePasswordAuthenticationFilter源碼分析:框架

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { .... public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { ..... //1.建立擁有用戶的詳情信息的Authentication對象
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); ​ // Allow subclasses to set the "details" property
 setDetails(request, authRequest); //2.AuthenticationManager進行認證
        return this.getAuthenticationManager().authenticate(authRequest); } ... }  

1.2請求通過過濾器處理以後,在AuthenticationManager以及ProviderManager認證

在UsernamePasswordAuthenticationFilter中看出,將調用AuthenticationManager接口的authenticate()方法進行詳細認證。默認狀況將使用AuthenticationManager子類ProviderManager的authenticate()進行認證,能夠分紅三個主要過程:ide

  1. AuthenticationProvide.authenticate()進行認證,默認下,將使用AbstractUserDetailsAuthenticationProvider進行認證;源碼分析

  2. 認證成功後,從authentication中刪除憑據和其餘機密數據,不然拋出異常或者認證失敗;post

  3. 發佈認證成功事件,並將Authentication對象保存到security context中。

ProviderManager源碼分析:

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { ... public Authentication authenticate(Authentication authentication) throws AuthenticationException { ... //AuthenticationProvider依次進行認證
        for (AuthenticationProvider provider : getProviders()) { ... try { //1.1進行認證,並返回Authentication對象
                result = provider.authenticate(authentication); ​ if (result != null) { copyDetails(authentication, result); break; } } ... catch (AuthenticationException e) { lastException = e; } } if (result == null && parent != null) { // Allow the parent to try.
            try { //1.2若是1.1認證中沒有一個驗證經過,則使用父類型AuthenticationManager進行驗證
                result = parent.authenticate(authentication); } catch (ProviderNotFoundException e) { // ignore as we will throw below if no other exception occurred prior to // calling parent and the parent // may throw ProviderNotFound even though a provider in the child already // handled the request
 } catch (AuthenticationException e) { lastException = e; } } //2.從authentication中刪除憑據和其餘機密數據
        if (result != null) { if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) { // Authentication is complete. Remove credentials and other secret data // from authentication
 ((CredentialsContainer) result).eraseCredentials(); } //3.發佈認證成功事件,並將Authentication對象保存到security context中
 eventPublisher.publishAuthenticationSuccess(result); return result; } }

1.3 認證過程詳細處理:AuthenticationProvider、AbstractUserDetailsAuthenticationProvider以及DaoAuthenticationProvider

在默認認證詳細處理過程當中,AuthenticationProvider認證由AbstractUserDetailsAuthenticationProvider抽象類以及AbstractUserDetailsAuthenticationProvider的子類DaoAuthenticationProvider進行方法重寫協助共同工做進行認證的。主要能夠分紅如下步驟:

  1. 獲取用戶信息UserDetails,首先從緩存中讀取信息,若是緩存中沒有的化,在UserDetailsService中加載,其最主要能夠從咱們自定義的UserDetailsService進行讀取用戶信息UserDetails;

  2. 驗證三步走: 1). preAuthenticationChecks

    2). additionalAuthenticationChecks:使用PasswordEncoder.matches()方法進行認證,其驗證方式中驗證數據已通過PasswordEncoder算法加密,能夠經過實現PasswordEncoder接口來定義算法加密方式。

    3). postAuthenticationChecks

  3. 將已經過驗證的用戶信息封裝成 UsernamePasswordAuthenticationToken對象並返回;該對象封裝了用戶的身份信息,以及相應的權限信息。

AbstractUserDetailsAuthenticationProvider主要功能提供authenticate()認證方法以及給DaoAuthenticationProvider重寫方法源碼分析:

  
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { ... public Authentication authenticate(Authentication authentication) throws AuthenticationException { ... boolean cacheWasUsed = true; //1.1獲取緩存中UserDetails信息
            UserDetails user = this.userCache.getUserFromCache(username); //1.2 若是緩存中沒有信息,從UserDetailsService中獲取
            if (user == null) { cacheWasUsed = false; try { //使用DaoAuthenticationProvider中重寫的方法去獲取信息
                    user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); }catch{ ... } ... try { //進行檢驗認證
 preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); }catch{ ... } ... postAuthenticationChecks.check(user); .... // 將已經過驗證的用戶信息封裝成 UsernamePasswordAuthenticationToken對象並返回
            return createSuccessAuthentication(principalToReturn, authentication, user); }

 

DaoAuthenticationProvider功能主要爲認證憑證加密PasswordEncoder,以及重寫AbstractUserDetailsAuthenticationProvider抽象類的retrieveUser、additionalAuthenticationChecks方法,其中retrieveUser主要是獲取UserDetails信息,源碼分析

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { prepareTimingAttackProtection(); try { //根據UserDetailsService獲取UserDetails信息,從自定義的UserDetailsService獲取
        UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; } catch (UsernameNotFoundException ex) { mitigateAgainstTimingAttack(authentication); throw ex; } catch (InternalAuthenticationServiceException ex) { throw ex; } catch (Exception ex) { throw new InternalAuthenticationServiceException(ex.getMessage(), ex); } }

additionalAuthenticationChecks主要使用PasswordEncoder進行密碼驗證,源碼分析:

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); ​ throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } ​ String presentedPassword = authentication.getCredentials().toString(); //進行密碼驗證
    if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { logger.debug("Authentication failed: password does not match stored value"); ​ throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } }

 

1.4 認證中所需的認證憑證獲取:UserDetailsService

在認證中必須獲取認證憑證,從UserDetailsService獲取到認證憑證,UserDetailsService接口只有一個方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

經過用戶名 username 調用方法 loadUserByUsername 返回了一個UserDetails接口對象:

public interface UserDetails extends Serializable { //1.權限集合
    Collection<? extends GrantedAuthority> getAuthorities(); //2.密碼 
 String getPassword(); //3.用戶名
 String getUsername(); //4.用戶是否過時
    boolean isAccountNonExpired(); //5.是否鎖定 
    boolean isAccountNonLocked(); //6.用戶密碼是否過時 
    boolean isCredentialsNonExpired(); //7.帳號是否可用(可理解爲是否刪除)
    boolean isEnabled(); }

咱們經過實現UserDetailsService自定義獲取UserDetails類,能夠從不一樣數據源中獲取認證憑證。

1.5 總結

總結Spring Security(二)--WebSecurityConfigurer配置以及filter順序和本節Spring security(三)想要實現簡單認證過程:

  1. 第一步:配置WebSecurityConfig

  2. 第二步: 實現自定義UserDetailsService,自定義從數據源碼獲取認證憑證。

2 Spring boot與Spring security整合

2.1配置WebSecurityConfig

@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // TODO Auto-generated method stub //super.configure(http);
 http .csrf().disable() .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .loginProcessingUrl("/login/form") .failureUrl("/login-error") .permitAll() //表單登陸,permitAll()表示這個不須要驗證 登陸頁面,登陸失敗頁面
 .and() .logout().permitAll(); } }

2.2 UserDetailsService實現

@service public class CustomUserService implements UserDetailsService { @Autowired private UserInfoMapper userInfoMapper; @Autowired private PermissionInfoMapper permissionInfoMapper; @Autowired private BCryptPasswordEncoderService bCryptPasswordEncoderService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // TODO Auto-generated method stub //這裏能夠能夠經過username(登陸時輸入的用戶名)而後到數據庫中找到對應的用戶信息,並構建成咱們本身的UserInfo來返回。
        UserInfoDTO user = userInfoMapper.getUserInfoByUserName(username); if (user != null) { List<PermissionInfoDTO> permissionInfoDTOS = permissionInfoMapper.findByAdminUserId(userInfo.getId()); List<GrantedAuthority> grantedAuthorityList = new ArrayList<>(); for (PermissionInfoDTO permissionInfoDTO : permissionInfoDTOS) { if (permissionInfoDTO != null && permissionInfoDTO.getPermissionName() != null) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority( permissionInfoDTO.getPermissionName()); grantedAuthorityList.add(grantedAuthority); } } return new User(userInfo.getUserName(), bCryptPasswordEncoderService.encode(userInfo.getPasswaord()), grantedAuthorityList); }else { throw new UsernameNotFoundException("admin" + username + "do not exist"); } } }

往期文章:

各位看官還能夠嗎?喜歡的話,動動手指點個贊💗,點個關注唄!!謝謝支持!

也歡迎關注公衆號【Ccww筆記】,原創技術文章第一時間推出

 

getAuthenticationManager().authenticate進行認證以及返回獲取Authentication實體)

原文出處:https://www.cnblogs.com/Ccwwlx/p/12033546.html

相關文章
相關標籤/搜索