在前面兩節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進行方法重寫協助共同工做進行認證的。主要能夠分紅如下步驟:架構
獲取用戶信息UserDetails,首先從緩存中讀取信息,若是緩存中沒有的化,在UserDetailsService中加載,其最主要能夠從咱們自定義的UserDetailsService進行讀取用戶信息UserDetails;app
驗證三步走:
1). preAuthenticationChecks框架
2). additionalAuthenticationChecks:使用PasswordEncoder.matches()方法進行認證,其驗證方式中驗證數據已通過PasswordEncoder算法加密,能夠經過實現PasswordEncoder接口來定義算法加密方式。
3). postAuthenticationChecks
將已經過驗證的用戶信息封裝成 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"));
}
}
複製代碼
在認證中必須獲取認證憑證,從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、短信以及微信方式登錄的代碼分析與分享。最後若有錯誤可評論告知。
歡迎轉載,標明出處!!