Spring security(三)---認證過程
關注我,可以獲取最新知識、經典面試題以及微服務技術分享
在前面兩節Spring security (一)架構框架-Component、Service、Filter分析和Spring Security(二)–WebSecurityConfigurer配置以及filter順序為Spring Security認證作好了準備,可以讓我們更好的理解認證過程以及專案程式碼編寫。
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()方法現實主要的兩步過程:
- 建立擁有使用者的詳情資訊的Authentication物件,在預設的UsernamePasswordAuthenticationFilter中將建立UsernamePasswordAuthenticationToken的Authentication物件;
- 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()進行認證,可以分成三個主要過程:
- AuthenticationProvide.authenticate()進行認證,預設下,將使用AbstractUserDetailsAuthenticationProvider進行認證;
- 認證成功後,從authentication中刪除憑據和其他機密資料,否則丟擲異常或者認證失敗;
- 釋出認證成功事件,並將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進行方法重寫協助共同工作進行認證的。主要可以分成以下步驟:
-
獲取使用者資訊UserDetails,首先從快取中讀取資訊,如果快取中沒有的化,在UserDetailsService中載入,其最主要可以從我們自定義的UserDetailsService進行讀取使用者資訊UserDetails;
-
驗證三步走:
1). preAuthenticationChecks2). 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"));
}
}
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(三)想要實現簡單認證過程:
- 第一步:配置WebSecurityConfig
- 第二步: 實現自定義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");
}
}
}
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4328/viewspace-2824144/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spring Security原始碼分析一:Spring Security認證過程Spring原始碼
- Spring Security系列之認證過程(六)Spring
- 淺析Spring Security 的認證過程及相關過濾器Spring過濾器
- Spring Security認證提供程式Spring
- spring security許可權認證Spring
- Spring Security認證器實現Spring
- Spring認證_什麼是Spring Security?Spring
- Spring security(四)-spring boot +spring security簡訊認證+redis整合Spring BootRedis
- Spring Security OAuth2.0認證授權三:使用JWT令牌SpringOAuthJWT
- spring security 認證原始碼跟蹤Spring原始碼
- spring security 自定義認證登入Spring
- 【認證與授權】Spring Security系列之認證流程解析Spring
- Spring Security原始碼分析二:Spring Security授權過程Spring原始碼
- 【Spring Security】實現多種認證方式Spring
- Spring Security 啟動過程分析Spring
- 五、Spring Boot整合Spring Security之認證流程2Spring Boot
- spring security oauth2 搭建認證中心demoSpringOAuth
- SpringBoot + Spring Security 學習筆記(三)實現圖片驗證碼認證Spring Boot筆記
- 【認證與授權】Spring Security的授權流程Spring
- sso與spring security整合 預認證場景 PreAuthenticatedSpring
- Spring Security系列之授權過程(七)Spring
- Spring Security OAuth2.0認證授權四:分散式系統認證授權SpringOAuth分散式
- Spring Security實戰三:說說我的認識Spring
- Spring Security 實戰乾貨:使用 JWT 認證訪問介面SpringJWT
- 最簡單易懂的 Spring Security 身份認證流程講解Spring
- SpringBoot安全認證SecuritySpring Boot
- 六、Spring Boot整合Spring Security之前後分離認證流程最佳方案Spring Boot
- 七、Spring Boot整合Spring Security之前後分離認證最佳實現Spring Boot
- Openssl 設定 雙向認證證書的過程
- Spring Security——基於表單登入認證原理及實現Spring
- 深入Spring Security-獲取認證機制核心原理講解Spring
- Security+認證812分輕鬆考過(備戰分享)
- 域滲透之初識Kerberos認證過程ROS
- Spring Security OAuth2.0認證授權二:搭建資源服務SpringOAuth
- Spring Cloud Security OAuth2.0 認證授權系列(一) 基礎概念SpringCloudOAuth
- SpringBoot整合Spring security JWT實現介面許可權認證Spring BootJWT
- 深入淺出:使用Java和Spring Security實現認證與授權JavaSpring
- 深入Spring Security魔幻山谷-獲取認證機制核心原理講解(新版)Spring