在前面兩節Spring security (一)架構框架-Component、Service、Filter分析)和Spring Security(二)--WebSecurityConfigurer配置以及filter順序)爲Spring Security認證做好了準備,可讓咱們更好的理解認證過程以及項目代碼編寫。git
認證工做流程: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)
具體流程請看下面小節。算法
當請求來臨時,在默認狀況下,請求先通過AbstractAuthenticationProcessingFilter的子類UsernamePasswordAuthenticationFilter過濾器。在UsernamePasswordAuthenticationFilter過濾器調用attemptAuthentication()方法現實主要的兩步過程:spring
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); } ... }
在UsernamePasswordAuthenticationFilter中看出,將調用AuthenticationManager接口的authenticate()方法進行詳細認證。默認狀況將使用AuthenticationManager子類ProviderManager的authenticate()進行認證,能夠分紅三個主要過程:緩存
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; } }
在默認認證詳細處理過程當中,AuthenticationProvider認證由AbstractUserDetailsAuthenticationProvider抽象類以及AbstractUserDetailsAuthenticationProvider的子類DaoAuthenticationProvider進行方法重寫協助共同工做進行認證的。主要能夠分紅如下步驟:架構
2). additionalAuthenticationChecks:使用PasswordEncoder.matches()方法進行認證,其驗證方式中驗證數據已通過PasswordEncoder算法加密,能夠經過實現PasswordEncoder接口來定義算法加密方式。 app
3). postAuthenticationChecks框架
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")); } }
在認證中必須獲取認證憑證,從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類,能夠從不一樣數據源中獲取認證憑證。
總結Spring Security(二)--WebSecurityConfigurer配置以及filter順序)和本節Spring security(三)想要實現簡單認證過程:
@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(); } }
@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"); } } }
後續會spring security認證的擴展知識Spring Security OAuth2等,以及項目demo:Spring Security OAuth2 整合 JWT、ip、短信以及微信方式登錄的代碼分析與分享。最後若有錯誤可評論告知。
最後可關注公衆號:【Ccww筆記】 一塊兒學習,天天會分享乾貨,還有學習視頻領取!