Spring security(三)---認證過程

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

1.認證過程工做流程

認證工做流程:github

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()方法現實主要的兩步過程:spring

  1. 建立擁有用戶的詳情信息的Authentication對象,在默認的UsernamePasswordAuthenticationFilter中將建立UsernamePasswordAuthenticationToken的Authentication對象;
  2. AuthenticationManager調用authenticate()方法進行認證過程,在默認狀況,使用ProviderManager類進行認證。

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()進行認證,能夠分紅三個主要過程:緩存

  1. AuthenticationProvide.authenticate()進行認證,默認下,將使用AbstractUserDetailsAuthenticationProvider進行認證;
  2. 認證成功後,從authentication中刪除憑據和其餘機密數據,不然拋出異常或者認證失敗;
  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;app

  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");
         }
    }
}
複製代碼

2.3 github代碼

連接

  後續會spring security認證的擴展知識Spring Security OAuth2等,以及項目demo:Spring Security OAuth2 整合 JWT、ip、短信以及微信方式登錄的代碼分析與分享。最後若有錯誤可評論告知。

歡迎轉載,標明出處!!

相關文章
相關標籤/搜索